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