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 : : #include <type_traits>
15 : : #include <utility> // for move
16 : :
17 : : #include <glib-object.h>
18 : : #include <glib.h>
19 : :
20 : : #include <js/BigInt.h>
21 : : #include <js/CharacterEncoding.h> // for JS_EncodeStringToUTF8
22 : : #include <js/Conversions.h>
23 : : #include <js/RootingAPI.h>
24 : : #include <js/TypeDecls.h>
25 : : #include <js/Utility.h> // for UniqueChars
26 : : #include <js/Value.h> // for CanonicalizeNaN
27 : :
28 : : #include "gi/arg-types-inl.h"
29 : : #include "gi/gtype.h"
30 : : #include "gi/value.h"
31 : : #include "gjs/auto.h"
32 : : #include "gjs/jsapi-util.h"
33 : : #include "gjs/macros.h"
34 : :
35 : : namespace Gjs {
36 : :
37 : : // There are two ways you can unpack a C value from a JSValue.
38 : : // ContainingType means storing the unpacked value in the most appropriate C
39 : : // type that can contain it. Implicit conversion may be performed and the value
40 : : // may need to be checked to make sure it is in range.
41 : : // PackType, on the other hand, means storing it in the C type that is exactly
42 : : // equivalent to how JSValue stores it, so no implicit conversion is performed
43 : : // unless the JSValue contains a pointer to a GC-thing, like BigInt.
44 : : enum HolderMode { ContainingType, PackType };
45 : :
46 : : template <typename TAG, HolderMode MODE = HolderMode::PackType>
47 : : constexpr bool type_has_js_getter() {
48 : : if constexpr (MODE == HolderMode::PackType) {
49 : : return std::is_same_v<Tag::RealT<TAG>, Tag::JSValuePackT<TAG>>;
50 : : } else {
51 : : return std::is_same_v<Tag::RealT<TAG>, Tag::JSValueContainingT<TAG>>;
52 : : }
53 : : }
54 : :
55 : : /* Avoid implicit conversions */
56 : : template <typename TAG, typename UnpackT>
57 : : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c(JSContext*,
58 : : JS::HandleValue,
59 : : UnpackT*) = delete;
60 : :
61 : : template <>
62 : 8 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c<signed char>(
63 : : JSContext* cx, JS::HandleValue value, int32_t* out) {
64 : 8 : return JS::ToInt32(cx, value, out);
65 : : }
66 : :
67 : : template <>
68 : : GJS_JSAPI_RETURN_CONVENTION inline bool
69 : 8 : js_value_to_c<signed short> // NOLINT(runtime/int)
70 : : (JSContext* cx, JS::HandleValue value, int32_t* out) {
71 : 8 : return JS::ToInt32(cx, value, out);
72 : : }
73 : :
74 : : template <>
75 : 14857 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c<int32_t>(
76 : : JSContext* cx, JS::HandleValue value, int32_t* out) {
77 : 14857 : return JS::ToInt32(cx, value, out);
78 : : }
79 : :
80 : : template <>
81 : : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c<unsigned char>(
82 : : JSContext* cx, JS::HandleValue value, int32_t* out) {
83 : : return JS::ToInt32(cx, value, out);
84 : : }
85 : :
86 : : template <>
87 : 38 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c<unsigned char>(
88 : : JSContext* cx, JS::HandleValue value, uint32_t* out) {
89 : 38 : return JS::ToUint32(cx, value, out);
90 : : }
91 : :
92 : : template <>
93 : : GJS_JSAPI_RETURN_CONVENTION inline bool
94 : : js_value_to_c<unsigned short> // NOLINT(runtime/int)
95 : : (JSContext* cx, JS::HandleValue value, int32_t* out) {
96 : : return JS::ToInt32(cx, value, out);
97 : : }
98 : :
99 : : template <>
100 : : GJS_JSAPI_RETURN_CONVENTION inline bool
101 : 0 : js_value_to_c<unsigned short> // NOLINT(runtime/int)
102 : : (JSContext* cx, JS::HandleValue value, uint32_t* out) {
103 : 0 : return JS::ToUint32(cx, value, out);
104 : : }
105 : :
106 : : template <>
107 : 75 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c<uint32_t>(
108 : : JSContext* cx, JS::HandleValue value, uint32_t* out) {
109 : 75 : return JS::ToUint32(cx, value, out);
110 : : }
111 : :
112 : : template <>
113 : 24 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c<char32_t>(
114 : : JSContext* cx, JS::HandleValue value, char32_t* out) {
115 : : uint32_t tmp;
116 : 24 : bool retval = JS::ToUint32(cx, value, &tmp);
117 : 24 : *out = tmp;
118 : 24 : return retval;
119 : : }
120 : :
121 : : template <>
122 : 638 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c<int64_t>(
123 : : JSContext* cx, JS::HandleValue value, int64_t* out) {
124 [ - + ]: 638 : if (value.isBigInt()) {
125 : 0 : *out = JS::ToBigInt64(value.toBigInt());
126 : 0 : return true;
127 : : }
128 : 638 : return JS::ToInt64(cx, value, out);
129 : : }
130 : :
131 : : template <>
132 : 9 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c<uint64_t>(
133 : : JSContext* cx, JS::HandleValue value, uint64_t* out) {
134 [ + + ]: 9 : if (value.isBigInt()) {
135 : 1 : *out = JS::ToBigUint64(value.toBigInt());
136 : 1 : return true;
137 : : }
138 : 8 : return JS::ToUint64(cx, value, out);
139 : : }
140 : :
141 : : template <>
142 : : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c<uint32_t>(
143 : : JSContext* cx, JS::HandleValue value, double* out) {
144 : : return JS::ToNumber(cx, value, out);
145 : : }
146 : :
147 : : template <>
148 : 2 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c<float>(
149 : : JSContext* cx, JS::HandleValue value, double* out) {
150 : 2 : return JS::ToNumber(cx, value, out);
151 : : }
152 : :
153 : : template <>
154 : 13975 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c<double>(
155 : : JSContext* cx, JS::HandleValue value, double* out) {
156 : 13975 : return JS::ToNumber(cx, value, out);
157 : : }
158 : :
159 : : template <>
160 : 27 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c<Tag::GBoolean>(
161 : : JSContext*, JS::HandleValue value, gboolean* out) {
162 : 27 : *out = !!JS::ToBoolean(value);
163 : 27 : return true;
164 : : }
165 : :
166 : : template <>
167 : 5 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c<Tag::GType>(
168 : : JSContext* cx, JS::HandleValue value, GType* out) {
169 [ + + ]: 5 : if (!value.isObject())
170 : 2 : return false;
171 : :
172 : 3 : JS::RootedObject elem_obj(cx);
173 : 3 : elem_obj = &value.toObject();
174 : :
175 [ - + ]: 3 : if (!gjs_gtype_get_actual_gtype(cx, elem_obj, out))
176 : 0 : return false;
177 : :
178 [ - + ]: 3 : if (*out == G_TYPE_INVALID)
179 : 0 : return false;
180 : :
181 : 3 : return true;
182 : 3 : }
183 : :
184 : : template <>
185 : 10 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c<GValue>(
186 : : JSContext* cx, JS::HandleValue value, GValue* out) {
187 : 10 : *out = G_VALUE_INIT;
188 : 10 : return gjs_value_to_g_value(cx, value, out);
189 : : }
190 : :
191 : : template <>
192 : 313 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c<char*>(
193 : : JSContext* cx, JS::HandleValue value, char** out) {
194 [ + + ]: 313 : if (value.isNull()) {
195 : 4 : *out = nullptr;
196 : 4 : return true;
197 : : }
198 : :
199 [ + + ]: 309 : if (!value.isString())
200 : 1 : return false;
201 : :
202 : 308 : JS::RootedString str{cx, value.toString()};
203 : 308 : JS::UniqueChars utf8 = JS_EncodeStringToUTF8(cx, str);
204 : :
205 : 308 : *out = js_chars_to_glib(std::move(utf8)).release();
206 : 308 : return true;
207 : 308 : }
208 : :
209 : : template <typename BigT>
210 : 2895 : [[nodiscard]] inline constexpr BigT max_safe_big_number() {
211 : 2895 : return (BigT(1) << std::numeric_limits<double>::digits) - 1;
212 : : }
213 : :
214 : : template <typename BigT>
215 : 2545 : [[nodiscard]] inline constexpr BigT min_safe_big_number() {
216 : : if constexpr (std::is_signed_v<BigT>)
217 : 382 : return -(max_safe_big_number<BigT>());
218 : :
219 : 2163 : return std::numeric_limits<BigT>::lowest();
220 : : }
221 : :
222 : : template <typename WantedType, typename TAG,
223 : : typename = std::enable_if_t<!std::is_same_v<Tag::RealT<TAG>, TAG>>,
224 : : typename U>
225 : 15725 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c_checked(
226 : : JSContext* cx, JS::HandleValue value, U* out, bool* out_of_range) {
227 : : using T = Tag::RealT<TAG>;
228 : : static_assert(std::numeric_limits<T>::max() >=
229 : : std::numeric_limits<WantedType>::max() &&
230 : : std::numeric_limits<T>::lowest() <=
231 : : std::numeric_limits<WantedType>::lowest(),
232 : : "Container can't contain wanted type");
233 : :
234 : : if constexpr (std::is_same_v<WantedType, uint64_t> ||
235 : : std::is_same_v<WantedType, int64_t>) {
236 [ + - ]: 1672 : if (out_of_range) {
237 : 1672 : JS::BigInt* bi = nullptr;
238 : 1672 : *out_of_range = false;
239 : :
240 [ + + ]: 1672 : if (value.isBigInt()) {
241 : 79 : bi = value.toBigInt();
242 [ + + ]: 1593 : } else if (value.isNumber()) {
243 : 1585 : double number = value.toNumber();
244 [ + + ]: 1585 : if (!std::isfinite(number)) {
245 : 15 : *out = 0;
246 : 15 : return true;
247 : : }
248 : 1570 : number = std::trunc(number);
249 : 1570 : bi = JS::NumberToBigInt(cx, number);
250 [ - + ]: 1570 : if (!bi)
251 : 0 : return false;
252 : : }
253 : :
254 [ + + ]: 1657 : if (bi) {
255 : 1649 : *out_of_range = Gjs::bigint_is_out_of_range(bi, out);
256 : 1649 : return true;
257 : : }
258 : : }
259 : : }
260 : :
261 : : if constexpr (std::is_same_v<WantedType, T>)
262 : 19 : return js_value_to_c<TAG>(cx, value, out);
263 : :
264 : : // JS::ToIntNN() converts undefined, NaN, infinity to 0
265 : : if constexpr (std::is_integral_v<WantedType>) {
266 [ + + + + : 27854 : if (value.isUndefined() ||
+ + ]
267 [ + + ]: 13941 : (value.isDouble() && !std::isfinite(value.toDouble()))) {
268 : 27 : *out = 0;
269 : 27 : return true;
270 : : }
271 : : }
272 : :
273 : : if constexpr (std::is_arithmetic_v<T>) {
274 : 14015 : bool ret = js_value_to_c<TAG>(cx, value, out);
275 [ + - ]: 14015 : if (out_of_range) {
276 : : // Infinity and NaN preserved between floating point types
277 : : if constexpr (std::is_floating_point_v<WantedType> &&
278 : : std::is_floating_point_v<T>) {
279 [ + + ]: 129 : if (!std::isfinite(*out)) {
280 : 5 : *out_of_range = false;
281 : 5 : return ret;
282 : : }
283 : : }
284 : :
285 : 14010 : *out_of_range =
286 : 14010 : (*out >
287 [ + - ]: 28020 : static_cast<T>(std::numeric_limits<WantedType>::max()) ||
288 : 14010 : *out <
289 [ + + ]: 14010 : static_cast<T>(std::numeric_limits<WantedType>::lowest()));
290 : :
291 : : if constexpr (std::is_integral_v<WantedType> &&
292 : : std::is_floating_point_v<T>)
293 : 13679 : *out_of_range |= std::isnan(*out);
294 : : }
295 : 14010 : return ret;
296 : : // https://trac.cppcheck.net/ticket/10731
297 : : // cppcheck-suppress missingReturn
298 : : }
299 : : }
300 : :
301 : : template <typename WantedType, typename T,
302 : : typename = std::enable_if_t<std::is_same_v<Tag::RealT<T>, T>>>
303 : 15725 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c_checked(
304 : : JSContext* cx, JS::HandleValue value, T* out, bool* out_of_range) {
305 : 15725 : return js_value_to_c_checked<WantedType, T, void, T>(cx, value, out,
306 : 15725 : out_of_range);
307 : : }
308 : :
309 : : template <typename WantedType, typename TAG, typename U>
310 : 1626 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c_checked(
311 : : JSContext* cx, JS::HandleValue value, TypeWrapper<U>* out,
312 : : bool* out_of_range) {
313 : : static_assert(std::is_integral_v<WantedType>);
314 : :
315 : : if constexpr (std::is_same_v<WantedType, U>) {
316 : : WantedType wanted_out;
317 [ - + ]: 1626 : if (!js_value_to_c_checked<WantedType, TAG>(cx, value, &wanted_out,
318 : : out_of_range))
319 : 0 : return false;
320 : :
321 : 1626 : *out = TypeWrapper<WantedType>{wanted_out};
322 : :
323 : 1626 : return true;
324 : : }
325 : :
326 : : // Handle the cases resulting from TypeWrapper<long> and
327 : : // TypeWrapper<int64_t> not being convertible on macOS
328 : : if constexpr (!std::is_same_v<int64_t, long> && // NOLINT(runtime/int)
329 : : std::is_same_v<WantedType, long> && // NOLINT(runtime/int)
330 : : std::is_same_v<U, int64_t>) {
331 : : return js_value_to_c_checked<int64_t, int64_t>(cx, value, out,
332 : : out_of_range);
333 : : }
334 : :
335 : : if constexpr (!std::is_same_v<uint64_t,
336 : : unsigned long> && // NOLINT(runtime/int)
337 : : std::is_same_v<WantedType,
338 : : unsigned long> && // NOLINT(runtime/int)
339 : : std::is_same_v<U, uint64_t>) {
340 : : return js_value_to_c_checked<uint64_t, uint64_t>(cx, value, out,
341 : : out_of_range);
342 : : // https://trac.cppcheck.net/ticket/10731
343 : : // cppcheck-suppress missingReturn
344 : : }
345 : : }
346 : :
347 : : template <typename TAG>
348 : 17836 : GJS_JSAPI_RETURN_CONVENTION inline bool c_value_to_js(
349 : : JSContext* cx [[maybe_unused]], Tag::RealT<TAG> value,
350 : : JS::MutableHandleValue js_value_p) {
351 : : using T = Tag::RealT<TAG>;
352 : :
353 : : if constexpr (std::is_same_v<TAG, bool> ||
354 : : std::is_same_v<TAG, Tag::GBoolean>) {
355 : 1211 : js_value_p.setBoolean(value);
356 : 1211 : return true;
357 : : } else if constexpr (std::is_arithmetic_v<T>) {
358 : : if constexpr (std::is_same_v<T, int64_t> ||
359 : : std::is_same_v<T, uint64_t>) {
360 [ + + + + : 2519 : if (value < Gjs::min_safe_big_number<T>() ||
+ + ]
361 : 1252 : value > Gjs::max_safe_big_number<T>()) {
362 : 48 : js_value_p.setDouble(value);
363 : 48 : return true;
364 : : }
365 : : }
366 : : if constexpr (std::is_floating_point_v<T>) {
367 : 129 : js_value_p.setDouble(JS::CanonicalizeNaN(double{value}));
368 : 129 : return true;
369 : : }
370 : 15442 : js_value_p.setNumber(value);
371 : 15442 : return true;
372 : : } else if constexpr (std::is_same_v<T, // NOLINT(readability/braces)
373 : : char*> ||
374 : : std::is_same_v<T, const char*>) {
375 [ + + ]: 1006 : if (!value) {
376 : 22 : js_value_p.setNull();
377 : 22 : return true;
378 : : }
379 : 984 : return gjs_string_from_utf8(cx, value, js_value_p);
380 : : } else {
381 : : static_assert(std::is_arithmetic_v<T>, "Unsupported type");
382 : : }
383 : : }
384 : :
385 : : // Specialization for types where TAG and RealT<TAG> are the same type, to allow
386 : : // inferring template parameter
387 : : template <typename T,
388 : : typename = std::enable_if_t<std::is_same_v<Tag::RealT<T>, T>>>
389 : 1227 : GJS_JSAPI_RETURN_CONVENTION inline bool c_value_to_js(
390 : : JSContext* cx, T value, JS::MutableHandleValue js_value_p) {
391 : 1227 : return c_value_to_js<T>(cx, value, js_value_p);
392 : : }
393 : :
394 : : template <typename TAG>
395 : 16581 : GJS_JSAPI_RETURN_CONVENTION inline bool c_value_to_js_checked(
396 : : JSContext* cx [[maybe_unused]], Tag::RealT<TAG> value,
397 : : JS::MutableHandleValue js_value_p) {
398 : : using T = Tag::RealT<TAG>;
399 : : if constexpr (std::is_same_v<T, int64_t> || std::is_same_v<T, uint64_t>) {
400 [ + + + + : 2463 : if (value < Gjs::min_safe_big_number<T>() ||
+ + ]
401 : 1224 : value > Gjs::max_safe_big_number<T>()) {
402 : 48 : g_warning(
403 : : "Value %s cannot be safely stored in a JS Number "
404 : : "and may be rounded",
405 : : std::to_string(value).c_str());
406 : : }
407 : : }
408 : :
409 : 16581 : return c_value_to_js<TAG>(cx, value, js_value_p);
410 : : }
411 : :
412 : : // Specialization for types where TAG and RealT<TAG> are the same type, to allow
413 : : // inferring template parameter
414 : : template <typename T,
415 : : typename = std::enable_if_t<std::is_same_v<Tag::RealT<T>, T>>>
416 : 40 : GJS_JSAPI_RETURN_CONVENTION inline bool c_value_to_js_checked(
417 : : JSContext* cx, T value, JS::MutableHandleValue js_value_p) {
418 : 40 : return c_value_to_js_checked<T>(cx, value, js_value_p);
419 : : }
420 : :
421 : : } // namespace Gjs
|