From c578ea6318a9df409aa22cb9f3fc1a9045036f87 Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Wed, 15 Nov 2023 22:49:38 +0800 Subject: [PATCH] Optimize codes --- include/quickjspp.hpp | 833 ++++++++++++++++++++++----- src/generator/config/nodemanip.cpp | 10 +- src/generator/config/subexport.cpp | 14 +- src/generator/template/templates.cpp | 14 +- src/generator/template/templates.h | 2 +- src/parser/config/proxy.h | 8 +- src/parser/subparser.cpp | 12 +- src/script/script_quickjs.cpp | 49 +- src/script/script_quickjs.h | 171 +++--- 9 files changed, 814 insertions(+), 299 deletions(-) diff --git a/include/quickjspp.hpp b/include/quickjspp.hpp index 651c4dc3c..b1a2e3f25 100644 --- a/include/quickjspp.hpp +++ b/include/quickjspp.hpp @@ -1,7 +1,6 @@ #pragma once #include "quickjs/quickjs.h" -#include "quickjs/quickjs-libc.h" #include #include @@ -15,6 +14,12 @@ #include #include #include +#include +#include +#include +#include +#include +#include #if defined(__cpp_rtti) @@ -26,11 +31,30 @@ namespace qjs { +class Context; +class Value; /** Exception type. * Indicates that exception has occured in JS context. */ -class exception {}; +class exception { + JSContext * ctx; +public: + exception(JSContext * ctx) : ctx(ctx) {} + Context & context() const; + + /// Clears and returns the occurred exception. + Value get(); +}; + +/** std::shared_ptr, for compatibility with quickjspp v2. */ +template using shared_ptr = std::shared_ptr; +/** std::make_shared, for compatibility with quickjspp v2. */ +template +shared_ptr make_shared(JSContext *, Args&&... args) +{ + return std::make_shared(std::forward(args)...); +} /** Javascript conversion traits. * Describes how to convert type R to/from JSValue. Second template argument can be used for SFINAE/enable_if type filters. @@ -43,13 +67,13 @@ struct js_traits * @param v This value is passed as JSValueConst so it should be freed by the caller. * @throws exception in case of conversion error */ - static R unwrap(JSContext * ctx, JSValueConst v); + static R unwrap(JSContext * ctx, JSValueConst v) = delete; /** Create JSValue from an object of type R and JSContext. * This function is intentionally not implemented. User should implement this function for their own type. * @return Returns JSValue which should be freed by the caller or JS_EXCEPTION in case of error. */ - static JSValue wrap(JSContext * ctx, R value); + static JSValue wrap(JSContext * ctx, R value) = delete; }; /** Conversion traits for JSValue (identity). @@ -62,18 +86,18 @@ struct js_traits return JS_DupValue(ctx, v); } - static JSValue wrap(JSContext * ctx, JSValue v) noexcept + static JSValue wrap(JSContext * ctx, JSValue&& v) noexcept { return v; } }; /** Conversion traits for integers. + * Intentionally doesn't define traits for uint64_t since it can be typedefed to JSValue. (@see JS_NAN_BOXING) */ template -struct js_traits && sizeof(Int) <= sizeof(int64_t)>> +struct js_traits && sizeof(Int) <= sizeof(int64_t) && !std::is_same_v>> { - /// @throws exception static Int unwrap(JSContext * ctx, JSValueConst v) { @@ -81,14 +105,14 @@ struct js_traits && sizeof(Int) <= { int64_t r; if(JS_ToInt64(ctx, &r, v)) - throw exception{}; + throw exception{ctx}; return static_cast(r); } else { int32_t r; if(JS_ToInt32(ctx, &r, v)) - throw exception{}; + throw exception{ctx}; return static_cast(r); } } @@ -128,7 +152,7 @@ struct js_traits static void unwrap(JSContext * ctx, JSValueConst value) { if(JS_IsException(value)) - throw exception{}; + throw exception{ctx}; } }; @@ -142,7 +166,7 @@ struct js_traits { double r; if(JS_ToFloat64(ctx, &r, v)) - throw exception{}; + throw exception{ctx}; return r; } @@ -193,7 +217,7 @@ struct js_traits size_t plen; const char * ptr = JS_ToCStringLen(ctx, &plen, v); if(!ptr) - throw exception{}; + throw exception{ctx}; return detail::js_string{ctx, ptr, plen}; } @@ -351,7 +375,7 @@ struct js_traits> return unwrapPriority(ctx, v); } JS_ThrowTypeError(ctx, "Expected type %s", QJSPP_TYPENAME(std::variant)); - throw exception{}; + throw exception{ctx}; } template @@ -461,10 +485,17 @@ struct js_traits> return unwrapPriority(ctx, v); } - throw exception{}; + throw exception{ctx}; } }; +template +struct rest : std::vector +{ + using std::vector::vector; + using std::vector::operator=; +}; + namespace detail { /** Helper function to convert and then free JSValue. */ @@ -492,20 +523,44 @@ T unwrap_free(JSContext * ctx, JSValue val) } } +template +struct unwrap_arg_impl { + static auto unwrap(JSContext * ctx, int argc, JSValueConst * argv) + { + if (size_t(argc) <= I) { + JS_ThrowTypeError(ctx, "Expected at least %lu arguments but received %d", + (unsigned long)NArgs, argc); + throw exception{ctx}; + } + return js_traits>::unwrap(ctx, argv[I]); + } +}; + +template +struct unwrap_arg_impl, I, NArgs> { + static rest unwrap(JSContext * ctx, int argc, JSValueConst * argv) { + static_assert(I == NArgs - 1, "The `rest` argument must be the last function argument."); + rest result; + result.reserve(argc - I); + for (size_t i = I; i < size_t(argc); ++i) + result.push_back(js_traits::unwrap(ctx, argv[i])); + return result; + } +}; + template -Tuple unwrap_args_impl(JSContext * ctx, JSValueConst * argv, std::index_sequence) +Tuple unwrap_args_impl(JSContext * ctx, int argc, JSValueConst * argv, std::index_sequence) { - (void)ctx; - return Tuple{js_traits>>::unwrap(ctx, argv[I])...}; + return Tuple{unwrap_arg_impl, I, sizeof...(I)>::unwrap(ctx, argc, argv)...}; } /** Helper function to convert an array of JSValues to a tuple. * @tparam Args C++ types of the argv array */ template -std::tuple...> unwrap_args(JSContext * ctx, JSValueConst * argv) +std::tuple...> unwrap_args(JSContext * ctx, int argc, JSValueConst * argv) { - return unwrap_args_impl...>>(ctx, argv, std::make_index_sequence()); + return unwrap_args_impl...>>(ctx, argc, argv, std::make_index_sequence()); } /** Helper function to call f with an array of JSValues. @@ -518,40 +573,50 @@ std::tuple...> unwrap_args(JSContext * ctx, JSValueConst * ar * @return converted return value of f or JS_NULL if f returns void */ template -JSValue wrap_call(JSContext * ctx, Callable&& f, JSValueConst * argv) noexcept +JSValue wrap_call(JSContext * ctx, Callable&& f, int argc, JSValueConst * argv) noexcept { try { if constexpr(std::is_same_v) { - std::apply(std::forward(f), unwrap_args(ctx, argv)); + std::apply(std::forward(f), unwrap_args(ctx, argc, argv)); return JS_NULL; } else { return js_traits>::wrap(ctx, std::apply(std::forward(f), - unwrap_args(ctx, argv))); + unwrap_args(ctx, argc, argv))); } } catch(exception) { return JS_EXCEPTION; } + catch (std::exception const & err) + { + JS_ThrowInternalError(ctx, "%s", err.what()); + return JS_EXCEPTION; + } + catch (...) + { + JS_ThrowInternalError(ctx, "Unknown error"); + return JS_EXCEPTION; + } } /** Same as wrap_call, but pass this_value as first argument. * @tparam FirstArg type of this_value */ template -JSValue wrap_this_call(JSContext * ctx, Callable&& f, JSValueConst this_value, JSValueConst * argv) noexcept +JSValue wrap_this_call(JSContext * ctx, Callable&& f, JSValueConst this_value, int argc, JSValueConst * argv) noexcept { try { if constexpr(std::is_same_v) { - std::apply(std::forward(f), std::tuple_cat(unwrap_args(ctx, &this_value), - unwrap_args(ctx, argv))); + std::apply(std::forward(f), std::tuple_cat(unwrap_args(ctx, 1, &this_value), + unwrap_args(ctx, argc, argv))); return JS_NULL; } else @@ -559,14 +624,24 @@ JSValue wrap_this_call(JSContext * ctx, Callable&& f, JSValueConst this_value, J return js_traits>::wrap(ctx, std::apply(std::forward(f), std::tuple_cat( - unwrap_args(ctx, &this_value), - unwrap_args(ctx, argv)))); + unwrap_args(ctx, 1, &this_value), + unwrap_args(ctx, argc, argv)))); } } catch(exception) { return JS_EXCEPTION; } + catch (std::exception const & err) + { + JS_ThrowInternalError(ctx, "%s", err.what()); + return JS_EXCEPTION; + } + catch (...) + { + JS_ThrowInternalError(ctx, "Unknown error"); + return JS_EXCEPTION; + } } template @@ -585,6 +660,12 @@ void wrap_args(JSContext * ctx, JSValue * argv, Args&& ... args) wrap_args_impl(ctx, argv, std::make_tuple(std::forward(args)...), std::make_index_sequence()); } + +// Helper trait to obtain `T` in `T::*` expressions +template struct class_from_member_pointer { using type = void; }; +template struct class_from_member_pointer { using type = U; }; +template using class_from_member_pointer_t = typename class_from_member_pointer::type; + } // namespace detail /** A wrapper type for free and class member functions. @@ -608,9 +689,9 @@ struct js_traits> return JS_NewCFunction(ctx, [](JSContext * ctx, JSValueConst this_value, int argc, JSValueConst * argv) noexcept -> JSValue { if constexpr(PassThis) - return detail::wrap_this_call(ctx, F, this_value, argv); + return detail::wrap_this_call(ctx, F, this_value, argc, argv); else - return detail::wrap_call(ctx, F, argv); + return detail::wrap_call(ctx, F, argc, argv); }, fw.name, sizeof...(Args)); } @@ -624,7 +705,7 @@ struct js_traits> { return JS_NewCFunction(ctx, [](JSContext * ctx, JSValueConst this_value, int argc, JSValueConst * argv) noexcept -> JSValue { - return detail::wrap_this_call, Args...>(ctx, F, this_value, argv); + return detail::wrap_this_call, Args...>(ctx, F, this_value, argc, argv); }, fw.name, sizeof...(Args)); } @@ -638,7 +719,7 @@ struct js_traits> { return JS_NewCFunction(ctx, [](JSContext * ctx, JSValueConst this_value, int argc, JSValueConst * argv) noexcept -> JSValue { - return detail::wrap_this_call, Args...>(ctx, F, this_value, argv); + return detail::wrap_this_call, Args...>(ctx, F, this_value, argc, argv); }, fw.name, sizeof...(Args)); } @@ -656,6 +737,16 @@ struct ctor_wrapper const char * name = nullptr; }; +namespace detail { +/// equivalent to JS_GetPropertyStr(ctx, this_value, "prototype"); +inline JSValue GetPropertyPrototype(JSContext * ctx, JSValueConst this_value) +{ + // constant atom: doesn't need to be freed and doesn't change with context + static const JSAtom JS_ATOM_prototype = JS_NewAtom(ctx, "prototype"); + return JS_GetProperty(ctx, this_value, JS_ATOM_prototype); +} +} // namespace detail + /** Conversion to JSValue for ctor_wrapper. */ template struct js_traits> @@ -676,7 +767,7 @@ struct js_traits> #endif } - auto proto = JS_GetPropertyStr(ctx, this_value, "prototype"); + auto proto = detail::GetPropertyPrototype(ctx, this_value); if(JS_IsException(proto)) return proto; auto jsobj = JS_NewObjectProtoClass(ctx, proto, js_traits>::QJSClassId); @@ -684,9 +775,29 @@ struct js_traits> if(JS_IsException(jsobj)) return jsobj; - std::shared_ptr ptr = std::apply(std::make_shared, detail::unwrap_args(ctx, argv)); - JS_SetOpaque(jsobj, new std::shared_ptr(std::move(ptr))); - return jsobj; + try + { + std::shared_ptr ptr = std::apply(std::make_shared, detail::unwrap_args(ctx, argc, argv)); + JS_SetOpaque(jsobj, new std::shared_ptr(std::move(ptr))); + return jsobj; + } + catch (exception) + { + JS_FreeValue(ctx, jsobj); + return JS_EXCEPTION; + } + catch (std::exception const & err) + { + JS_FreeValue(ctx, jsobj); + JS_ThrowInternalError(ctx, "%s", err.what()); + return JS_EXCEPTION; + } + catch (...) + { + JS_FreeValue(ctx, jsobj); + JS_ThrowInternalError(ctx, "Unknown error"); + return JS_EXCEPTION; + } // return detail::wrap_call, Args...>(ctx, std::make_shared, argv); }, cw.name, sizeof...(Args), JS_CFUNC_constructor, 0); @@ -694,7 +805,7 @@ struct js_traits> }; -/** Conversions for std::shared_ptr. +/** Conversions for std::shared_ptr. Empty shared_ptr corresponds to JS_NULL. * T should be registered to a context before conversions. * @tparam T class type */ @@ -704,14 +815,83 @@ struct js_traits> /// Registered class id in QuickJS. inline static JSClassID QJSClassId = 0; + /// Signature of the function to obtain the std::shared_ptr from the JSValue. + using ptr_cast_fcn_t = std::function(JSContext*, JSValueConst)>; + + /// Used by registerDerivedClass to register new derived classes with this class' base type. + inline static std::function registerWithBase; + + /// Mapping between derived class' JSClassID and function to obtain the std::shared_ptr from the JSValue. + inline static std::unordered_map ptrCastFcnMap; + + /** Register a class as a derived class. + * + * @tparam D type of the derived class + * @param derived_class_id class id of the derived class + * @param ptr_cast_fcn function to obtain a std::shared_ptr from the JSValue + */ + template + static void registerDerivedClass(JSClassID derived_class_id, ptr_cast_fcn_t ptr_cast_fcn) { + static_assert(std::is_base_of::value && !std::is_same::value, "Type is not a derived class"); + using derived_ptr_cast_fcn_t = typename js_traits>::ptr_cast_fcn_t; + + // Register how to obtain the std::shared_ptr from the derived class. + ptrCastFcnMap[derived_class_id] = ptr_cast_fcn; + + // Propagate the registration to our base class (if any). + if (registerWithBase) registerWithBase(derived_class_id, ptr_cast_fcn); + + // Instrument the derived class so that it can propagate new derived classes to us. + auto old_registerWithBase = js_traits>::registerWithBase; + js_traits>::registerWithBase = + [old_registerWithBase = std::move(old_registerWithBase)] + (JSClassID derived_class_id, derived_ptr_cast_fcn_t derived_ptr_cast_fcn){ + if (old_registerWithBase) old_registerWithBase(derived_class_id, derived_ptr_cast_fcn); + registerDerivedClass(derived_class_id, [derived_cast_fcn = std::move(derived_ptr_cast_fcn)](JSContext * ctx, JSValueConst v) { + return std::shared_ptr(derived_cast_fcn(ctx, v)); + }); + }; + } + + template + static + std::enable_if_t || std::is_same_v> + ensureCanCastToBase() { } + + template + static + std::enable_if_t && !std::is_same_v> + ensureCanCastToBase() { + static_assert(std::is_base_of_v, "Type is not a derived class"); + + if(js_traits>::QJSClassId == 0) + JS_NewClassID(&js_traits>::QJSClassId); + + js_traits>::template registerDerivedClass(QJSClassId, unwrap); + } + + template + static void ensureCanCastToBase() { + ensureCanCastToBase>(); + } + + /** Stores offsets to qjs::Value members of T. + * These values should be marked by class_registrar::mark for QuickJS garbage collector + * so that the cycle removal algorithm can find the other objects referenced by this object. + */ + static inline std::vector markOffsets; + /** Register class in QuickJS context. * * @param ctx context * @param name class name * @param proto class prototype or JS_NULL + * @param call QJS call function. see quickjs doc + * @param exotic pointer to QJS exotic methods(static lifetime) which allow custom property handling. see quickjs doc * @throws exception */ - static void register_class(JSContext * ctx, const char * name, JSValue proto = JS_NULL) + static void register_class(JSContext * ctx, const char * name, JSValue proto = JS_NULL, + JSClassCall * call = nullptr, JSClassExoticMethods * exotic = nullptr) { if(QJSClassId == 0) { @@ -720,22 +900,39 @@ struct js_traits> auto rt = JS_GetRuntime(ctx); if(!JS_IsRegisteredClass(rt, QJSClassId)) { + JSClassGCMark * marker = nullptr; + if(!markOffsets.empty()) + { + marker = [](JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) { + auto pptr = static_cast *>(JS_GetOpaque(val, QJSClassId)); + assert(pptr); + const T * ptr = pptr->get(); + assert(ptr); + for(Value T::* member : markOffsets) + { + JS_MarkValue(rt, (*ptr.*member).v, mark_func); + } + }; + } JSClassDef def{ name, - // destructor + // destructor (finalizer) [](JSRuntime * rt, JSValue obj) noexcept { auto pptr = static_cast *>(JS_GetOpaque(obj, QJSClassId)); delete pptr; }, - nullptr, - nullptr, - nullptr + // mark + marker, + // call + call, + // exotic + exotic }; int e = JS_NewClass(rt, QJSClassId, &def); if(e < 0) { - JS_ThrowInternalError(ctx, "Cant register class %s", name); - throw exception{}; + JS_ThrowInternalError(ctx, "Can't register class %s", name); + throw exception{ctx}; } } JS_SetClassProto(ctx, QJSClassId, proto); @@ -746,6 +943,8 @@ struct js_traits> */ static JSValue wrap(JSContext * ctx, std::shared_ptr ptr) { + if(!ptr) + return JS_NULL; if(QJSClassId == 0) // not registered { #if defined(__cpp_rtti) @@ -766,16 +965,37 @@ struct js_traits> } /// @throws exception if #v doesn't have the correct class id - static const std::shared_ptr& unwrap(JSContext * ctx, JSValueConst v) + static std::shared_ptr unwrap(JSContext * ctx, JSValueConst v) { - auto ptr = static_cast *>(JS_GetOpaque2(ctx, v, QJSClassId)); - if(!ptr) - throw exception{}; - return *ptr; + std::shared_ptr ptr = nullptr; + if (JS_IsNull(v)) { + return ptr; + } + auto obj_class_id = JS_GetClassID(v); + + if (obj_class_id == QJSClassId) { + // The JS object is of class T + void * opaque = JS_GetOpaque2(ctx, v, obj_class_id); + assert(opaque && "No opaque pointer in object"); + ptr = *static_cast *>(opaque); + } else if (ptrCastFcnMap.count(obj_class_id)) { + // The JS object is of a class derived from T + ptr = ptrCastFcnMap[obj_class_id](ctx, v); + } else { + // The JS object does not derives from T + JS_ThrowTypeError(ctx, "Expected type %s, got object with classid %d", + QJSPP_TYPENAME(T), obj_class_id); + throw exception{ctx}; + } + if(!ptr) { + JS_ThrowInternalError(ctx, "Object's opaque pointer is NULL"); + throw exception{ctx}; + } + return ptr; } }; -/** Conversions for non-owning pointers to class T. +/** Conversions for non-owning pointers to class T. nullptr corresponds to JS_NULL. * @tparam T class type */ template @@ -783,9 +1003,14 @@ struct js_traits>> { static JSValue wrap(JSContext * ctx, T * ptr) { + if (ptr == nullptr) { + return JS_NULL; + } if(js_traits>::QJSClassId == 0) // not registered { #if defined(__cpp_rtti) + // If you have an error here with T=JSValueConst + // it probably means you are passing JSValueConst to where JSValue is expected js_traits>::register_class(ctx, typeid(T).name()); #else JS_ThrowTypeError(ctx, "quickjspp js_traits::wrap: Class is not registered"); @@ -804,10 +1029,24 @@ struct js_traits>> static T * unwrap(JSContext * ctx, JSValueConst v) { - auto ptr = static_cast *>(JS_GetOpaque2(ctx, v, js_traits>::QJSClassId)); - if(!ptr) - throw exception{}; - return ptr->get(); + if (JS_IsNull(v)) { + return nullptr; + } + auto ptr = js_traits>::unwrap(ctx, v); + return ptr.get(); + } +}; + +/** Conversions for enums. */ +template +struct js_traits>> { + using T = std::underlying_type_t; + static E unwrap(JSContext* ctx, JSValue v) noexcept { + return static_cast(js_traits::unwrap(ctx, v)); + } + + static JSValue wrap(JSContext* ctx, E t) noexcept { + return js_traits::wrap(ctx, static_cast(t));; } }; @@ -827,18 +1066,19 @@ struct function template static function * create(JSRuntime * rt, Functor&& f) { - auto fptr = static_cast(js_malloc_rt(rt, sizeof(function) + sizeof(Functor))); + using Functor_t = std::decay_t; + auto fptr = static_cast(js_malloc_rt(rt, sizeof(function) + sizeof(Functor_t))); if(!fptr) throw std::bad_alloc{}; new(fptr) function; - auto functorptr = reinterpret_cast(fptr->functor); - new(functorptr) Functor(std::forward(f)); + auto functorptr = reinterpret_cast(fptr->functor); + new(functorptr) Functor_t(std::forward(f)); fptr->destroyer = nullptr; - if constexpr(!std::is_trivially_destructible_v) + if constexpr(!std::is_trivially_destructible_v) { fptr->destroyer = [](function * fptr) { - auto functorptr = static_cast(fptr->functor); - functorptr->~Functor(); + auto functorptr = reinterpret_cast(fptr->functor); + functorptr->~Functor_t(); }; } return fptr; @@ -909,7 +1149,7 @@ struct js_property_traits { int err = JS_SetPropertyStr(ctx, this_obj, name, value); if(err < 0) - throw exception{}; + throw exception{ctx}; } static JSValue get_property(JSContext * ctx, JSValue this_obj, const char * name) noexcept @@ -925,7 +1165,7 @@ struct js_property_traits { int err = JS_SetPropertyUint32(ctx, this_obj, idx, value); if(err < 0) - throw exception{}; + throw exception{ctx}; } static JSValue get_property(JSContext * ctx, JSValue this_obj, uint32_t idx) noexcept @@ -934,7 +1174,9 @@ struct js_property_traits } }; -class Value; +template <> +struct js_property_traits : js_property_traits {}; + namespace detail { template @@ -958,13 +1200,24 @@ struct property_proxy /** Implicit converion to qjs::Value */ operator Value() const; // defined later due to Value being incomplete type - template - property_proxy& operator =(Value value) + /// noncopyable + property_proxy& operator =(property_proxy) = delete; + + template + property_proxy& operator =(T&& value) { js_property_traits::set_property(ctx, this_obj, key, - js_traits::wrap(ctx, std::move(value))); + js_traits>::wrap(ctx, std::forward(value))); return *this; } + + template + property_proxy operator[](Key2 key2) const + { + return {ctx, as(), std::move(key2)}; + } + + ~property_proxy() noexcept { JS_FreeValue(ctx, this_obj); } }; @@ -972,23 +1225,42 @@ struct property_proxy template struct get_set {}; +// M - member object template struct get_set { using is_const = std::is_const; - static const R& get(const std::shared_ptr& ptr) + static const R& get(std::shared_ptr ptr) { return *ptr.*M; } - static R& set(const std::shared_ptr& ptr, R value) + static R& set(std::shared_ptr ptr, R value) { return *ptr.*M = std::move(value); } }; +// M - static member object +template +struct get_set +{ + using is_const = std::is_const; + + static const R& get(bool) + { + return *M; + } + + static R& set(bool, R value) + { + return *M = std::move(value); + } + +}; + } // namespace detail /** JSValue with RAAI semantics. @@ -1011,22 +1283,24 @@ class Value { v = js_traits>::wrap(ctx, std::forward(val)); if(JS_IsException(v)) - throw exception{}; + throw exception{ctx}; } - Value(const Value& rhs) + Value(JSValue&& v) noexcept : v(std::move(v)), ctx(nullptr) {} + + Value(const Value& rhs) noexcept { ctx = rhs.ctx; v = JS_DupValue(ctx, rhs.v); } - Value(Value&& rhs) + Value(Value&& rhs) noexcept { std::swap(ctx, rhs.ctx); v = rhs.v; } - Value& operator =(Value rhs) + Value& operator =(Value rhs) noexcept { std::swap(ctx, rhs.ctx); std::swap(v, rhs.v); @@ -1040,11 +1314,10 @@ class Value bool operator !=(JSValueConst other) const { return !((*this) == other); } - /** Returns true if 2 values are the same (equality for arithmetic types or point to the same object) */ bool operator ==(const Value& rhs) const { - return ctx == rhs.ctx && (*this == rhs.v); + return (*this == rhs.v); } bool operator !=(const Value& rhs) const { return !((*this) == rhs); } @@ -1068,21 +1341,22 @@ class Value template explicit operator T() const { return as(); } - JSValue release() // dont call freevalue + JSValue release() noexcept// dont call freevalue { ctx = nullptr; return v; } /** Implicit conversion to JSValue (rvalue only). Example: JSValue v = std::move(value); */ - operator JSValue()&& { return release(); } + operator JSValue()&& noexcept { return release(); } /** Access JS properties. Returns proxy type which is implicitly convertible to qjs::Value */ template detail::property_proxy operator [](Key key) { - return {ctx, v, std::move(key)}; + assert(ctx && "Trying to access properties of Value with no JSContext"); + return {ctx, JS_DupValue(ctx, v), std::move(key)}; } @@ -1098,7 +1372,7 @@ class Value // add<&f>("f"); // add<&T::f>("f"); template - std::enable_if_t, Value&> + std::enable_if_t || std::is_function_v>, Value&> add(const char * name) { (*this)[name] = fwrapper{name}; @@ -1119,7 +1393,7 @@ class Value ); JS_FreeAtom(ctx, prop); if(ret < 0) - throw exception{}; + throw exception{ctx}; return *this; } @@ -1136,7 +1410,7 @@ class Value ); JS_FreeAtom(ctx, prop); if(ret < 0) - throw exception{}; + throw exception{ctx}; return *this; } @@ -1146,23 +1420,37 @@ class Value add(const char * name) { if constexpr (detail::get_set::is_const::value) - { return add_getter::get>(name); - } else - { return add_getter_setter::get, detail::get_set::set>(name); - } + } + + // add<&T::static_member>("static_member"); + template + std::enable_if_t && !std::is_function_v> , Value&> + add(const char * name) + { + if constexpr (detail::get_set::is_const::value) + return add_getter::get>(name); + else + return add_getter_setter::get, detail::get_set::set>(name); } std::string - toJSON(const Value& replacer = Value{nullptr, JS_UNDEFINED}, const Value& space = Value{nullptr, JS_UNDEFINED}) + toJSON(const Value& replacer = JS_UNDEFINED, const Value& space = JS_UNDEFINED) { assert(ctx); assert(!replacer.ctx || ctx == replacer.ctx); assert(!space.ctx || ctx == space.ctx); - JSValue json = JS_JSONStringify(ctx, v, replacer.v, space.v); - return (std::string) Value{ctx, json}; + return (std::string) Value{ctx, JS_JSONStringify(ctx, v, replacer.v, space.v)}; + } + + /** same as Context::eval() but with this Value as 'this' */ + Value evalThis(std::string_view buffer, const char * filename = "", int flags = 0) + { + assert(buffer.data()[buffer.size()] == '\0' && "eval buffer is not null-terminated"); // JS_Eval requirement + assert(ctx); + return Value{ctx, JS_EvalThis(ctx, v, buffer.data(), buffer.size(), filename, flags)}; } }; @@ -1180,6 +1468,9 @@ class Runtime rt = JS_NewRuntime(); if(!rt) throw std::runtime_error{"qjs: Cannot create runtime"}; + + JS_SetHostUnhandledPromiseRejectionTracker(rt, promise_unhandled_rejection_tracker, NULL); + JS_SetModuleLoaderFunc(rt, nullptr, module_loader, nullptr); } // noncopyable @@ -1189,8 +1480,49 @@ class Runtime { JS_FreeRuntime(rt); } + + /// @return pointer to qjs::Context of the executed job or nullptr if no job is pending + Context * executePendingJob(); + + bool isJobPending() const { + return JS_IsJobPending(rt); + } + +private: + static void promise_unhandled_rejection_tracker(JSContext *ctx, JSValueConst promise, + JSValueConst reason, JS_BOOL is_handled, void *opaque); + + static JSModuleDef *module_loader(JSContext *ctx, + const char *module_name, void *opaque); }; +namespace detail { + +inline std::optional readFile(std::filesystem::path const & filepath) +{ + if (!std::filesystem::exists(filepath)) return std::nullopt; + std::ifstream f(filepath, std::ios::in | std::ios::binary); + if (!f.is_open()) return std::nullopt; + std::stringstream sstream; + sstream << f.rdbuf(); + return sstream.str(); +} + +inline std::string toUri(std::string_view filename) { + auto fname = std::string{filename}; + if (fname.find("://") < fname.find("/")) return fname; + + auto fpath = std::filesystem::path(fname); + if (!fpath.is_absolute()) { + fpath = "." / fpath; + } + fpath = std::filesystem::weakly_canonical(fpath); + fname = "file://" + fpath.generic_string(); + return fname; +} + +} + /** Wrapper over JSContext * ctx * Calls JS_SetContextOpaque(ctx, this); on construction and JS_FreeContext on destruction */ @@ -1208,12 +1540,12 @@ class Context JSModuleDef * m; JSContext * ctx; - /*const char * name;*/ + const char * name; using nvp = std::pair; std::vector exports; public: - Module(JSContext * ctx, const char * name) : ctx(ctx)/*, name(name)*/ + Module(JSContext * ctx, const char * name) : ctx(ctx), name(name) { m = JS_NewCModule(ctx, name, [](JSContext * ctx, JSModuleDef * m) noexcept { auto& context = Context::get(ctx); @@ -1229,28 +1561,21 @@ class Context return 0; }); if(!m) - throw exception{}; + throw exception{ctx}; } - Module& add(const char * name, JSValue value) + Module& add(const char * name, JSValue&& value) { - exports.push_back({name, {ctx, value}}); + exports.push_back({name, {ctx, std::move(value)}}); JS_AddModuleExport(ctx, m, name); return *this; } - Module& add(const char * name, Value value) - { - assert(value.ctx == ctx); - exports.push_back({name, std::move(value)}); - JS_AddModuleExport(ctx, m, name); - return *this; - } template - Module& add(const char * name, T value) + Module& add(const char * name, T&& value) { - return add(name, js_traits::wrap(ctx, std::move(value))); + return add(name, js_traits::wrap(ctx, std::forward(value))); } Module(const Module&) = delete; @@ -1294,12 +1619,14 @@ class Context qjs::Value prototype; qjs::Context::Module& module; qjs::Context& context; + qjs::Value ctor; // last added constructor public: explicit class_registrar(const char * name, qjs::Context::Module& module, qjs::Context& context) : name(name), prototype(context.newObject()), module(module), - context(context) + context(context), + ctor(JS_NULL) { } @@ -1310,7 +1637,7 @@ class Context template class_registrar& fun(const char * name, F&& f) { - prototype.add(name, std::forward(f)); + prototype[name] = std::forward(f); return *this; } @@ -1323,13 +1650,34 @@ class Context template class_registrar& fun(const char * name) { + js_traits>::template ensureCanCastToBase(); prototype.add(name); return *this; } + /** Add a static member or function to the last added constructor. + * Example: + * struct T { static int var; static int func(); } + * module.class_("T").contructor<>("T").static_fun<&T::var>("var").static_fun<&T::func>("func"); + */ + template + class_registrar& static_fun(const char * name) + { + assert(!JS_IsNull(ctor.v) && "You should call .constructor before .static_fun"); + js_traits>::template ensureCanCastToBase(); + ctor.add(name); + return *this; + } + + /** Add a property with custom getter and setter. + * Example: + * module.class_("T").property<&T::getX, &T::setX>("x"); + */ template class_registrar& property(const char * name) { + js_traits>::template ensureCanCastToBase(); + js_traits>::template ensureCanCastToBase(); if constexpr (std::is_same_v) prototype.add_getter(name); else @@ -1346,25 +1694,39 @@ class Context { if(!name) name = this->name; - Value ctor = context.newValue(qjs::ctor_wrapper{name}); + ctor = context.newValue(qjs::ctor_wrapper{name}); JS_SetConstructor(context.ctx, ctor.v, prototype.v); - module.add(name, std::move(ctor)); + module.add(name, qjs::Value{ctor}); return *this; } - /* TODO: needs casting to base class + /** Sets the base class + * @tparam B base class + */ template class_registrar& base() { + static_assert(!std::is_same_v, "Type cannot be a base of itself"); assert(js_traits>::QJSClassId && "base class is not registered"); + js_traits>::template ensureCanCastToBase(); auto base_proto = JS_GetClassProto(context.ctx, js_traits>::QJSClassId); int err = JS_SetPrototype(context.ctx, prototype.v, base_proto); JS_FreeValue(context.ctx, base_proto); if(err < 0) - throw exception{}; + throw exception{context.ctx}; return *this; } + + /** All qjs::Value members of T should be marked by mark<> for QuickJS garbage collector + * so that the cycle removal algorithm can find the other objects referenced by this object. */ + template + class_registrar& mark() + { + js_traits>::markOffsets.push_back(V); + return *this; + } + ~class_registrar() { @@ -1417,6 +1779,26 @@ class Context JS_FreeContext(ctx); } + /** Callback triggered when a Promise rejection won't ever be handled */ + std::function onUnhandledPromiseRejection; + + /** Data type returned by the moduleLoader function */ + struct ModuleData { + std::optional source, url; + ModuleData() : source(std::nullopt), url(std::nullopt) {} + ModuleData(std::optional source) : source(std::move(source)), url(std::nullopt) {} + ModuleData(std::optional url, std::optional source) : source(std::move(source)), url(std::move(url)) {} + }; + + /** Function called to obtain the source of a module */ + std::function moduleLoader = + [](std::string_view filename) -> ModuleData { + return ModuleData{ detail::toUri(filename), detail::readFile(filename) }; + }; + + template + void enqueueJob(Function && job); + /** Create module and return a reference to it */ Module& addModule(const char * name) { @@ -1434,7 +1816,7 @@ class Context template Value newValue(T&& val) { return Value{ctx, std::forward(val)}; } - /** returns current exception associated with context, and resets it. Should be called when qjs::exception is caught */ + /** returns current exception associated with context and clears it. Should be called when qjs::exception is caught */ Value getException() { return Value{ctx, JS_GetException(ctx)}; } /** Register class T for conversions to/from std::shared_ptr to work. @@ -1449,29 +1831,28 @@ class Context js_traits>::register_class(ctx, name, proto); } - Value eval(std::string_view buffer, const char * filename = "", unsigned eval_flags = 0) + /// @see JS_Eval + Value eval(std::string_view buffer, const char * filename = "", int flags = 0) { assert(buffer.data()[buffer.size()] == '\0' && "eval buffer is not null-terminated"); // JS_Eval requirement - JSValue v = JS_Eval(ctx, buffer.data(), buffer.size(), filename, eval_flags); - return Value{ctx, v}; + JSValue v = JS_Eval(ctx, buffer.data(), buffer.size(), filename, flags); + return Value{ctx, std::move(v)}; } - Value evalFile(const char * filename, unsigned eval_flags = 0) + Value evalFile(const char * filename, int flags = 0) { - size_t buf_len; - auto deleter = [this](void * p) { js_free(ctx, p); }; - auto buf = std::unique_ptr{js_load_file(ctx, &buf_len, filename), deleter}; - if(!buf) + auto buf = detail::readFile(filename); + if (!buf) throw std::runtime_error{std::string{"evalFile: can't read file: "} + filename}; - return eval({reinterpret_cast(buf.get()), buf_len}, filename, eval_flags); + return eval(*buf, filename, flags); } - Value fromJSON(std::string_view buffer, const char * filename = "") + /// @see JS_ParseJSON2 + Value fromJSON(std::string_view buffer, const char * filename = "", int flags = 0) { assert(buffer.data()[buffer.size()] == '\0' && "fromJSON buffer is not null-terminated"); // JS_ParseJSON requirement - JSValue v = JS_ParseJSON(ctx, buffer.data(), buffer.size(), filename); - return Value{ctx, v}; + return Value{ctx, JS_ParseJSON2(ctx, buffer.data(), buffer.size(), filename, flags)}; } /** Get qjs::Context from JSContext opaque pointer */ @@ -1495,7 +1876,7 @@ struct js_traits static JSValue wrap(JSContext * ctx, Value v) noexcept { - assert(ctx == v.ctx); + assert(JS_GetRuntime(ctx) == JS_GetRuntime(v.ctx)); return v.release(); } }; @@ -1505,7 +1886,7 @@ struct js_traits * @tparam Args argument types */ template -struct js_traits> +struct js_traits, int> { static auto unwrap(JSContext * ctx, JSValueConst fun_obj) { @@ -1515,16 +1896,13 @@ struct js_traits> return [jsfun_obj = Value{ctx, JS_DupValue(ctx, fun_obj)}]() -> R { JSValue result = JS_Call(jsfun_obj.ctx, jsfun_obj.v, JS_UNDEFINED, 0, nullptr); if(JS_IsException(result)) - { - JS_FreeValue(jsfun_obj.ctx, result); - throw exception{}; - } + throw exception{jsfun_obj.ctx}; return detail::unwrap_free(jsfun_obj.ctx, result); }; } else { - return [jsfun_obj = Value{ctx, JS_DupValue(ctx, fun_obj)}](Args&& ... args) -> R { + return [jsfun_obj = Value{ctx, JS_DupValue(ctx, fun_obj)}](Args ... args) -> R { const int argc = sizeof...(Args); JSValue argv[argc]; detail::wrap_args(jsfun_obj.ctx, argv, std::forward(args)...); @@ -1532,10 +1910,7 @@ struct js_traits> const_cast(argv)); for(int i = 0; i < argc; i++) JS_FreeValue(jsfun_obj.ctx, argv[i]); if(JS_IsException(result)) - { - JS_FreeValue(jsfun_obj.ctx, result); - throw exception{}; - } + throw exception{jsfun_obj.ctx}; return detail::unwrap_free(jsfun_obj.ctx, result); }; } @@ -1545,21 +1920,68 @@ struct js_traits> * Uses detail::function for type-erasure. */ template - static JSValue wrap(JSContext * ctx, Functor&& functor) + static JSValue wrap(JSContext * ctx, Functor&& functor) noexcept { using detail::function; assert(js_traits::QJSClassId); auto obj = JS_NewObjectClass(ctx, js_traits::QJSClassId); if(JS_IsException(obj)) + return obj; + try + { + auto fptr = function::create(JS_GetRuntime(ctx), std::forward(functor)); + fptr->invoker = [](function * self, JSContext * ctx, JSValueConst this_value, int argc, + JSValueConst * argv) { + assert(self); + auto f = reinterpret_cast *>(&self->functor); + return detail::wrap_call(ctx, *f, argc, argv); + }; + JS_SetOpaque(obj, fptr); + return obj; + } + catch(const std::exception& e) + { + JS_ThrowInternalError(ctx, "%s", e.what()); return JS_EXCEPTION; - auto fptr = function::create(JS_GetRuntime(ctx), std::forward(functor)); - fptr->invoker = [](function * self, JSContext * ctx, JSValueConst this_value, int argc, JSValueConst * argv) { - assert(self); - auto f = reinterpret_cast(&self->functor); - return detail::wrap_call(ctx, *f, argv); - }; - JS_SetOpaque(obj, fptr); - return obj; + } + catch(...) + { + JS_ThrowInternalError(ctx, "Unknown errror"); + return JS_EXCEPTION; + } + } +}; + +namespace detail { + +template +struct is_callable : std::is_function { }; + +template +struct is_callable>> : std::true_type { }; + +template +inline constexpr bool is_callable_v = is_callable::value; + +} + +template +struct js_traits>> { + static auto unwrap(JSContext * ctx, JSValueConst fun_obj) + { + return js_traits< + decltype(std::function{std::declval()}), + int + >::unwrap(ctx, fun_obj); + } + + template + static JSValue wrap(JSContext * ctx, Functor&& functor) + { + return js_traits< + decltype(std::function{std::declval()}), + int + >::wrap(ctx, std::forward(functor)); } }; @@ -1580,6 +2002,16 @@ struct js_traits> { return JS_EXCEPTION; } + catch (std::exception const & err) + { + JS_ThrowInternalError(ctx, "%s", err.what()); + return JS_EXCEPTION; + } + catch (...) + { + JS_ThrowInternalError(ctx, "Unknown error"); + return JS_EXCEPTION; + } } static std::vector unwrap(JSContext * ctx, JSValueConst jsarr) @@ -1588,7 +2020,7 @@ struct js_traits> if(e == 0) JS_ThrowTypeError(ctx, "js_traits>::unwrap expects array"); if(e <= 0) - throw exception{}; + throw exception{ctx}; Value jsarray{ctx, JS_DupValue(ctx, jsarr)}; std::vector arr; auto len = static_cast(jsarray["length"]); @@ -1616,6 +2048,16 @@ struct js_traits> { return JS_EXCEPTION; } + catch (std::exception const & err) + { + JS_ThrowInternalError(ctx, "%s", err.what()); + return JS_EXCEPTION; + } + catch (...) + { + JS_ThrowInternalError(ctx, "Unknown error"); + return JS_EXCEPTION; + } } static std::pair unwrap(JSContext * ctx, JSValueConst jsarr) @@ -1624,14 +2066,14 @@ struct js_traits> if(e == 0) JS_ThrowTypeError(ctx, "js_traits<%s>::unwrap expects array", QJSPP_TYPENAME(std::pair)); if(e <= 0) - throw exception{}; + throw exception{ctx}; Value jsarray{ctx, JS_DupValue(ctx, jsarr)}; const auto len = static_cast(jsarray["length"]); if(len != 2) { JS_ThrowTypeError(ctx, "js_traits<%s>::unwrap expected array of length 2, got length %d", QJSPP_TYPENAME(std::pair), len); - throw exception{}; + throw exception{ctx}; } return std::pair{ static_cast(jsarray[uint32_t(0)]), @@ -1682,4 +2124,109 @@ property_proxy::operator Value() const } } -} // namespace qjs \ No newline at end of file +template +void Context::enqueueJob(Function && job) { + JSValue job_val = js_traits>::wrap(ctx, std::forward(job)); + JSValueConst arg = job_val; + int err = JS_EnqueueJob(ctx, [](JSContext *ctx, int argc, JSValueConst *argv){ + try + { + assert(argc >= 1); + js_traits>::unwrap(ctx, argv[0])(); + } + catch (exception) + { + return JS_EXCEPTION; + } + catch (std::exception const & err) + { + JS_ThrowInternalError(ctx, "%s", err.what()); + return JS_EXCEPTION; + } + catch (...) + { + JS_ThrowInternalError(ctx, "Unknown error"); + return JS_EXCEPTION; + } + return JS_UNDEFINED; + }, 1, &arg); + JS_FreeValue(ctx, job_val); + if(err < 0) + throw exception{ctx}; +} + +inline Context & exception::context() const { + return Context::get(ctx); +} + +inline Value exception::get() { + return context().getException(); +} + +inline void Runtime::promise_unhandled_rejection_tracker(JSContext *ctx, JSValueConst promise, + JSValueConst reason, JS_BOOL is_handled, void *opaque) +{ + auto & context = Context::get(ctx); + if (context.onUnhandledPromiseRejection) { + context.onUnhandledPromiseRejection(context.newValue(JS_DupValue(ctx, reason))); + } +} + +inline JSModuleDef * Runtime::module_loader(JSContext *ctx, + const char *module_name, void *opaque) +{ + Context::ModuleData data; + auto & context = Context::get(ctx); + + try { + if (context.moduleLoader) data = context.moduleLoader(module_name); + + if (!data.source) { + JS_ThrowReferenceError(ctx, "could not load module filename '%s'", module_name); + return NULL; + } + + if (!data.url) data.url = module_name; + + // compile the module + auto func_val = context.eval(*data.source, module_name, JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); + assert(JS_VALUE_GET_TAG(func_val.v) == JS_TAG_MODULE); + JSModuleDef * m = reinterpret_cast(JS_VALUE_GET_PTR(func_val.v)); + + // set import.meta + auto meta = context.newValue(JS_GetImportMeta(ctx, m)); + meta["url"] = *data.url; + meta["main"] = false; + + return m; + } + catch(exception) + { + return NULL; + } + catch (std::exception const & err) + { + JS_ThrowInternalError(ctx, "%s", err.what()); + return NULL; + } + catch (...) + { + JS_ThrowInternalError(ctx, "Unknown error"); + return NULL; + } +} + + +inline Context * Runtime::executePendingJob() { + JSContext * ctx; + auto err = JS_ExecutePendingJob(rt, &ctx); + if (err == 0) { + // There was no job to run + return nullptr; + } else if (err < 0) { + throw exception{ctx}; + } + return &Context::get(ctx); +} + +} // namespace qjs diff --git a/src/generator/config/nodemanip.cpp b/src/generator/config/nodemanip.cpp index f7174ea6f..c178410e6 100644 --- a/src/generator/config/nodemanip.cpp +++ b/src/generator/config/nodemanip.cpp @@ -210,20 +210,20 @@ int addNodes(std::string link, std::vector &allNodes, int groupID, parse_ for(Proxy &x : nodes) { x.GroupId = groupID; - if(custom_group.size()) + if(!custom_group.empty()) x.Group = custom_group; } copyNodes(nodes, allNodes); break; default: explode(link, node); - if(node.Type == -1) + if(node.Type == ProxyType::Unknown) { writeLog(LOG_TYPE_ERROR, "No valid link found."); return -1; } node.GroupId = groupID; - if(custom_group.size()) + if(!custom_group.empty()) node.Group = custom_group; allNodes.emplace_back(std::move(node)); } @@ -494,9 +494,9 @@ void preprocessNodes(std::vector &nodes, extra_settings &ext) auto compare = (std::function) ctx.eval("compare"); auto comparer = [&](const Proxy &a, const Proxy &b) { - if(a.Type == ProxyType::Unknow) + if(a.Type == ProxyType::Unknown) return 1; - if(b.Type == ProxyType::Unknow) + if(b.Type == ProxyType::Unknown) return 0; return compare(a, b); }; diff --git a/src/generator/config/subexport.cpp b/src/generator/config/subexport.cpp index 941eabc51..499eadfd4 100644 --- a/src/generator/config/subexport.cpp +++ b/src/generator/config/subexport.cpp @@ -116,7 +116,15 @@ bool applyMatcher(const std::string &rule, std::string &real_rule, const Proxy & std::string target, ret_real_rule; static const std::string groupid_regex = R"(^!!(?:GROUPID|INSERT)=([\d\-+!,]+)(?:!!(.*))?$)", group_regex = R"(^!!(?:GROUP)=(.+?)(?:!!(.*))?$)"; static const std::string type_regex = R"(^!!(?:TYPE)=(.+?)(?:!!(.*))?$)", port_regex = R"(^!!(?:PORT)=(.+?)(?:!!(.*))?$)", server_regex = R"(^!!(?:SERVER)=(.+?)(?:!!(.*))?$)"; - static const string_array types = {"", "SS", "SSR", "VMESS", "TROJAN", "SNELL", "HTTP", "HTTPS", "SOCKS5", "WIREGUARD"}; + static const std::map types = {{ProxyType::Shadowsocks, "SS"}, + {ProxyType::ShadowsocksR, "SSR"}, + {ProxyType::VMess, "VMESS"}, + {ProxyType::Trojan, "TROJAN"}, + {ProxyType::Snell, "SNELL"}, + {ProxyType::HTTP, "HTTP"}, + {ProxyType::HTTPS, "HTTPS"}, + {ProxyType::SOCKS5, "SOCKS5"}, + {ProxyType::WireGuard, "WIREGUARD"}}; if(startsWith(rule, "!!GROUP=")) { regGetMatch(rule, group_regex, 3, 0, &target, &ret_real_rule); @@ -134,9 +142,9 @@ bool applyMatcher(const std::string &rule, std::string &real_rule, const Proxy & { regGetMatch(rule, type_regex, 3, 0, &target, &ret_real_rule); real_rule = ret_real_rule; - if(node.Type == ProxyType::Unknow) + if(node.Type == ProxyType::Unknown) return false; - return regMatch(types[node.Type], target); + return regMatch(types.at(node.Type), target); } else if(startsWith(rule, "!!PORT=")) { diff --git a/src/generator/template/templates.cpp b/src/generator/template/templates.cpp index 9cfaf23c5..a6d1c6e54 100644 --- a/src/generator/template/templates.cpp +++ b/src/generator/template/templates.cpp @@ -96,7 +96,7 @@ int render_template(const std::string &content, const template_args &vars, std:: for(auto &x : vars.request_params) { all_args += x.first; - if(x.second.size()) + if(!x.second.empty()) { parse_json_pointer(data["request"], x.first, x.second); all_args += "=" + x.second; @@ -295,19 +295,19 @@ const std::string clash_script_keyword_template = R"( keywords = [{{ rule.keywo std::string findFileName(const std::string &path) { string_size pos = path.rfind('/'); - if(pos == path.npos) + if(pos == std::string::npos) { pos = path.rfind('\\'); - if(pos == path.npos) + if(pos == std::string::npos) pos = 0; } string_size pos2 = path.rfind('.'); - if(pos2 < pos || pos2 == path.npos) + if(pos2 < pos || pos2 == std::string::npos) pos2 = path.size(); return path.substr(pos + 1, pos2 - pos - 1); } -int renderClashScript(YAML::Node &base_rule, std::vector &ruleset_content_array, std::string remote_path_prefix, bool script, bool overwrite_original_rules, bool clash_classical_ruleset) +int renderClashScript(YAML::Node &base_rule, std::vector &ruleset_content_array, const std::string &remote_path_prefix, bool script, bool overwrite_original_rules, bool clash_classical_ruleset) { nlohmann::json data; std::string match_group, geoips, retrieved_rules; @@ -381,7 +381,7 @@ int renderClashScript(YAML::Node &base_rule, std::vector &rulese groups.emplace_back(std::move(rule_name)); continue; } - if(remote_path_prefix.size()) + if(!remote_path_prefix.empty()) { if(fileExist(rule_path, true) || isLink(rule_path)) { @@ -524,7 +524,7 @@ int renderClashScript(YAML::Node &base_rule, std::vector &rulese } if(script) { - if(geoips.size()) + if(!geoips.empty()) parse_json_pointer(data, "geoips", geoips.erase(geoips.size() - 1)); parse_json_pointer(data, "match_group", match_group); diff --git a/src/generator/template/templates.h b/src/generator/template/templates.h index 312d9e334..b99806a5e 100644 --- a/src/generator/template/templates.h +++ b/src/generator/template/templates.h @@ -16,6 +16,6 @@ struct template_args }; int render_template(const std::string &content, const template_args &vars, std::string &output, const std::string &include_scope = "templates"); -int renderClashScript(YAML::Node &base_rule, std::vector &ruleset_content_array, std::string remote_path_prefix, bool script, bool overwrite_original_rules, bool clash_classic_ruleset); +int renderClashScript(YAML::Node &base_rule, std::vector &ruleset_content_array, const std::string &remote_path_prefix, bool script, bool overwrite_original_rules, bool clash_classic_ruleset); #endif // TEMPLATES_H_INCLUDED diff --git a/src/parser/config/proxy.h b/src/parser/config/proxy.h index 94f6886cb..7a806a9cf 100644 --- a/src/parser/config/proxy.h +++ b/src/parser/config/proxy.h @@ -9,9 +9,9 @@ using String = std::string; using StringArray = std::vector; -enum ProxyType +enum class ProxyType { - Unknow, + Unknown, Shadowsocks, ShadowsocksR, VMess, @@ -23,7 +23,7 @@ enum ProxyType WireGuard }; -inline String getProxyTypeName(int type) +inline String getProxyTypeName(ProxyType type) { switch(type) { @@ -50,7 +50,7 @@ inline String getProxyTypeName(int type) struct Proxy { - int Type = ProxyType::Unknow; + ProxyType Type = ProxyType::Unknown; uint32_t Id = 0; uint32_t GroupId = 0; String Group; diff --git a/src/parser/subparser.cpp b/src/parser/subparser.cpp index 07cd39412..ec6d27bf8 100644 --- a/src/parser/subparser.cpp +++ b/src/parser/subparser.cpp @@ -743,15 +743,15 @@ void explodeTrojan(std::string trojan, Proxy &node) std::string server, port, psk, addition, group, remark, host, path, network; tribool tfo, scv; trojan.erase(0, 9); - string_size pos = trojan.rfind("#"); + string_size pos = trojan.rfind('#'); - if(pos != trojan.npos) + if(pos != std::string::npos) { remark = urlDecode(trojan.substr(pos + 1)); trojan.erase(pos); } - pos = trojan.find("?"); - if(pos != trojan.npos) + pos = trojan.find('?'); + if(pos != std::string::npos) { addition = trojan.substr(pos + 1); trojan.erase(pos); @@ -1242,7 +1242,7 @@ void explodeShadowrocket(std::string rocket, Proxy &node) std::string addition; rocket = rocket.substr(8); - string_size pos = rocket.find("?"); + string_size pos = rocket.find('?'); addition = rocket.substr(pos + 1); rocket.erase(pos); @@ -2274,7 +2274,7 @@ void explodeSub(std::string sub, std::vector &nodes) if(strLink.rfind("\r") != strLink.npos) strLink.erase(strLink.size() - 1); explode(strLink, node); - if(strLink.empty() || node.Type == ProxyType::Unknow) + if(strLink.empty() || node.Type == ProxyType::Unknown) { continue; } diff --git a/src/script/script_quickjs.cpp b/src/script/script_quickjs.cpp index 1bbe7e882..bd798ea9f 100644 --- a/src/script/script_quickjs.cpp +++ b/src/script/script_quickjs.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #ifdef _WIN32 #include @@ -291,9 +292,7 @@ namespace qjs static qjs_fetch_Headers unwrap(JSContext *ctx, JSValueConst v) { qjs_fetch_Headers result; - auto headers = JS_GetPropertyStr(ctx, v, "headers"); - result.headers = js_traits::unwrap(ctx, headers); - JS_FreeValue(ctx, headers); + result.headers = unwrap_free(ctx, v, "headers"); return result; } static JSValue wrap(JSContext *ctx, const qjs_fetch_Headers &h) @@ -310,14 +309,12 @@ namespace qjs static qjs_fetch_Request unwrap(JSContext *ctx, JSValueConst v) { qjs_fetch_Request request; - auto headers = JS_GetPropertyStr(ctx, v, "headers"); - request.method = JS_GetPropertyToString(ctx, v, "method"); - request.url = JS_GetPropertyToString(ctx, v, "url"); - request.postdata = JS_GetPropertyToString(ctx, v, "data"); - request.proxy = JS_GetPropertyToString(ctx, v, "proxy"); - request.cookies = JS_GetPropertyToString(ctx, v, "cookies"); - request.headers = js_traits::unwrap(ctx, headers); - JS_FreeValue(ctx, headers); + request.method = unwrap_free(ctx, v, "method"); + request.url = unwrap_free(ctx, v, "url"); + request.postdata = unwrap_free(ctx, v, "data"); + request.proxy = unwrap_free(ctx, v, "proxy"); + request.cookies = unwrap_free(ctx, v, "cookies"); + request.headers = unwrap_free(ctx, v, "headers"); return request; } }; @@ -390,7 +387,6 @@ std::string getGeoIP(const std::string &address, const std::string &proxy) void script_runtime_init(qjs::Runtime &runtime) { js_std_init_handlers(runtime.rt); - JS_SetModuleLoaderFunc(runtime.rt, nullptr, js_module_loader, nullptr); } int ShowMsgbox(const std::string &title, std::string content, uint16_t type = 0) @@ -459,18 +455,6 @@ int script_context_init(qjs::Context &context) .fun<&qjs_fetch_Response::content>("data") .fun<&qjs_fetch_Response::cookies>("cookies") .fun<&qjs_fetch_Response::headers>("headers"); - /* - module.class_("NodeInfo") - .constructor<>() - .fun<&nodeInfo::linkType>("LinkType") - .fun<&nodeInfo::id>("ID") - .fun<&nodeInfo::groupID>("GroupID") - .fun<&nodeInfo::group>("Group") - .fun<&nodeInfo::remarks>("Remark") - .fun<&nodeInfo::server>("Hostname") - .fun<&nodeInfo::port>("Port") - .fun<&nodeInfo::proxyStr>("ProxyInfo"); - */ module.class_("Proxy") .constructor<>() .fun<&Proxy::Type>("Type") @@ -502,7 +486,20 @@ int script_context_init(qjs::Context &context) .fun<&Proxy::UDP>("UDP") .fun<&Proxy::TCPFastOpen>("TCPFastOpen") .fun<&Proxy::AllowInsecure>("AllowInsecure") - .fun<&Proxy::TLS13>("TLS13"); + .fun<&Proxy::TLS13>("TLS13") + .fun<&Proxy::SnellVersion>("SnellVersion") + .fun<&Proxy::ServerName>("ServerName") + .fun<&Proxy::SelfIP>("SelfIP") + .fun<&Proxy::SelfIPv6>("SelfIPv6") + .fun<&Proxy::PublicKey>("PublicKey") + .fun<&Proxy::PrivateKey>("PrivateKey") + .fun<&Proxy::PreSharedKey>("PreSharedKey") + .fun<&Proxy::DnsServers>("DnsServers") + .fun<&Proxy::Mtu>("Mtu") + .fun<&Proxy::AllowedIPs>("AllowedIPs") + .fun<&Proxy::KeepAlive>("KeepAlive") + .fun<&Proxy::TestUrl>("TestUrl") + .fun<&Proxy::ClientId>("ClientId"); context.global().add<&makeDataURI>("makeDataURI") .add<&qjs_fetch>("fetch") .add<&base64Encode>("atob") @@ -518,7 +515,7 @@ int script_context_init(qjs::Context &context) globalThis.Request = interUtils.Request globalThis.Response = interUtils.Response globalThis.Headers = interUtils.Headers - globalThis.NodeInfo = interUtils.NodeInfo + globalThis.Proxy = interUtils.Proxy import * as std from 'std' import * as os from 'os' globalThis.std = std diff --git a/src/script/script_quickjs.h b/src/script/script_quickjs.h index e9d261166..ff89f1b11 100644 --- a/src/script/script_quickjs.h +++ b/src/script/script_quickjs.h @@ -18,18 +18,7 @@ inline JSValue JS_NewString(JSContext *ctx, const std::string& str) return JS_NewStringLen(ctx, str.c_str(), str.size()); } -inline std::string JS_GetPropertyToString(JSContext *ctx, JSValue v, const char* prop) -{ - auto val = JS_GetPropertyStr(ctx, v, prop); - size_t len; - const char *str = JS_ToCStringLen(ctx, &len, val); - std::string result(str, len); - JS_FreeCString(ctx, str); - JS_FreeValue(ctx, val); - return result; -} - -inline std::string JS_GetPropertyToString(JSContext *ctx, JSValueConst obj, uint32_t index) { +inline std::string JS_GetPropertyIndexToString(JSContext *ctx, JSValueConst obj, uint32_t index) { JSValue val = JS_GetPropertyUint32(ctx, obj, index); size_t len; const char *str = JS_ToCStringLen(ctx, &len, val); @@ -39,38 +28,17 @@ inline std::string JS_GetPropertyToString(JSContext *ctx, JSValueConst obj, uint return result; } -inline int JS_GetPropertyToInt32(JSContext *ctx, JSValue v, const char* prop, int32_t def_value = 0) -{ - int32_t result = def_value; - auto val = JS_GetPropertyStr(ctx, v, prop); - int32_t ret = JS_ToInt32(ctx, &result, val); - JS_FreeValue(ctx, val); - if(ret != 0) return def_value; - return result; -} - -inline uint32_t JS_GetPropertyToUInt32(JSContext *ctx, JSValue v, const char* prop, uint32_t def_value = 0) -{ - uint32_t result = def_value; - auto val = JS_GetPropertyStr(ctx, v, prop); - int ret = JS_ToUint32(ctx, &result, val); - JS_FreeValue(ctx, val); - if(ret != 0) return def_value; - return result; -} - -inline bool JS_GetPropertyToBool(JSContext *ctx, JSValue v, const char* prop, bool def_value = false) -{ - bool result = def_value; - auto val = JS_GetPropertyStr(ctx, v, prop); - int ret = JS_ToBool(ctx, val); - JS_FreeValue(ctx, val); - if(ret != 0) return def_value; - return result; -} - namespace qjs { + template + static T unwrap_free(JSContext *ctx, JSValue v, const char* key) noexcept + { + auto obj = JS_GetPropertyStr(ctx, v, key); + T t = js_traits::unwrap(ctx, obj); + JS_FreeValue(ctx, obj); + return t; + } + template<> struct js_traits { @@ -81,33 +49,28 @@ namespace qjs JS_SetPropertyStr(ctx, obj, "isDefined", JS_NewBool(ctx, !t.is_undef())); return obj; } + static tribool unwrap(JSContext *ctx, JSValueConst v) { tribool t; - bool defined = JS_GetPropertyToBool(ctx, v, "isDefined"); + bool defined = unwrap_free(ctx, v, "isDefined"); if(defined) { - bool value = JS_GetPropertyToBool(ctx, v, "value"); + bool value = unwrap_free(ctx, v, "value"); t.set(value); } return t; } - static tribool JS_GetPropertyToTriBool(JSContext *ctx, JSValue v, const char* prop) - { - auto obj = JS_GetPropertyStr(ctx, v, prop); - auto tb = unwrap(ctx, obj); - JS_FreeValue(ctx, obj); - return tb; - } }; template<> - struct js_traits { + struct js_traits + { static StringArray unwrap(JSContext *ctx, JSValueConst v) { StringArray arr; - uint32_t length = JS_GetPropertyToUInt32(ctx, v, "length"); + uint32_t length = unwrap_free(ctx, v, "length"); for (uint32_t i = 0; i < length; i++) { - arr.push_back(JS_GetPropertyToString(ctx, v, i)); + arr.push_back(JS_GetPropertyIndexToString(ctx, v, i)); } return arr; } @@ -131,7 +94,7 @@ namespace qjs return obj; } - JS_DefinePropertyValueStr(ctx, obj, "Type", JS_NewInt32(ctx, n.Type), JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "Type", js_traits::wrap(ctx, n.Type), JS_PROP_C_W_E); JS_DefinePropertyValueStr(ctx, obj, "Id", JS_NewUint32(ctx, n.Id), JS_PROP_C_W_E); JS_DefinePropertyValueStr(ctx, obj, "GroupId", JS_NewUint32(ctx, n.GroupId), JS_PROP_C_W_E); JS_DefinePropertyValueStr(ctx, obj, "Group", JS_NewString(ctx, n.Group), JS_PROP_C_W_E); @@ -187,55 +150,55 @@ namespace qjs static Proxy unwrap(JSContext *ctx, JSValueConst v) { Proxy node; - node.Type = JS_GetPropertyToInt32(ctx, v, "Type"); - node.Id = JS_GetPropertyToInt32(ctx, v, "Id"); - node.GroupId = JS_GetPropertyToInt32(ctx, v, "GroupId"); - node.Group = JS_GetPropertyToString(ctx, v, "Group"); - node.Remark = JS_GetPropertyToString(ctx, v, "Remark"); - node.Hostname = JS_GetPropertyToString(ctx, v, "Server"); - node.Port = JS_GetPropertyToUInt32(ctx, v, "Port"); - - node.Username = JS_GetPropertyToString(ctx, v, "Username"); - node.Password = JS_GetPropertyToString(ctx, v, "Password"); - node.EncryptMethod = JS_GetPropertyToString(ctx, v, "EncryptMethod"); - node.Plugin = JS_GetPropertyToString(ctx, v, "Plugin"); - node.PluginOption = JS_GetPropertyToString(ctx, v, "PluginOption"); - node.Protocol = JS_GetPropertyToString(ctx, v, "Protocol"); - node.ProtocolParam = JS_GetPropertyToString(ctx, v, "ProtocolParam"); - node.OBFS = JS_GetPropertyToString(ctx, v, "OBFS"); - node.OBFSParam = JS_GetPropertyToString(ctx, v, "OBFSParam"); - node.UserId = JS_GetPropertyToString(ctx, v, "UserId"); - node.AlterId = JS_GetPropertyToUInt32(ctx, v, "AlterId"); - node.TransferProtocol = JS_GetPropertyToString(ctx, v, "TransferProtocol"); - node.FakeType = JS_GetPropertyToString(ctx, v, "FakeType"); - node.TLSSecure = JS_GetPropertyToBool(ctx, v, "TLSSecure"); - - node.Host = JS_GetPropertyToString(ctx, v, "Host"); - node.Path = JS_GetPropertyToString(ctx, v, "Path"); - node.Edge = JS_GetPropertyToString(ctx, v, "Edge"); - - node.QUICSecure = JS_GetPropertyToString(ctx, v, "QUICSecure"); - node.QUICSecret = JS_GetPropertyToString(ctx, v, "QUICSecret"); - - node.UDP = js_traits::JS_GetPropertyToTriBool(ctx, v, "UDP"); - node.TCPFastOpen = js_traits::JS_GetPropertyToTriBool(ctx, v, "TCPFastOpen"); - node.AllowInsecure = js_traits::JS_GetPropertyToTriBool(ctx, v, "AllowInsecure"); - node.TLS13 = js_traits::JS_GetPropertyToTriBool(ctx, v, "TLS13"); - - node.SnellVersion = JS_GetPropertyToInt32(ctx, v, "SnellVersion"); - node.ServerName = JS_GetPropertyToString(ctx, v, "ServerName"); - - node.SelfIP = JS_GetPropertyToString(ctx, v, "SelfIP"); - node.SelfIPv6 = JS_GetPropertyToString(ctx, v, "SelfIPv6"); - node.PublicKey = JS_GetPropertyToString(ctx, v, "PublicKey"); - node.PrivateKey = JS_GetPropertyToString(ctx, v, "PrivateKey"); - node.PreSharedKey = JS_GetPropertyToString(ctx, v, "PreSharedKey"); - node.DnsServers = js_traits::unwrap(ctx, JS_GetPropertyStr(ctx, v, "DnsServers")); - node.Mtu = JS_GetPropertyToUInt32(ctx, v, "Mtu"); - node.AllowedIPs = JS_GetPropertyToString(ctx, v, "AllowedIPs"); - node.KeepAlive = JS_GetPropertyToUInt32(ctx, v, "KeepAlive"); - node.TestUrl = JS_GetPropertyToString(ctx, v, "TestUrl"); - node.ClientId = JS_GetPropertyToString(ctx, v, "ClientId"); + node.Type = unwrap_free(ctx, v, "Type"); + node.Id = unwrap_free(ctx, v, "Id"); + node.GroupId = unwrap_free(ctx, v, "GroupId"); + node.Group = unwrap_free(ctx, v, "Group"); + node.Remark = unwrap_free(ctx, v, "Remark"); + node.Hostname = unwrap_free(ctx, v, "Server"); + node.Port = unwrap_free(ctx, v, "Port"); + + node.Username = unwrap_free(ctx, v, "Username"); + node.Password = unwrap_free(ctx, v, "Password"); + node.EncryptMethod = unwrap_free(ctx, v, "EncryptMethod"); + node.Plugin = unwrap_free(ctx, v, "Plugin"); + node.PluginOption = unwrap_free(ctx, v, "PluginOption"); + node.Protocol = unwrap_free(ctx, v, "Protocol"); + node.ProtocolParam = unwrap_free(ctx, v, "ProtocolParam"); + node.OBFS = unwrap_free(ctx, v, "OBFS"); + node.OBFSParam = unwrap_free(ctx, v, "OBFSParam"); + node.UserId = unwrap_free(ctx, v, "UserId"); + node.AlterId = unwrap_free(ctx, v, "AlterId"); + node.TransferProtocol = unwrap_free(ctx, v, "TransferProtocol"); + node.FakeType = unwrap_free(ctx, v, "FakeType"); + node.TLSSecure = unwrap_free(ctx, v, "TLSSecure"); + + node.Host = unwrap_free(ctx, v, "Host"); + node.Path = unwrap_free(ctx, v, "Path"); + node.Edge = unwrap_free(ctx, v, "Edge"); + + node.QUICSecure = unwrap_free(ctx, v, "QUICSecure"); + node.QUICSecret = unwrap_free(ctx, v, "QUICSecret"); + + node.UDP = unwrap_free(ctx, v, "UDP"); + node.TCPFastOpen = unwrap_free(ctx, v, "TCPFastOpen"); + node.AllowInsecure = unwrap_free(ctx, v, "AllowInsecure"); + node.TLS13 = unwrap_free(ctx, v, "TLS13"); + + node.SnellVersion = unwrap_free(ctx, v, "SnellVersion"); + node.ServerName = unwrap_free(ctx, v, "ServerName"); + + node.SelfIP = unwrap_free(ctx, v, "SelfIP"); + node.SelfIPv6 = unwrap_free(ctx, v, "SelfIPv6"); + node.PublicKey = unwrap_free(ctx, v, "PublicKey"); + node.PrivateKey = unwrap_free(ctx, v, "PrivateKey"); + node.PreSharedKey = unwrap_free(ctx, v, "PreSharedKey"); + node.DnsServers = unwrap_free(ctx, v, "DnsServers"); + node.Mtu = unwrap_free(ctx, v, "Mtu"); + node.AllowedIPs = unwrap_free(ctx, v, "AllowedIPs"); + node.KeepAlive = unwrap_free(ctx, v, "KeepAlive"); + node.TestUrl = unwrap_free(ctx, v, "TestUrl"); + node.ClientId = unwrap_free(ctx, v, "ClientId"); return node; }