From 7a64fda3d4e9dfbbe4bf8d2c8cc83130b6f8d99d Mon Sep 17 00:00:00 2001 From: ptarabbia Date: Mon, 1 May 2017 14:47:04 -0400 Subject: [PATCH 1/2] add branch for duktapev2 From e34724b7bfe415f19b99bd75ad364fc2fca99a38 Mon Sep 17 00:00:00 2001 From: ptarabbia Date: Mon, 1 May 2017 14:48:55 -0400 Subject: [PATCH 2/2] Changes for duktape v2.x --- include/dukglue/dukvalue.h | 1188 +++++++++++++++++---------------- include/dukglue/public_util.h | 634 +++++++++--------- 2 files changed, 931 insertions(+), 891 deletions(-) diff --git a/include/dukglue/dukvalue.h b/include/dukglue/dukvalue.h index 88c2ed1..e8c177c 100644 --- a/include/dukglue/dukvalue.h +++ b/include/dukglue/dukvalue.h @@ -1,590 +1,600 @@ -#pragma once - -#include -#include -#include -#include - -#include "dukexception.h" - -// A variant class for Duktape values. -// This class is not really dependant on the rest of dukglue, but the rest of dukglue is integrated to support it. -// Script objects are persisted by copying a reference to the object into an array in the heap stash. -// When we need to push a reference to the object, we just look up that reference in the stash. - -// DukValues can be copied freely. We use reference counting behind the scenes to keep track of when we need -// to remove our reference from the heap stash. Memory for reference counting is only allocated once a DukValue -// is copied (either by copy constructor or operator=). std::move can be used if you are trying to avoid ref counting -// for some reason. - -// One script object can have multiple, completely separate DukValues "pointing" to it - in this case, there will be -// multiple entries in the "ref array" that point to the same object. This will happen if the same script object is -// put on the stack and turned into a DukValue multiple times independently (copy-constructing/operator=-ing -// DukValues will not do this!). This is okay, as we are only keeping track of these objects to prevent garbage -// collection (and access them later). This could be changed to use a map structure to look up one canonical entry per -// script object in the "ref array" (I guess it would be more like a ref map in this case), but this would require a map -// lookup every time we construct a DukValue. The performance difference probably isn't *that* noticeable (a good map -// would probably be amortized constant-time lookup), but I am guessing constructing many separate DukValues that point -// to the same script object isn't a very common thing. -class DukValue { -public: - enum Type : uint8_t { - //NONE = DUK_TYPE_NONE, - UNDEFINED = DUK_TYPE_UNDEFINED, - NULLREF = DUK_TYPE_NULL, - BOOLEAN = DUK_TYPE_BOOLEAN, - NUMBER = DUK_TYPE_NUMBER, - STRING = DUK_TYPE_STRING, - OBJECT = DUK_TYPE_OBJECT, - BUFFER = DUK_TYPE_BUFFER, // not implemented - POINTER = DUK_TYPE_POINTER, - LIGHTFUNC = DUK_TYPE_LIGHTFUNC // not implemented - }; - - // default constructor just makes an undefined-type DukValue - inline DukValue() : mContext(NULL), mType(UNDEFINED), mRefCount(NULL) {} - - virtual ~DukValue() { - // release any references we have - release_ref_count(); - } - - // move constructor - inline DukValue(DukValue&& move) { - mContext = move.mContext; - mType = move.mType; - mPOD = move.mPOD; - mRefCount = move.mRefCount; - - if (mType == STRING) - mString = std::move(move.mString); - - move.mType = UNDEFINED; - move.mRefCount = NULL; - } - - inline DukValue& operator=(const DukValue& rhs) { - // free whatever we had - release_ref_count(); - - // copy things - mContext = rhs.mContext; - mType = rhs.mType; - mPOD = rhs.mPOD; - - if (mType == STRING) - mString = rhs.mString; - - if (mType == OBJECT) - { - // ref counting increment - if (rhs.mRefCount == NULL) { - // not ref counted before, need to allocate memory - const_cast(rhs).mRefCount = new int(2); - mRefCount = rhs.mRefCount; - } else { - // already refcounting, just increment - mRefCount = rhs.mRefCount; - *mRefCount = *mRefCount + 1; - } - } - - return *this; - } - - // copy constructor - inline DukValue(const DukValue& copy) : DukValue() { - *this = copy; - } - - // equality operator - inline bool operator==(const DukValue& rhs) const - { - if (mType != rhs.mType || mContext != rhs.mContext) - return false; - - switch (mType) { - case UNDEFINED: - case NULLREF: - return true; - case BOOLEAN: - return mPOD.boolean == rhs.mPOD.boolean; - case NUMBER: - return mPOD.number == rhs.mPOD.number; - case STRING: - return mString == rhs.mString; - - case OBJECT: - { - // this could be optimized to only push ref_array once... - this->push(); - rhs.push(); - bool equal = duk_equals(mContext, -1, -2) ? true : false; - duk_pop_2(mContext); - return equal; - } - - case POINTER: - return mPOD.pointer == rhs.mPOD.pointer; - - case BUFFER: - case LIGHTFUNC: - default: - throw DukException() << "operator== not implemented (" << type_name() << ")"; - } - } - - inline bool operator!=(const DukValue& rhs) const { - return !(*this == rhs); - } - - // copies the object at idx on the stack into a new DukValue and returns it - static DukValue copy_from_stack(duk_context* ctx, duk_idx_t idx = -1) { - DukValue value; - value.mContext = ctx; - value.mType = (Type) duk_get_type(ctx, idx); - switch (value.mType) { - case UNDEFINED: - break; - - case NULLREF: - value.mPOD.pointer = NULL; - break; - - case BOOLEAN: - value.mPOD.boolean = duk_require_boolean(ctx, idx) ? true : false; - break; - - case NUMBER: - value.mPOD.number = duk_require_number(ctx, idx); - break; - - case STRING: - { - duk_size_t len; - const char* data = duk_get_lstring(ctx, idx, &len); - value.mString.assign(data, len); - break; - } - - case OBJECT: - value.mPOD.ref_array_idx = stash_ref(ctx, idx); - break; - - case POINTER: - value.mPOD.pointer = duk_require_pointer(ctx, idx); - break; - - case BUFFER: - case LIGHTFUNC: - default: - throw DukException() << "Cannot turn type into DukValue (" << value.type_name() << ")"; - } - - return value; - } - -protected: - static duk_ret_t json_decode_safe(duk_context* ctx) - { - duk_json_decode(ctx, -1); - return 1; - } - -public: - static_assert(sizeof(char) == 1, "Serialization probably broke"); - static DukValue deserialize(duk_context* ctx, const char* data, size_t data_len) { - DukValue v; - v.mContext = ctx; - v.mType = *((Type*)data); - - const char* data_ptr = data + sizeof(Type); - data_len -= sizeof(Type); - - switch (v.mType) { - case UNDEFINED: - case NULLREF: - break; - - case BOOLEAN: - { - if (data_len < 1) - throw DukException() << "Malformed boolean data"; - - v.mPOD.boolean = data[1] == 1 ? true : false; - break; - } - - case NUMBER: - { - if (data_len < sizeof(double)) - throw DukException() << "Malformed number data"; - - v.mPOD.number = *((double*)data_ptr); - break; - } - - case STRING: - { - if (data_len < sizeof(uint32_t)) - throw DukException() << "Malformed string data (no length)"; - uint32_t str_len = *((uint32_t*)data_ptr); - - if (data_len < sizeof(uint32_t) + str_len) - throw DukException() << "Malformed string data (appears truncated)"; - - const char* str_data = (data_ptr + sizeof(uint32_t)); - v.mString.assign(str_data, str_len); - break; - } - - case OBJECT: - { - if (data_len < sizeof(uint32_t)) - throw DukException() << "Malformed object JSON data (no length)"; - uint32_t json_len = *((uint32_t*)data_ptr); - - if (data_len < sizeof(uint32_t) + json_len) - throw DukException() << "Malformed object JSON data (appears truncated)"; - - const char* json_data = (data_ptr + sizeof(uint32_t)); - duk_push_lstring(ctx, json_data, json_len); - int rc = duk_safe_call(ctx, &json_decode_safe, 1, 1); - if (rc) { - throw DukErrorException(ctx, rc) << "Could not decode JSON"; - } else { - v.mPOD.ref_array_idx = stash_ref(ctx, -1); - duk_pop(ctx); - } - break; - } - - default: - throw DukException() << "not implemented"; - } - - return v; - } - - // same as above (copy_from_stack), but also removes the value we copied from the stack - static DukValue take_from_stack(duk_context* ctx, duk_idx_t idx = -1) { - DukValue val = copy_from_stack(ctx, idx); - duk_remove(ctx, idx); - return val; - } - - // push the value we hold onto the stack - inline void push() const { - duk_context* ctx = mContext; - - switch (mType) { - case UNDEFINED: - duk_push_undefined(ctx); - break; - case NULLREF: - duk_push_null(ctx); - break; - - case BOOLEAN: - duk_push_boolean(ctx, mPOD.boolean); - break; - - case NUMBER: - duk_push_number(ctx, mPOD.number); - break; - - case STRING: - duk_push_lstring(ctx, mString.data(), mString.size()); - break; - - case OBJECT: - push_ref_array(ctx); - duk_get_prop_index(ctx, -1, mPOD.ref_array_idx); - duk_remove(ctx, -2); - break; - - case POINTER: - duk_push_pointer(ctx, mPOD.pointer); - break; - - case BUFFER: - case LIGHTFUNC: - default: - throw DukException() << "DukValue.push() not implemented for type (" << type_name() << ")"; - } - } - - // various (type-safe) getters - inline bool as_bool() const { - if (mType != BOOLEAN) - throw DukException() << "Expected boolean, got " << type_name(); - return mPOD.boolean; - } - - inline double as_double() const { - if (mType != NUMBER) - throw DukException() << "Expected number, got " << type_name(); - return mPOD.number; - } - - inline float as_float() const { - if (mType != NUMBER) - throw DukException() << "Expected number, got " << type_name(); - return static_cast(mPOD.number); - } - - inline duk_int_t as_int() const { - if (mType != NUMBER) - throw DukException() << "Expected number, got " << type_name(); - return static_cast(mPOD.number); - } - - inline duk_uint_t as_uint() const { - if (mType != NUMBER) - throw DukException() << "Expected number, got " << type_name(); - return static_cast(mPOD.number); - } - - inline void* as_pointer() const { - if (mType != POINTER && mType != NULLREF) - throw DukException() << "Expected pointer or null, got " << type_name(); - return mPOD.pointer; - } - - inline const std::string& as_string() const { - if (mType != STRING) - throw DukException() << "Expected string, got " << type_name(); - return mString; - } - - inline const char* as_c_string() const { - if (mType != STRING) - throw DukException() << "Expected string, got " << type_name(); - return mString.data(); - } - - inline Type type() const { - return mType; - } - - // same as duk_get_type_name(), but that's internal to Duktape, so we shouldn't use it - inline const char* type_name() const { - switch (mType) { - case UNDEFINED: return "undefined"; - case NULLREF: return "null"; - case BOOLEAN: return "boolean"; - case NUMBER: return "number"; - case STRING: return "string"; - case OBJECT: return "object"; - case BUFFER: return "buffer"; - case POINTER: return "pointer"; - case LIGHTFUNC: return "lightfunc"; - } - return "?"; - } - - inline duk_context* context() const { - return mContext; - } - - // Important limitations: - // - The returned value is binary and will not behave well if you treat it like a string (it will almost certainly contain '\0'). - // If you need to transport it like a string, maybe try encoding it as base64. - // - Strings longer than 2^32 (UINT32_MAX) characters will throw an exception. You can raise this to be a uint64_t if you need - // really long strings for some reason (be sure to change DukValue::deserialize() as well). - // - Objects are encoded to JSON and then sent like a string. If your object can't be encoded as JSON (i.e. it's a function), - // this will not work. This can be done, but I chose not to because it poses a security issue if you deserializing untrusted data. - // If you require this functionality, you'll have to add it yourself with using duk_dump_function(...). - static_assert(sizeof(char) == 1, "Serialization probably broke"); - std::vector serialize() const { - std::vector buff; - buff.resize(sizeof(Type)); - *((Type*)buff.data()) = mType; - - switch (mType) { - case UNDEFINED: - case NULLREF: - break; - - case BOOLEAN: - { - buff.push_back(mPOD.boolean ? 1 : 0); - break; - } - - case NUMBER: - { - buff.resize(buff.size() + sizeof(double)); - *((double*)(buff.data() + sizeof(Type))) = mPOD.number; - break; - } - - case STRING: - { - if (mString.length() > static_cast(UINT32_MAX)) - throw DukException() << "String length larger than uint32_t max"; - - uint32_t len = mString.length(); - buff.resize(buff.size() + sizeof(uint32_t) + len); - - uint32_t* len_ptr = (uint32_t*)(buff.data() + sizeof(Type)); - *len_ptr = len; - - char* out_ptr = (char*)(buff.data() + sizeof(Type) + sizeof(uint32_t)); - strncpy(out_ptr, mString.data(), len); // note: this will NOT be null-terminated - break; - } - - case OBJECT: - { - push(); - if (duk_is_function(mContext, -1)) { - duk_pop(mContext); - throw DukException() << "Functions cannot be serialized"; - // well, technically they can...see the comments at the start of this method - } - - std::string json = duk_json_encode(mContext, -1); - duk_pop(mContext); - - if (json.length() > static_cast(UINT32_MAX)) - throw DukException() << "JSON length larger than uint32_t max"; - - uint32_t len = json.length(); - buff.resize(buff.size() + sizeof(uint32_t) + len); - - uint32_t* len_ptr = (uint32_t*)(buff.data() + sizeof(Type)); - *len_ptr = len; - - char* out_ptr = (char*)(buff.data() + sizeof(Type) + sizeof(uint32_t)); - strncpy(out_ptr, json.data(), len); // note: this will NOT be null-terminated - break; - } - - default: - throw DukException() << "Type not implemented for serialization."; - } - - return buff; - } - -private: - // THIS IS COMPLETELY UNRELATED TO DETAIL_REFS.H. - // detail_refs.h stores a mapping of native object -> script object. - // This just stores arbitrary script objects (which likely have no native object backing them). - // If I was smarter I might merge the two implementations, but this one is simpler - // (since we don't need the std::map here). - static void push_ref_array(duk_context* ctx) - { - static const char* DUKVALUE_REF_ARRAY = "dukglue_dukvalue_refs"; - duk_push_heap_stash(ctx); - - if (!duk_has_prop_string(ctx, -1, DUKVALUE_REF_ARRAY)) { - duk_push_array(ctx); - - // ref_array[0] = 0 (initialize free list as empty) - duk_push_int(ctx, 0); - duk_put_prop_index(ctx, -2, 0); - - duk_put_prop_string(ctx, -2, DUKVALUE_REF_ARRAY); - } - - duk_get_prop_string(ctx, -1, DUKVALUE_REF_ARRAY); - duk_remove(ctx, -2); // pop heap stash - } - - // put a new reference into the ref array and return its index in the array - static duk_uint_t stash_ref(duk_context* ctx, duk_idx_t idx) - { - push_ref_array(ctx); - - // if idx is relative, we need to adjust it to deal with the array we just pushed - if (idx < 0) - idx--; - - // find next free index - // free indices are kept in a linked list, starting at ref_array[0] - duk_get_prop_index(ctx, -1, 0); - duk_uarridx_t next_free_idx = duk_get_uint(ctx, -1); - duk_pop(ctx); - - if (next_free_idx == 0) { - // no free spots in the array, make a new one at arr.length - next_free_idx = duk_get_length(ctx, -1); - } else { - // free spot found, need to remove it from the free list - // ref_array[0] = ref_array[next_free_idx] - duk_get_prop_index(ctx, -1, next_free_idx); - duk_put_prop_index(ctx, -2, 0); - } - - duk_dup(ctx, idx); // copy value we are storing (since store consumes it) - duk_put_prop_index(ctx, -2, next_free_idx); // store it (consumes duplicated value) - duk_pop(ctx); // pop ref array - - return next_free_idx; - } - - // remove ref_array_idx from the ref array and add its spot to the free list (at refs[0]) - static void free_ref(duk_context* ctx, duk_uarridx_t ref_array_idx) - { - push_ref_array(ctx); - - // add this spot to the free list - // refs[old_obj_idx] = refs[0] (implicitly gives up our reference) - duk_get_prop_index(ctx, -1, 0); - duk_put_prop_index(ctx, -2, ref_array_idx); - - // refs[0] = old_obj_idx - duk_push_uint(ctx, ref_array_idx); - duk_put_prop_index(ctx, -2, 0); - - duk_pop(ctx); // pop ref array - } - - // this is for reference counting - used to release our reference based on the state - // of mRefCount. If mRefCount is NULL, we never got copy constructed, so we have ownership - // of our reference and can free it. If it's not null and above 1, we decrement the counter - // (someone else owns the reference). If it's not null and equal to 1, we are the last owner - // of a previously shared reference, so we can free it. - void release_ref_count() - { - if (mType == OBJECT) - { - if (mRefCount != NULL) - { - // sharing with another DukValue, are we the only one left? - if (*mRefCount > 1) { // still someone else referencing this - *mRefCount = *mRefCount - 1; - } else { - // not sharing anymore, we can free it - free_ref(mContext, mPOD.ref_array_idx); - delete mRefCount; - } - - mRefCount = NULL; - } else { - // not sharing with any other DukValue, free it - free_ref(mContext, mPOD.ref_array_idx); - } - - mType = UNDEFINED; - } - } - - duk_context* mContext; - Type mType; // our type - one of the standard Duktape DUK_TYPE_* values - - // This holds the plain-old-data types. Since this is a variant, - // we hold only one value at a time, so this is a union to save - // a bit of space. - union ValueTypes { - bool boolean; - double number; - void* pointer; // if mType == NULLREF, this is 0 (otherwise holds pointer value when mType == POINTER) - duk_uarridx_t ref_array_idx; - } mPOD; - - std::string mString; // if it's a string, we store it with std::string - int* mRefCount; // if mType == OBJECT and we're sharing, this will point to our ref counter +#pragma once + +#include +#include +#include +#include + +#include "dukexception.h" + +// A variant class for Duktape values. +// This class is not really dependant on the rest of dukglue, but the rest of dukglue is integrated to support it. +// Script objects are persisted by copying a reference to the object into an array in the heap stash. +// When we need to push a reference to the object, we just look up that reference in the stash. + +// DukValues can be copied freely. We use reference counting behind the scenes to keep track of when we need +// to remove our reference from the heap stash. Memory for reference counting is only allocated once a DukValue +// is copied (either by copy constructor or operator=). std::move can be used if you are trying to avoid ref counting +// for some reason. + +// One script object can have multiple, completely separate DukValues "pointing" to it - in this case, there will be +// multiple entries in the "ref array" that point to the same object. This will happen if the same script object is +// put on the stack and turned into a DukValue multiple times independently (copy-constructing/operator=-ing +// DukValues will not do this!). This is okay, as we are only keeping track of these objects to prevent garbage +// collection (and access them later). This could be changed to use a map structure to look up one canonical entry per +// script object in the "ref array" (I guess it would be more like a ref map in this case), but this would require a map +// lookup every time we construct a DukValue. The performance difference probably isn't *that* noticeable (a good map +// would probably be amortized constant-time lookup), but I am guessing constructing many separate DukValues that point +// to the same script object isn't a very common thing. +class DukValue { +public: + enum Type : uint8_t { + //NONE = DUK_TYPE_NONE, + UNDEFINED = DUK_TYPE_UNDEFINED, + NULLREF = DUK_TYPE_NULL, + BOOLEAN = DUK_TYPE_BOOLEAN, + NUMBER = DUK_TYPE_NUMBER, + STRING = DUK_TYPE_STRING, + OBJECT = DUK_TYPE_OBJECT, + BUFFER = DUK_TYPE_BUFFER, // not implemented + POINTER = DUK_TYPE_POINTER, + LIGHTFUNC = DUK_TYPE_LIGHTFUNC // not implemented + }; + + // default constructor just makes an undefined-type DukValue + inline DukValue() : mContext(NULL), mType(UNDEFINED), mRefCount(NULL) {} + + virtual ~DukValue() { + // release any references we have + release_ref_count(); + } + + // move constructor + inline DukValue(DukValue&& move) { + mContext = move.mContext; + mType = move.mType; + mPOD = move.mPOD; + mRefCount = move.mRefCount; + + if (mType == STRING) + mString = std::move(move.mString); + + move.mType = UNDEFINED; + move.mRefCount = NULL; + } + + inline DukValue& operator=(const DukValue& rhs) { + // free whatever we had + release_ref_count(); + + // copy things + mContext = rhs.mContext; + mType = rhs.mType; + mPOD = rhs.mPOD; + + if (mType == STRING) + mString = rhs.mString; + + if (mType == OBJECT) + { + // ref counting increment + if (rhs.mRefCount == NULL) { + // not ref counted before, need to allocate memory + const_cast(rhs).mRefCount = new int(2); + mRefCount = rhs.mRefCount; + } else { + // already refcounting, just increment + mRefCount = rhs.mRefCount; + *mRefCount = *mRefCount + 1; + } + } + + return *this; + } + + // copy constructor + inline DukValue(const DukValue& copy) : DukValue() { + *this = copy; + } + + // equality operator + inline bool operator==(const DukValue& rhs) const + { + if (mType != rhs.mType || mContext != rhs.mContext) + return false; + + switch (mType) { + case UNDEFINED: + case NULLREF: + return true; + case BOOLEAN: + return mPOD.boolean == rhs.mPOD.boolean; + case NUMBER: + return mPOD.number == rhs.mPOD.number; + case STRING: + return mString == rhs.mString; + + case OBJECT: + { + // this could be optimized to only push ref_array once... + this->push(); + rhs.push(); + bool equal = duk_equals(mContext, -1, -2) ? true : false; + duk_pop_2(mContext); + return equal; + } + + case POINTER: + return mPOD.pointer == rhs.mPOD.pointer; + + case BUFFER: + case LIGHTFUNC: + default: + throw DukException() << "operator== not implemented (" << type_name() << ")"; + } + } + + inline bool operator!=(const DukValue& rhs) const { + return !(*this == rhs); + } + + // copies the object at idx on the stack into a new DukValue and returns it + static DukValue copy_from_stack(duk_context* ctx, duk_idx_t idx = -1) { + DukValue value; + value.mContext = ctx; + value.mType = (Type) duk_get_type(ctx, idx); + switch (value.mType) { + case UNDEFINED: + break; + + case NULLREF: + value.mPOD.pointer = NULL; + break; + + case BOOLEAN: + value.mPOD.boolean = duk_require_boolean(ctx, idx) ? true : false; + break; + + case NUMBER: + value.mPOD.number = duk_require_number(ctx, idx); + break; + + case STRING: + { + duk_size_t len; + const char* data = duk_get_lstring(ctx, idx, &len); + value.mString.assign(data, len); + break; + } + + case OBJECT: + value.mPOD.ref_array_idx = stash_ref(ctx, idx); + break; + + case POINTER: + value.mPOD.pointer = duk_require_pointer(ctx, idx); + break; + + case BUFFER: + case LIGHTFUNC: + default: + throw DukException() << "Cannot turn type into DukValue (" << value.type_name() << ")"; + } + + return value; + } + +protected: + +#if (DUK_VERSION >= 20000L) + static duk_ret_t json_decode_safe(duk_context* ctx, void* udata) +#else + static duk_ret_t json_decode_safe(duk_context* ctx) +#endif + { + duk_json_decode(ctx, -1); + return 1; + } + +public: + static_assert(sizeof(char) == 1, "Serialization probably broke"); + static DukValue deserialize(duk_context* ctx, const char* data, size_t data_len) { + DukValue v; + v.mContext = ctx; + v.mType = *((Type*)data); + + const char* data_ptr = data + sizeof(Type); + data_len -= sizeof(Type); + + switch (v.mType) { + case UNDEFINED: + case NULLREF: + break; + + case BOOLEAN: + { + if (data_len < 1) + throw DukException() << "Malformed boolean data"; + + v.mPOD.boolean = data[1] == 1 ? true : false; + break; + } + + case NUMBER: + { + if (data_len < sizeof(double)) + throw DukException() << "Malformed number data"; + + v.mPOD.number = *((double*)data_ptr); + break; + } + + case STRING: + { + if (data_len < sizeof(uint32_t)) + throw DukException() << "Malformed string data (no length)"; + uint32_t str_len = *((uint32_t*)data_ptr); + + if (data_len < sizeof(uint32_t) + str_len) + throw DukException() << "Malformed string data (appears truncated)"; + + const char* str_data = (data_ptr + sizeof(uint32_t)); + v.mString.assign(str_data, str_len); + break; + } + + case OBJECT: + { + if (data_len < sizeof(uint32_t)) + throw DukException() << "Malformed object JSON data (no length)"; + uint32_t json_len = *((uint32_t*)data_ptr); + + if (data_len < sizeof(uint32_t) + json_len) + throw DukException() << "Malformed object JSON data (appears truncated)"; + + const char* json_data = (data_ptr + sizeof(uint32_t)); + duk_push_lstring(ctx, json_data, json_len); + +#if (DUK_VERSION >= 20000L) + int rc = duk_safe_call(ctx, &json_decode_safe, nullptr, 1, 1); +#else + int rc = duk_safe_call(ctx, &json_decode_safe, 1, 1); +#endif + if (rc) { + throw DukErrorException(ctx, rc) << "Could not decode JSON"; + } else { + v.mPOD.ref_array_idx = stash_ref(ctx, -1); + duk_pop(ctx); + } + break; + } + + default: + throw DukException() << "not implemented"; + } + + return v; + } + + // same as above (copy_from_stack), but also removes the value we copied from the stack + static DukValue take_from_stack(duk_context* ctx, duk_idx_t idx = -1) { + DukValue val = copy_from_stack(ctx, idx); + duk_remove(ctx, idx); + return val; + } + + // push the value we hold onto the stack + inline void push() const { + duk_context* ctx = mContext; + + switch (mType) { + case UNDEFINED: + duk_push_undefined(ctx); + break; + case NULLREF: + duk_push_null(ctx); + break; + + case BOOLEAN: + duk_push_boolean(ctx, mPOD.boolean); + break; + + case NUMBER: + duk_push_number(ctx, mPOD.number); + break; + + case STRING: + duk_push_lstring(ctx, mString.data(), mString.size()); + break; + + case OBJECT: + push_ref_array(ctx); + duk_get_prop_index(ctx, -1, mPOD.ref_array_idx); + duk_remove(ctx, -2); + break; + + case POINTER: + duk_push_pointer(ctx, mPOD.pointer); + break; + + case BUFFER: + case LIGHTFUNC: + default: + throw DukException() << "DukValue.push() not implemented for type (" << type_name() << ")"; + } + } + + // various (type-safe) getters + inline bool as_bool() const { + if (mType != BOOLEAN) + throw DukException() << "Expected boolean, got " << type_name(); + return mPOD.boolean; + } + + inline double as_double() const { + if (mType != NUMBER) + throw DukException() << "Expected number, got " << type_name(); + return mPOD.number; + } + + inline float as_float() const { + if (mType != NUMBER) + throw DukException() << "Expected number, got " << type_name(); + return static_cast(mPOD.number); + } + + inline duk_int_t as_int() const { + if (mType != NUMBER) + throw DukException() << "Expected number, got " << type_name(); + return static_cast(mPOD.number); + } + + inline duk_uint_t as_uint() const { + if (mType != NUMBER) + throw DukException() << "Expected number, got " << type_name(); + return static_cast(mPOD.number); + } + + inline void* as_pointer() const { + if (mType != POINTER && mType != NULLREF) + throw DukException() << "Expected pointer or null, got " << type_name(); + return mPOD.pointer; + } + + inline const std::string& as_string() const { + if (mType != STRING) + throw DukException() << "Expected string, got " << type_name(); + return mString; + } + + inline const char* as_c_string() const { + if (mType != STRING) + throw DukException() << "Expected string, got " << type_name(); + return mString.data(); + } + + inline Type type() const { + return mType; + } + + // same as duk_get_type_name(), but that's internal to Duktape, so we shouldn't use it + inline const char* type_name() const { + switch (mType) { + case UNDEFINED: return "undefined"; + case NULLREF: return "null"; + case BOOLEAN: return "boolean"; + case NUMBER: return "number"; + case STRING: return "string"; + case OBJECT: return "object"; + case BUFFER: return "buffer"; + case POINTER: return "pointer"; + case LIGHTFUNC: return "lightfunc"; + } + return "?"; + } + + inline duk_context* context() const { + return mContext; + } + + // Important limitations: + // - The returned value is binary and will not behave well if you treat it like a string (it will almost certainly contain '\0'). + // If you need to transport it like a string, maybe try encoding it as base64. + // - Strings longer than 2^32 (UINT32_MAX) characters will throw an exception. You can raise this to be a uint64_t if you need + // really long strings for some reason (be sure to change DukValue::deserialize() as well). + // - Objects are encoded to JSON and then sent like a string. If your object can't be encoded as JSON (i.e. it's a function), + // this will not work. This can be done, but I chose not to because it poses a security issue if you deserializing untrusted data. + // If you require this functionality, you'll have to add it yourself with using duk_dump_function(...). + static_assert(sizeof(char) == 1, "Serialization probably broke"); + std::vector serialize() const { + std::vector buff; + buff.resize(sizeof(Type)); + *((Type*)buff.data()) = mType; + + switch (mType) { + case UNDEFINED: + case NULLREF: + break; + + case BOOLEAN: + { + buff.push_back(mPOD.boolean ? 1 : 0); + break; + } + + case NUMBER: + { + buff.resize(buff.size() + sizeof(double)); + *((double*)(buff.data() + sizeof(Type))) = mPOD.number; + break; + } + + case STRING: + { + if (mString.length() > static_cast(UINT32_MAX)) + throw DukException() << "String length larger than uint32_t max"; + + uint32_t len = mString.length(); + buff.resize(buff.size() + sizeof(uint32_t) + len); + + uint32_t* len_ptr = (uint32_t*)(buff.data() + sizeof(Type)); + *len_ptr = len; + + char* out_ptr = (char*)(buff.data() + sizeof(Type) + sizeof(uint32_t)); + strncpy(out_ptr, mString.data(), len); // note: this will NOT be null-terminated + break; + } + + case OBJECT: + { + push(); + if (duk_is_function(mContext, -1)) { + duk_pop(mContext); + throw DukException() << "Functions cannot be serialized"; + // well, technically they can...see the comments at the start of this method + } + + std::string json = duk_json_encode(mContext, -1); + duk_pop(mContext); + + if (json.length() > static_cast(UINT32_MAX)) + throw DukException() << "JSON length larger than uint32_t max"; + + uint32_t len = json.length(); + buff.resize(buff.size() + sizeof(uint32_t) + len); + + uint32_t* len_ptr = (uint32_t*)(buff.data() + sizeof(Type)); + *len_ptr = len; + + char* out_ptr = (char*)(buff.data() + sizeof(Type) + sizeof(uint32_t)); + strncpy(out_ptr, json.data(), len); // note: this will NOT be null-terminated + break; + } + + default: + throw DukException() << "Type not implemented for serialization."; + } + + return buff; + } + +private: + // THIS IS COMPLETELY UNRELATED TO DETAIL_REFS.H. + // detail_refs.h stores a mapping of native object -> script object. + // This just stores arbitrary script objects (which likely have no native object backing them). + // If I was smarter I might merge the two implementations, but this one is simpler + // (since we don't need the std::map here). + static void push_ref_array(duk_context* ctx) + { + static const char* DUKVALUE_REF_ARRAY = "dukglue_dukvalue_refs"; + duk_push_heap_stash(ctx); + + if (!duk_has_prop_string(ctx, -1, DUKVALUE_REF_ARRAY)) { + duk_push_array(ctx); + + // ref_array[0] = 0 (initialize free list as empty) + duk_push_int(ctx, 0); + duk_put_prop_index(ctx, -2, 0); + + duk_put_prop_string(ctx, -2, DUKVALUE_REF_ARRAY); + } + + duk_get_prop_string(ctx, -1, DUKVALUE_REF_ARRAY); + duk_remove(ctx, -2); // pop heap stash + } + + // put a new reference into the ref array and return its index in the array + static duk_uint_t stash_ref(duk_context* ctx, duk_idx_t idx) + { + push_ref_array(ctx); + + // if idx is relative, we need to adjust it to deal with the array we just pushed + if (idx < 0) + idx--; + + // find next free index + // free indices are kept in a linked list, starting at ref_array[0] + duk_get_prop_index(ctx, -1, 0); + duk_uarridx_t next_free_idx = duk_get_uint(ctx, -1); + duk_pop(ctx); + + if (next_free_idx == 0) { + // no free spots in the array, make a new one at arr.length + next_free_idx = duk_get_length(ctx, -1); + } else { + // free spot found, need to remove it from the free list + // ref_array[0] = ref_array[next_free_idx] + duk_get_prop_index(ctx, -1, next_free_idx); + duk_put_prop_index(ctx, -2, 0); + } + + duk_dup(ctx, idx); // copy value we are storing (since store consumes it) + duk_put_prop_index(ctx, -2, next_free_idx); // store it (consumes duplicated value) + duk_pop(ctx); // pop ref array + + return next_free_idx; + } + + // remove ref_array_idx from the ref array and add its spot to the free list (at refs[0]) + static void free_ref(duk_context* ctx, duk_uarridx_t ref_array_idx) + { + push_ref_array(ctx); + + // add this spot to the free list + // refs[old_obj_idx] = refs[0] (implicitly gives up our reference) + duk_get_prop_index(ctx, -1, 0); + duk_put_prop_index(ctx, -2, ref_array_idx); + + // refs[0] = old_obj_idx + duk_push_uint(ctx, ref_array_idx); + duk_put_prop_index(ctx, -2, 0); + + duk_pop(ctx); // pop ref array + } + + // this is for reference counting - used to release our reference based on the state + // of mRefCount. If mRefCount is NULL, we never got copy constructed, so we have ownership + // of our reference and can free it. If it's not null and above 1, we decrement the counter + // (someone else owns the reference). If it's not null and equal to 1, we are the last owner + // of a previously shared reference, so we can free it. + void release_ref_count() + { + if (mType == OBJECT) + { + if (mRefCount != NULL) + { + // sharing with another DukValue, are we the only one left? + if (*mRefCount > 1) { // still someone else referencing this + *mRefCount = *mRefCount - 1; + } else { + // not sharing anymore, we can free it + free_ref(mContext, mPOD.ref_array_idx); + delete mRefCount; + } + + mRefCount = NULL; + } else { + // not sharing with any other DukValue, free it + free_ref(mContext, mPOD.ref_array_idx); + } + + mType = UNDEFINED; + } + } + + duk_context* mContext; + Type mType; // our type - one of the standard Duktape DUK_TYPE_* values + + // This holds the plain-old-data types. Since this is a variant, + // we hold only one value at a time, so this is a union to save + // a bit of space. + union ValueTypes { + bool boolean; + double number; + void* pointer; // if mType == NULLREF, this is 0 (otherwise holds pointer value when mType == POINTER) + duk_uarridx_t ref_array_idx; + } mPOD; + + std::string mString; // if it's a string, we store it with std::string + int* mRefCount; // if mType == OBJECT and we're sharing, this will point to our ref counter }; \ No newline at end of file diff --git a/include/dukglue/public_util.h b/include/dukglue/public_util.h index 6c91b1b..d22ecf6 100644 --- a/include/dukglue/public_util.h +++ b/include/dukglue/public_util.h @@ -1,303 +1,333 @@ -#pragma once - -#include "dukexception.h" -#include "detail_traits.h" // for index_tuple/make_indexes - -// This file has some useful utility functions for users. -// Hopefully this saves you from wading through the implementation. - -/** - * @brief Push a value onto the duktape stack. - * - * WARNING: THIS IS NOT "PROTECTED." If an error occurs when pushing (unlikely, but possible), - * the Duktape fatal error handler will be invoked (and the program will probably terminate). - * - * @param ctx duktape context - * @param[in] val value to push - */ -template -void dukglue_push(duk_context* ctx, const FullT& val) { - // ArgStorage has some static_asserts in it that validate value types, - // so we typedef it to force ArgStorage to compile and run the asserts - typedef typename dukglue::types::ArgStorage::type ValidateReturnType; - - using namespace dukglue::types; - DukType::type>::template push(ctx, std::move(val)); -} - -template -void dukglue_push(duk_context* ctx, const T& arg, ArgTs... args) -{ - dukglue_push(ctx, arg); - dukglue_push(ctx, args...); -} - -inline void dukglue_push(duk_context* ctx) -{ - // no-op -} - - -/** - * WARNING: THIS IS NOT "PROTECTED." If an error occurs while reading (which is possible if you didn't - * explicitly check the type), the fatal Duktape error handler will be invoked, and the program - * will probably abort. - */ -template -void dukglue_read(duk_context* ctx, duk_idx_t arg_idx, RetT* out) -{ - // ArgStorage has some static_asserts in it that validate value types, - // so we typedef it to force ArgStorage to compile and run the asserts - typedef typename dukglue::types::ArgStorage::type ValidateReturnType; - - using namespace dukglue::types; - *out = DukType::type>::template read(ctx, arg_idx); -} - - -// methods - -// leaves return value on stack -template -void dukglue_call_method(duk_context* ctx, const ObjT& obj, const char* method_name, ArgTs... args) -{ - dukglue_push(ctx, obj); - duk_get_prop_string(ctx, -1, method_name); - - if (duk_check_type(ctx, -1, DUK_TYPE_UNDEFINED)) { - duk_error(ctx, DUK_ERR_REFERENCE_ERROR, "Method does not exist", method_name); - return; - } - - if (!duk_is_callable(ctx, -1)) { - duk_error(ctx, DUK_ERR_TYPE_ERROR, "Property is not callable"); - return; - } - - duk_swap_top(ctx, -2); - dukglue_push(ctx, args...); - duk_call_method(ctx, sizeof...(args)); -} - -namespace dukglue { -namespace detail { - -template -void call_method_safe_helper(duk_context* ctx, const ObjT& obj, const char* method_name, std::tuple&& tup, index_tuple indexes) -{ - dukglue_call_method(ctx, obj, method_name, std::forward(std::get(tup))...); -} - -template -typename std::enable_if::value, duk_idx_t>::type call_method_safe(duk_context* ctx) -{ - ObjT* obj = (ObjT*)duk_require_pointer(ctx, -3); - const char* method_name = (const char*)duk_require_pointer(ctx, -2); - typedef std::tuple TupleT; - TupleT* tuple = (TupleT*)duk_require_pointer(ctx, -1); - duk_pop_3(ctx); - - call_method_safe_helper(ctx, *obj, method_name, std::tuple(*tuple), typename make_indexes::type()); - return 1; -} - -template -typename std::enable_if::value, duk_idx_t>::type call_method_safe(duk_context* ctx) -{ - ObjT* obj = (ObjT*)duk_require_pointer(ctx, -4); - const char* method_name = (const char*)duk_require_pointer(ctx, -3); - typedef std::tuple TupleT; - TupleT* tuple = (TupleT*)duk_require_pointer(ctx, -2); - RetT* out = (RetT*)duk_require_pointer(ctx, -1); - duk_pop_n(ctx, 4); - - call_method_safe_helper(ctx, *obj, method_name, std::tuple(*tuple), typename make_indexes::type()); - dukglue_read(ctx, -1, out); - return 1; -} - -} -} - -template -typename std::enable_if::value, RetT>::type dukglue_pcall_method(duk_context* ctx, const ObjT& obj, const char* method_name, ArgTs... args) -{ - std::tuple tuple(args...); - - duk_push_pointer(ctx, (void*)&obj); - duk_push_pointer(ctx, (void*)method_name); - duk_push_pointer(ctx, (void*)&tuple); - - duk_idx_t rc = duk_safe_call(ctx, &dukglue::detail::call_method_safe, 3, 1); - if (rc != 0) - throw DukErrorException(ctx, rc); - - duk_pop(ctx); // remove result from stack -} - -template -typename std::enable_if::value, RetT>::type dukglue_pcall_method(duk_context* ctx, const ObjT& obj, const char* method_name, ArgTs... args) -{ - std::tuple tuple(args...); - RetT out; - - duk_push_pointer(ctx, (void*)&obj); - duk_push_pointer(ctx, (void*)method_name); - duk_push_pointer(ctx, (void*)&tuple); - duk_push_pointer(ctx, (void*)&out); - - duk_idx_t rc = duk_safe_call(ctx, &dukglue::detail::call_method_safe, 4, 1); - if (rc != 0) - throw DukErrorException(ctx, rc); - - duk_pop(ctx); // remove result from stack - return std::move(out); -} - - -// calls - -// leaves return value on the stack -template -void dukglue_call(duk_context* ctx, const ObjT& func, ArgTs... args) -{ - dukglue_push(ctx, func); - if (!duk_is_callable(ctx, -1)) { - duk_pop(ctx); - duk_error(ctx, DUK_ERR_TYPE_ERROR, "Object is not callable"); - return; - } - - dukglue_push(ctx, args...); - duk_call(ctx, sizeof...(args)); -} - - -// safe call -namespace dukglue { -namespace detail { - -template -void call_safe_helper(duk_context* ctx, const ObjT& obj, std::tuple&& tup, index_tuple indexes) -{ - dukglue_call(ctx, obj, std::forward(std::get(tup))...); -} - -// leaves result on stack -template -typename std::enable_if::value, duk_ret_t>::type call_safe(duk_context* ctx) -{ - ObjT* obj = (ObjT*) duk_require_pointer(ctx, -2); - typedef std::tuple TupleT; - TupleT* tuple = (TupleT*) duk_require_pointer(ctx, -1); - duk_pop_2(ctx); - - call_safe_helper(ctx, *obj, std::tuple(*tuple), typename make_indexes::type()); - return 1; -} - -// leaves result on stack -// The result is read into RetT here because it can potentially trigger an error (with duk_error). -// If we did it "above" this function, that error would trigger a panic instead of error handling. -template -typename std::enable_if::value, duk_ret_t>::type call_safe(duk_context* ctx) -{ - ObjT* obj = (ObjT*) duk_require_pointer(ctx, -3); - - typedef std::tuple TupleT; - TupleT* tuple = (TupleT*) duk_require_pointer(ctx, -2); - RetT* out = (RetT*) duk_require_pointer(ctx, -1); - duk_pop_3(ctx); - - call_safe_helper(ctx, *obj, std::tuple(*tuple), typename make_indexes::type()); - dukglue_read(ctx, -1, out); - return 1; -} - -} -} - -// Unlike duktape, this will remove the return value from the stack! -template -typename std::enable_if::value, RetT>::type dukglue_pcall(duk_context* ctx, const ObjT& obj, ArgTs... args) -{ - std::tuple tuple(args...); - - duk_push_pointer(ctx, (void*) &obj); - duk_push_pointer(ctx, (void*) &tuple); - duk_int_t rc = duk_safe_call(ctx, &dukglue::detail::call_safe, 2, 1); - if (rc != 0) - throw DukErrorException(ctx, rc); - duk_pop(ctx); // remove result from stack -} - -template -typename std::enable_if::value, RetT>::type dukglue_pcall(duk_context* ctx, const ObjT& obj, ArgTs... args) -{ - std::tuple tuple(args...); - RetT result; - - duk_push_pointer(ctx, (void*)&obj); - duk_push_pointer(ctx, (void*)&tuple); - duk_push_pointer(ctx, (void*)&result); - duk_int_t rc = duk_safe_call(ctx, &dukglue::detail::call_safe, 3, 1); - if (rc != 0) - throw DukErrorException(ctx, rc); - - duk_pop(ctx); // remove result from stack - return std::move(result); -} - - -// peval -namespace dukglue { -namespace detail { - -template -duk_ret_t eval_safe(duk_context* ctx) -{ - const char* str = (const char*) duk_require_pointer(ctx, -2); - RetT* out = (RetT*) duk_require_pointer(ctx, -1); - duk_pop_2(ctx); - - duk_eval_string(ctx, str); - dukglue_read(ctx, -1, out); - return 1; -} - -} -} - -template -typename std::enable_if::value, RetT>::type dukglue_peval(duk_context* ctx, const char* str) -{ - int prev_top = duk_get_top(ctx); - int rc = duk_peval_string(ctx, str); - if (rc != 0) - throw DukErrorException(ctx, rc); - - duk_pop_n(ctx, duk_get_top(ctx) - prev_top); // pop any results -} - -template -typename std::enable_if::value, RetT>::type dukglue_peval(duk_context* ctx, const char* str) -{ - int prev_top = duk_get_top(ctx); - - RetT ret; - duk_push_pointer(ctx, (void*)str); - duk_push_pointer(ctx, (void*)&ret); - int rc = duk_safe_call(ctx, &dukglue::detail::eval_safe, 2, 1); - if (rc != 0) - throw DukErrorException(ctx, rc); - duk_pop_n(ctx, duk_get_top(ctx) - prev_top); // pop any results - return ret; -} - -// register a global object (very simple helper, but very common for "Hello World"-ish applications) -template -inline void dukglue_register_global(duk_context* ctx, const T& obj, const char* name) -{ - dukglue_push(ctx, obj); - duk_put_global_string(ctx, name); +#pragma once + +#include "dukexception.h" +#include "detail_traits.h" // for index_tuple/make_indexes + +// This file has some useful utility functions for users. +// Hopefully this saves you from wading through the implementation. + +/** + * @brief Push a value onto the duktape stack. + * + * WARNING: THIS IS NOT "PROTECTED." If an error occurs when pushing (unlikely, but possible), + * the Duktape fatal error handler will be invoked (and the program will probably terminate). + * + * @param ctx duktape context + * @param[in] val value to push + */ +template +void dukglue_push(duk_context* ctx, const FullT& val) { + // ArgStorage has some static_asserts in it that validate value types, + // so we typedef it to force ArgStorage to compile and run the asserts + typedef typename dukglue::types::ArgStorage::type ValidateReturnType; + + using namespace dukglue::types; + DukType::type>::template push(ctx, std::move(val)); +} + +template +void dukglue_push(duk_context* ctx, const T& arg, ArgTs... args) +{ + dukglue_push(ctx, arg); + dukglue_push(ctx, args...); +} + +inline void dukglue_push(duk_context* ctx) +{ + // no-op +} + + +/** + * WARNING: THIS IS NOT "PROTECTED." If an error occurs while reading (which is possible if you didn't + * explicitly check the type), the fatal Duktape error handler will be invoked, and the program + * will probably abort. + */ +template +void dukglue_read(duk_context* ctx, duk_idx_t arg_idx, RetT* out) +{ + // ArgStorage has some static_asserts in it that validate value types, + // so we typedef it to force ArgStorage to compile and run the asserts + typedef typename dukglue::types::ArgStorage::type ValidateReturnType; + + using namespace dukglue::types; + *out = DukType::type>::template read(ctx, arg_idx); +} + + +// methods + +// leaves return value on stack +template +void dukglue_call_method(duk_context* ctx, const ObjT& obj, const char* method_name, ArgTs... args) +{ + dukglue_push(ctx, obj); + duk_get_prop_string(ctx, -1, method_name); + + if (duk_check_type(ctx, -1, DUK_TYPE_UNDEFINED)) { + duk_error(ctx, DUK_ERR_REFERENCE_ERROR, "Method does not exist", method_name); + return; + } + + if (!duk_is_callable(ctx, -1)) { + duk_error(ctx, DUK_ERR_TYPE_ERROR, "Property is not callable"); + return; + } + + duk_swap_top(ctx, -2); + dukglue_push(ctx, args...); + duk_call_method(ctx, sizeof...(args)); +} + +namespace dukglue { +namespace detail { + +template +void call_method_safe_helper(duk_context* ctx, const ObjT& obj, const char* method_name, std::tuple&& tup, index_tuple indexes) +{ + dukglue_call_method(ctx, obj, method_name, std::forward(std::get(tup))...); +} + +template + +#if (DUK_VERSION >= 20000L) +typename std::enable_if::value, duk_idx_t>::type call_method_safe(duk_context* ctx, void* udata) +#else +typename std::enable_if::value, duk_idx_t>::type call_method_safe(duk_context* ctx) +#endif +{ + ObjT* obj = (ObjT*)duk_require_pointer(ctx, -3); + const char* method_name = (const char*)duk_require_pointer(ctx, -2); + typedef std::tuple TupleT; + TupleT* tuple = (TupleT*)duk_require_pointer(ctx, -1); + duk_pop_3(ctx); + + call_method_safe_helper(ctx, *obj, method_name, std::tuple(*tuple), typename make_indexes::type()); + return 1; +} + +template +#if (DUK_VERSION >= 20000L) +typename std::enable_if::value, duk_idx_t>::type call_method_safe(duk_context* ctx, void* udata) +#else +typename std::enable_if::value, duk_idx_t>::type call_method_safe(duk_context* ctx) +#endif +{ + ObjT* obj = (ObjT*)duk_require_pointer(ctx, -4); + const char* method_name = (const char*)duk_require_pointer(ctx, -3); + typedef std::tuple TupleT; + TupleT* tuple = (TupleT*)duk_require_pointer(ctx, -2); + RetT* out = (RetT*)duk_require_pointer(ctx, -1); + duk_pop_n(ctx, 4); + + call_method_safe_helper(ctx, *obj, method_name, std::tuple(*tuple), typename make_indexes::type()); + dukglue_read(ctx, -1, out); + return 1; +} + +} +} + +template +typename std::enable_if::value, RetT>::type dukglue_pcall_method(duk_context* ctx, const ObjT& obj, const char* method_name, ArgTs... args) +{ + std::tuple tuple(args...); + + duk_push_pointer(ctx, (void*)&obj); + duk_push_pointer(ctx, (void*)method_name); + duk_push_pointer(ctx, (void*)&tuple); + +#if (DUK_VERSION >= 20000L) + duk_idx_t rc = duk_safe_call(ctx, &dukglue::detail::call_method_safe, nullptr, 3, 1); +#else + duk_idx_t rc = duk_safe_call(ctx, &dukglue::detail::call_method_safe, 3, 1); +#endif + if (rc != 0) + throw DukErrorException(ctx, rc); + + duk_pop(ctx); // remove result from stack +} + +template +typename std::enable_if::value, RetT>::type dukglue_pcall_method(duk_context* ctx, const ObjT& obj, const char* method_name, ArgTs... args) +{ + std::tuple tuple(args...); + RetT out; + + duk_push_pointer(ctx, (void*)&obj); + duk_push_pointer(ctx, (void*)method_name); + duk_push_pointer(ctx, (void*)&tuple); + duk_push_pointer(ctx, (void*)&out); + +#if (DUK_VERSION >= 20000L) + duk_idx_t rc = duk_safe_call(ctx, &dukglue::detail::call_method_safe, nullptr, 4, 1); +#else + duk_idx_t rc = duk_safe_call(ctx, &dukglue::detail::call_method_safe, 4, 1); +#endif + if (rc != 0) + throw DukErrorException(ctx, rc); + + duk_pop(ctx); // remove result from stack + return std::move(out); +} + + +// calls + +// leaves return value on the stack +template +void dukglue_call(duk_context* ctx, const ObjT& func, ArgTs... args) +{ + dukglue_push(ctx, func); + if (!duk_is_callable(ctx, -1)) { + duk_pop(ctx); + duk_error(ctx, DUK_ERR_TYPE_ERROR, "Object is not callable"); + return; + } + + dukglue_push(ctx, args...); + duk_call(ctx, sizeof...(args)); +} + + +// safe call +namespace dukglue { +namespace detail { + +template +void call_safe_helper(duk_context* ctx, const ObjT& obj, std::tuple&& tup, index_tuple indexes) +{ + dukglue_call(ctx, obj, std::forward(std::get(tup))...); +} + +// leaves result on stack +template +typename std::enable_if::value, duk_ret_t>::type call_safe(duk_context* ctx) +{ + ObjT* obj = (ObjT*) duk_require_pointer(ctx, -2); + typedef std::tuple TupleT; + TupleT* tuple = (TupleT*) duk_require_pointer(ctx, -1); + duk_pop_2(ctx); + + call_safe_helper(ctx, *obj, std::tuple(*tuple), typename make_indexes::type()); + return 1; +} + +// leaves result on stack +// The result is read into RetT here because it can potentially trigger an error (with duk_error). +// If we did it "above" this function, that error would trigger a panic instead of error handling. +template +typename std::enable_if::value, duk_ret_t>::type call_safe(duk_context* ctx) +{ + ObjT* obj = (ObjT*) duk_require_pointer(ctx, -3); + + typedef std::tuple TupleT; + TupleT* tuple = (TupleT*) duk_require_pointer(ctx, -2); + RetT* out = (RetT*) duk_require_pointer(ctx, -1); + duk_pop_3(ctx); + + call_safe_helper(ctx, *obj, std::tuple(*tuple), typename make_indexes::type()); + dukglue_read(ctx, -1, out); + return 1; +} + +} +} + +// Unlike duktape, this will remove the return value from the stack! +template +typename std::enable_if::value, RetT>::type dukglue_pcall(duk_context* ctx, const ObjT& obj, ArgTs... args) +{ + std::tuple tuple(args...); + + duk_push_pointer(ctx, (void*) &obj); + duk_push_pointer(ctx, (void*) &tuple); +#if (DUK_VERSION >= 20000L) + duk_int_t rc = duk_safe_call(ctx, &dukglue::detail::call_safe, nullptr, 2, 1); +#else + duk_int_t rc = duk_safe_call(ctx, &dukglue::detail::call_safe, 2, 1); +#endif + if (rc != 0) + throw DukErrorException(ctx, rc); + duk_pop(ctx); // remove result from stack +} + +template +typename std::enable_if::value, RetT>::type dukglue_pcall(duk_context* ctx, const ObjT& obj, ArgTs... args) +{ + std::tuple tuple(args...); + RetT result; + + duk_push_pointer(ctx, (void*)&obj); + duk_push_pointer(ctx, (void*)&tuple); + duk_push_pointer(ctx, (void*)&result); +#if (DUK_VERSION >= 20000L) + duk_int_t rc = duk_safe_call(ctx, &dukglue::detail::call_safe, nullptr, 3, 1); +#else + duk_int_t rc = duk_safe_call(ctx, &dukglue::detail::call_safe, nullptr, 3, 1); +#endif + if (rc != 0) + throw DukErrorException(ctx, rc); + + duk_pop(ctx); // remove result from stack + return std::move(result); +} + + +// peval +namespace dukglue { +namespace detail { + +template +duk_ret_t eval_safe(duk_context* ctx) +{ + const char* str = (const char*) duk_require_pointer(ctx, -2); + RetT* out = (RetT*) duk_require_pointer(ctx, -1); + duk_pop_2(ctx); + + duk_eval_string(ctx, str); + dukglue_read(ctx, -1, out); + return 1; +} + +} +} + +template +typename std::enable_if::value, RetT>::type dukglue_peval(duk_context* ctx, const char* str) +{ + int prev_top = duk_get_top(ctx); + int rc = duk_peval_string(ctx, str); + if (rc != 0) + throw DukErrorException(ctx, rc); + + duk_pop_n(ctx, duk_get_top(ctx) - prev_top); // pop any results +} + +template +typename std::enable_if::value, RetT>::type dukglue_peval(duk_context* ctx, const char* str) +{ + int prev_top = duk_get_top(ctx); + + RetT ret; + duk_push_pointer(ctx, (void*)str); + duk_push_pointer(ctx, (void*)&ret); + +#if (DUK_VERSION >= 20000L) + int rc = duk_safe_call(ctx, &dukglue::detail::eval_safe, nullptr, 2, 1); +#else + int rc = duk_safe_call(ctx, &dukglue::detail::eval_safe, 2, 1); +#endif + if (rc != 0) + throw DukErrorException(ctx, rc); + duk_pop_n(ctx, duk_get_top(ctx) - prev_top); // pop any results + return ret; +} + +// register a global object (very simple helper, but very common for "Hello World"-ish applications) +template +inline void dukglue_register_global(duk_context* ctx, const T& obj, const char* name) +{ + dukglue_push(ctx, obj); + duk_put_global_string(ctx, name); } \ No newline at end of file