Branch data Line data Source code
1 : : /* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
2 : : // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
3 : : // SPDX-FileCopyrightText: 2020 Marco Trevisan <marco.trevisan@canonical.com>
4 : :
5 : : #pragma once
6 : :
7 : : #include <config.h>
8 : :
9 : : #include <stdint.h>
10 : :
11 : : #include <cmath> // for isnan
12 : : #include <limits>
13 : : #include <string>
14 : :
15 : : #include <girepository.h>
16 : : #include <glib-object.h>
17 : : #include <glib.h>
18 : :
19 : : #include <js/BigInt.h>
20 : : #include <js/Conversions.h>
21 : : #include <js/RootingAPI.h>
22 : : #include <js/TypeDecls.h>
23 : : #include <js/Utility.h> // for UniqueChars
24 : : #include <js/Value.h> // for CanonicalizeNaN
25 : :
26 : : #include "gi/gtype.h"
27 : : #include "gi/value.h"
28 : : #include "gjs/jsapi-util.h"
29 : : #include "gjs/macros.h"
30 : :
31 : : namespace Gjs {
32 : :
33 : : template <typename T>
34 : : struct TypeWrapper {
35 : : constexpr TypeWrapper() : m_value(0) {}
36 : 1609 : explicit constexpr TypeWrapper(T v) : m_value(v) {}
37 : : constexpr operator T() const { return m_value; }
38 : 1605 : constexpr operator T() { return m_value; }
39 : :
40 : : private:
41 : : T m_value;
42 : : };
43 : :
44 : : namespace JsValueHolder {
45 : :
46 : : template <typename T1, typename T2>
47 : : constexpr bool comparable_types() {
48 : : return std::is_arithmetic_v<T1> == std::is_arithmetic_v<T2> &&
49 : : std::is_signed_v<T1> == std::is_signed_v<T2>;
50 : : }
51 : :
52 : : template <typename T, typename Container>
53 : : constexpr bool type_fits() {
54 : : if constexpr (comparable_types<T, Container>()) {
55 : : return (std::is_integral_v<T> == std::is_integral_v<Container> &&
56 : : std::numeric_limits<T>::max() <=
57 : : std::numeric_limits<Container>::max() &&
58 : : std::numeric_limits<T>::lowest() >=
59 : : std::numeric_limits<Container>::lowest());
60 : : }
61 : :
62 : : return false;
63 : : }
64 : :
65 : : /* The tag is needed to disambiguate types such as gboolean and GType
66 : : * which are in fact typedef's of other generic types.
67 : : * Setting a tag for a type allows to perform proper specialization. */
68 : : template <typename T, GITypeTag TAG>
69 : : constexpr auto get_strict() {
70 : : if constexpr (TAG != GI_TYPE_TAG_VOID) {
71 : : if constexpr (std::is_same_v<T, GType> && TAG == GI_TYPE_TAG_GTYPE)
72 : : return GType{};
73 : : else if constexpr (std::is_same_v<T, gboolean> &&
74 : : TAG == GI_TYPE_TAG_BOOLEAN)
75 : : return gboolean{};
76 : : else
77 : : return;
78 : : } else {
79 : : if constexpr (std::is_same_v<T, char32_t>)
80 : : return char32_t{};
81 : : else if constexpr (type_fits<T, int32_t>())
82 : : return int32_t{};
83 : : else if constexpr (type_fits<T, uint32_t>())
84 : : return uint32_t{};
85 : : else if constexpr (type_fits<T, int64_t>())
86 : : return int64_t{};
87 : : else if constexpr (type_fits<T, uint64_t>())
88 : : return uint64_t{};
89 : : else if constexpr (type_fits<T, double>())
90 : : return double{};
91 : : else
92 : : return T{};
93 : : }
94 : : }
95 : :
96 : : template <typename T>
97 : : constexpr auto get_relaxed() {
98 : : if constexpr (std::is_same_v<T, int64_t> || std::is_same_v<T, uint64_t>)
99 : : return TypeWrapper<T>{};
100 : : else if constexpr (type_fits<T, int32_t>())
101 : : return int32_t{};
102 : : else if constexpr (type_fits<T, uint16_t>())
103 : : return uint32_t{};
104 : : else if constexpr (std::is_arithmetic_v<T>)
105 : : return double{};
106 : : else
107 : : return T{};
108 : : }
109 : :
110 : : template <typename T, GITypeTag TAG = GI_TYPE_TAG_VOID>
111 : : using Strict = decltype(JsValueHolder::get_strict<T, TAG>());
112 : :
113 : : template <typename T>
114 : : using Relaxed = decltype(JsValueHolder::get_relaxed<T>());
115 : :
116 : : } // namespace JsValueHolder
117 : :
118 : :
119 : : template <typename T, typename MODE = JsValueHolder::Relaxed<T>>
120 : : constexpr bool type_has_js_getter() {
121 : : return std::is_same_v<T, MODE>;
122 : : }
123 : :
124 : : /* Avoid implicit conversions */
125 : : template <GITypeTag TAG = GI_TYPE_TAG_VOID, typename T>
126 : : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c(JSContext*,
127 : : const JS::HandleValue&,
128 : : T*) = delete;
129 : :
130 : : template <>
131 : 12721 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c(
132 : : JSContext* cx, const JS::HandleValue& value, int32_t* out) {
133 : 12721 : return JS::ToInt32(cx, value, out);
134 : : }
135 : :
136 : : template <>
137 : 144 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c(
138 : : JSContext* cx, const JS::HandleValue& value, uint32_t* out) {
139 : 144 : return JS::ToUint32(cx, value, out);
140 : : }
141 : :
142 : : template <>
143 : 24 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c(
144 : : JSContext* cx, const JS::HandleValue& value, char32_t* out) {
145 : : uint32_t tmp;
146 : 24 : bool retval = JS::ToUint32(cx, value, &tmp);
147 : 24 : *out = tmp;
148 : 24 : return retval;
149 : : }
150 : :
151 : : template <>
152 : 594 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c(
153 : : JSContext* cx, const JS::HandleValue& value, int64_t* out) {
154 [ - + ]: 594 : if (value.isBigInt()) {
155 : 0 : *out = JS::ToBigInt64(value.toBigInt());
156 : 0 : return true;
157 : : }
158 : 594 : return JS::ToInt64(cx, value, out);
159 : : }
160 : :
161 : : template <>
162 : 7 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c(
163 : : JSContext* cx, const JS::HandleValue& value, uint64_t* out) {
164 [ - + ]: 7 : if (value.isBigInt()) {
165 : 0 : *out = JS::ToBigUint64(value.toBigInt());
166 : 0 : return true;
167 : : }
168 : 7 : return JS::ToUint64(cx, value, out);
169 : : }
170 : :
171 : : template <>
172 : 11986 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c(
173 : : JSContext* cx, const JS::HandleValue& value, double* out) {
174 : 11986 : return JS::ToNumber(cx, value, out);
175 : : }
176 : :
177 : : template <>
178 : 12 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c<GI_TYPE_TAG_BOOLEAN>(
179 : : JSContext*, const JS::HandleValue& value, gboolean* out) {
180 : 12 : *out = !!JS::ToBoolean(value);
181 : 12 : return true;
182 : : }
183 : :
184 : : template <>
185 : 5 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c<GI_TYPE_TAG_GTYPE>(
186 : : JSContext* cx, const JS::HandleValue& value, GType* out) {
187 [ + + ]: 5 : if (!value.isObject())
188 : 2 : return false;
189 : :
190 : 3 : JS::RootedObject elem_obj(cx);
191 : 3 : elem_obj = &value.toObject();
192 : :
193 [ - + ]: 3 : if (!gjs_gtype_get_actual_gtype(cx, elem_obj, out))
194 : 0 : return false;
195 : :
196 [ - + ]: 3 : if (*out == G_TYPE_INVALID)
197 : 0 : return false;
198 : :
199 : 3 : return true;
200 : 3 : }
201 : :
202 : : template <>
203 : 10 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c(
204 : : JSContext* cx, const JS::HandleValue& value, GValue* out) {
205 : 10 : *out = G_VALUE_INIT;
206 : 10 : return gjs_value_to_g_value(cx, value, out);
207 : : }
208 : :
209 : : template <>
210 : 94 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c(
211 : : JSContext* cx, const JS::HandleValue& value, char** out) {
212 : 94 : JS::UniqueChars tmp_result = gjs_string_to_utf8(cx, value);
213 : :
214 [ + + ]: 94 : if (!tmp_result)
215 : 1 : return false;
216 : :
217 : 93 : *out = g_strdup(tmp_result.get());
218 : 93 : return true;
219 : 94 : }
220 : :
221 : : template <typename BigT>
222 : 2611 : [[nodiscard]] inline constexpr BigT max_safe_big_number() {
223 : 2611 : return (BigT(1) << std::numeric_limits<double>::digits) - 1;
224 : : }
225 : :
226 : : template <typename BigT>
227 : 2416 : [[nodiscard]] inline constexpr BigT min_safe_big_number() {
228 : : if constexpr (std::is_signed_v<BigT>)
229 : 217 : return -(max_safe_big_number<BigT>());
230 : :
231 : 2199 : return std::numeric_limits<BigT>::lowest();
232 : : }
233 : :
234 : : template <typename WantedType, GITypeTag TAG = GI_TYPE_TAG_VOID, typename T>
235 : 13712 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c_checked(
236 : : JSContext* cx, const JS::HandleValue& value, T* out, bool* out_of_range) {
237 : : static_assert(std::numeric_limits<T>::max() >=
238 : : std::numeric_limits<WantedType>::max() &&
239 : : std::numeric_limits<T>::lowest() <=
240 : : std::numeric_limits<WantedType>::lowest(),
241 : : "Container can't contain wanted type");
242 : :
243 : : if constexpr (std::is_same_v<WantedType, uint64_t> ||
244 : : std::is_same_v<WantedType, int64_t>) {
245 [ + - ]: 1666 : if (out_of_range) {
246 : 1666 : JS::BigInt* bi = nullptr;
247 : 1666 : *out_of_range = false;
248 : :
249 [ + + ]: 1666 : if (value.isBigInt()) {
250 : 60 : bi = value.toBigInt();
251 [ + + ]: 1606 : } else if (value.isNumber()) {
252 : 1600 : double number = value.toNumber();
253 [ + + ]: 1600 : if (!std::isfinite(number)) {
254 : 15 : *out = 0;
255 : 15 : return true;
256 : : }
257 : 1585 : number = std::trunc(number);
258 : 1585 : bi = JS::NumberToBigInt(cx, number);
259 [ - + ]: 1585 : if (!bi)
260 : 0 : return false;
261 : : }
262 : :
263 [ + + ]: 1651 : if (bi) {
264 : 1645 : *out_of_range = Gjs::bigint_is_out_of_range(bi, out);
265 : 1645 : return true;
266 : : }
267 : : }
268 : : }
269 : :
270 : : if constexpr (std::is_same_v<WantedType, T>)
271 : 14 : return js_value_to_c<TAG>(cx, value, out);
272 : :
273 : : // JS::ToIntNN() converts undefined, NaN, infinity to 0
274 : : if constexpr (std::is_integral_v<WantedType>) {
275 [ + + + + : 23883 : if (value.isUndefined() ||
+ + ]
276 [ + + ]: 11955 : (value.isDouble() && !std::isfinite(value.toDouble()))) {
277 : 27 : *out = 0;
278 : 27 : return true;
279 : : }
280 : : }
281 : :
282 : : if constexpr (std::is_arithmetic_v<T>) {
283 : 12011 : bool ret = js_value_to_c<TAG>(cx, value, out);
284 [ + - ]: 12011 : if (out_of_range) {
285 : : // Infinity and NaN preserved between floating point types
286 : : if constexpr (std::is_floating_point_v<WantedType> &&
287 : : std::is_floating_point_v<T>) {
288 [ + + ]: 110 : if (!std::isfinite(*out)) {
289 : 5 : *out_of_range = false;
290 : 5 : return ret;
291 : : }
292 : : }
293 : :
294 : 12006 : *out_of_range =
295 : 12006 : (*out >
296 [ + + ]: 24010 : static_cast<T>(std::numeric_limits<WantedType>::max()) ||
297 : 12004 : *out <
298 [ + + ]: 12004 : static_cast<T>(std::numeric_limits<WantedType>::lowest()));
299 : :
300 : : if constexpr (std::is_integral_v<WantedType> &&
301 : : std::is_floating_point_v<T>)
302 : 11738 : *out_of_range |= std::isnan(*out);
303 : : }
304 : 12006 : return ret;
305 : : // https://trac.cppcheck.net/ticket/10731
306 : : // cppcheck-suppress missingReturn
307 : : }
308 : : }
309 : :
310 : : template <typename WantedType, GITypeTag TAG = GI_TYPE_TAG_VOID>
311 : 1609 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c_checked(
312 : : JSContext* cx, const JS::HandleValue& value, TypeWrapper<WantedType>* out,
313 : : bool* out_of_range) {
314 : : static_assert(std::is_integral_v<WantedType>);
315 : :
316 : : WantedType wanted_out;
317 [ - + ]: 1609 : if (!js_value_to_c_checked<WantedType, TAG>(cx, value, &wanted_out,
318 : : out_of_range))
319 : 0 : return false;
320 : :
321 : 1609 : *out = TypeWrapper<WantedType>{wanted_out};
322 : :
323 : 1609 : return true;
324 : : }
325 : :
326 : : template <typename T, GITypeTag TAG = GI_TYPE_TAG_VOID>
327 : 14951 : GJS_JSAPI_RETURN_CONVENTION inline bool c_value_to_js(
328 : : JSContext* cx [[maybe_unused]], T value,
329 : : JS::MutableHandleValue js_value_p) {
330 : : if constexpr (std::is_same_v<T, bool>) {
331 : : js_value_p.setBoolean(value);
332 : : return true;
333 : : } else if constexpr (std::is_same_v< // NOLINT(readability/braces)
334 : : T, gboolean> &&
335 : : TAG == GI_TYPE_TAG_BOOLEAN) {
336 : 1067 : js_value_p.setBoolean(value);
337 : 1067 : return true;
338 : : } else if constexpr (std::is_arithmetic_v<T>) {
339 : : if constexpr (std::is_same_v<T, int64_t> ||
340 : : std::is_same_v<T, uint64_t>) {
341 [ + + + + : 2368 : if (value < Gjs::min_safe_big_number<T>() ||
+ + ]
342 : 1179 : value > Gjs::max_safe_big_number<T>()) {
343 : 30 : js_value_p.setDouble(value);
344 : 30 : return true;
345 : : }
346 : : }
347 : : if constexpr (std::is_floating_point_v<T>) {
348 : 53 : js_value_p.setDouble(JS::CanonicalizeNaN(double{value}));
349 : 53 : return true;
350 : : }
351 : 13097 : js_value_p.setNumber(value);
352 : 13097 : return true;
353 : : } else if constexpr (std::is_same_v<T, // NOLINT(readability/braces)
354 : : char*> ||
355 : : std::is_same_v<T, const char*>) {
356 [ + + ]: 704 : if (!value) {
357 : 9 : js_value_p.setNull();
358 : 9 : return true;
359 : : }
360 : 695 : return gjs_string_from_utf8(cx, value, js_value_p);
361 : : } else {
362 : : static_assert(std::is_arithmetic_v<T>, "Unsupported type");
363 : : }
364 : : }
365 : :
366 : : template <typename T, GITypeTag TAG = GI_TYPE_TAG_VOID>
367 : 14247 : GJS_JSAPI_RETURN_CONVENTION inline bool c_value_to_js_checked(
368 : : JSContext* cx [[maybe_unused]], T value,
369 : : JS::MutableHandleValue js_value_p) {
370 : : if constexpr (std::is_same_v<T, int64_t> || std::is_same_v<T, uint64_t>) {
371 [ + + + + : 2368 : if (value < Gjs::min_safe_big_number<T>() ||
+ + ]
372 : 1179 : value > Gjs::max_safe_big_number<T>()) {
373 : 30 : g_warning(
374 : : "Value %s cannot be safely stored in a JS Number "
375 : : "and may be rounded",
376 : : std::to_string(value).c_str());
377 : : }
378 : : }
379 : :
380 : 14247 : return c_value_to_js<T, TAG>(cx, value, js_value_p);
381 : : }
382 : :
383 : : } // namespace Gjs
|