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 : :
14 : : #include <girepository.h>
15 : : #include <glib-object.h>
16 : : #include <glib.h>
17 : :
18 : : #include <js/BigInt.h>
19 : : #include <js/Conversions.h>
20 : : #include <js/RootingAPI.h>
21 : : #include <js/TypeDecls.h>
22 : : #include <js/Utility.h> // for UniqueChars
23 : :
24 : : #include "gi/gtype.h"
25 : : #include "gi/value.h"
26 : : #include "gjs/jsapi-util.h"
27 : : #include "gjs/macros.h"
28 : :
29 : : namespace Gjs {
30 : :
31 : : template <typename T>
32 : : struct TypeWrapper {
33 : : constexpr TypeWrapper() : m_value(0) {}
34 : 1404 : explicit constexpr TypeWrapper(T v) : m_value(v) {}
35 : : constexpr operator T() const { return m_value; }
36 : 1400 : constexpr operator T() { return m_value; }
37 : :
38 : : private:
39 : : T m_value;
40 : : };
41 : :
42 : : namespace JsValueHolder {
43 : :
44 : : template <typename T1, typename T2>
45 : : constexpr bool comparable_types() {
46 : : return std::is_arithmetic_v<T1> == std::is_arithmetic_v<T2> &&
47 : : std::is_signed_v<T1> == std::is_signed_v<T2>;
48 : : }
49 : :
50 : : template <typename T, typename Container>
51 : : constexpr bool type_fits() {
52 : : if constexpr (comparable_types<T, Container>()) {
53 : : return (std::is_integral_v<T> == std::is_integral_v<Container> &&
54 : : std::numeric_limits<T>::max() <=
55 : : std::numeric_limits<Container>::max() &&
56 : : std::numeric_limits<T>::lowest() >=
57 : : std::numeric_limits<Container>::lowest());
58 : : }
59 : :
60 : : return false;
61 : : }
62 : :
63 : : /* The tag is needed to disambiguate types such as gboolean and GType
64 : : * which are in fact typedef's of other generic types.
65 : : * Setting a tag for a type allows to perform proper specialization. */
66 : : template <typename T, GITypeTag TAG>
67 : : constexpr auto get_strict() {
68 : : if constexpr (TAG != GI_TYPE_TAG_VOID) {
69 : : if constexpr (std::is_same_v<T, GType> && TAG == GI_TYPE_TAG_GTYPE)
70 : : return GType{};
71 : : else if constexpr (std::is_same_v<T, gboolean> &&
72 : : TAG == GI_TYPE_TAG_BOOLEAN)
73 : : return gboolean{};
74 : : else
75 : : return;
76 : : } else {
77 : : if constexpr (std::is_same_v<T, char32_t>)
78 : : return char32_t{};
79 : : else if constexpr (type_fits<T, int32_t>())
80 : : return int32_t{};
81 : : else if constexpr (type_fits<T, uint32_t>())
82 : : return uint32_t{};
83 : : else if constexpr (type_fits<T, int64_t>())
84 : : return int64_t{};
85 : : else if constexpr (type_fits<T, uint64_t>())
86 : : return uint64_t{};
87 : : else if constexpr (type_fits<T, double>())
88 : : return double{};
89 : : else
90 : : return T{};
91 : : }
92 : : }
93 : :
94 : : template <typename T>
95 : : constexpr auto get_relaxed() {
96 : : if constexpr (std::is_same_v<T, int64_t> || std::is_same_v<T, uint64_t>)
97 : : return TypeWrapper<T>{};
98 : : else if constexpr (type_fits<T, int32_t>())
99 : : return int32_t{};
100 : : else if constexpr (type_fits<T, uint16_t>())
101 : : return uint32_t{};
102 : : else if constexpr (std::is_arithmetic_v<T>)
103 : : return double{};
104 : : else
105 : : return T{};
106 : : }
107 : :
108 : : template <typename T, GITypeTag TAG = GI_TYPE_TAG_VOID>
109 : : using Strict = decltype(JsValueHolder::get_strict<T, TAG>());
110 : :
111 : : template <typename T>
112 : : using Relaxed = decltype(JsValueHolder::get_relaxed<T>());
113 : :
114 : : } // namespace JsValueHolder
115 : :
116 : :
117 : : template <typename T, typename MODE = JsValueHolder::Relaxed<T>>
118 : : constexpr bool type_has_js_getter() {
119 : : return std::is_same_v<T, MODE>;
120 : : }
121 : :
122 : : /* Avoid implicit conversions */
123 : : template <GITypeTag TAG = GI_TYPE_TAG_VOID, typename T>
124 : : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c(JSContext*,
125 : : const JS::HandleValue&,
126 : : T*) = delete;
127 : :
128 : : template <>
129 : 12179 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c(
130 : : JSContext* cx, const JS::HandleValue& value, int32_t* out) {
131 : 12179 : return JS::ToInt32(cx, value, out);
132 : : }
133 : :
134 : : template <>
135 : 128 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c(
136 : : JSContext* cx, const JS::HandleValue& value, uint32_t* out) {
137 : 128 : return JS::ToUint32(cx, value, out);
138 : : }
139 : :
140 : : template <>
141 : 24 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c(
142 : : JSContext* cx, const JS::HandleValue& value, char32_t* out) {
143 : : uint32_t tmp;
144 : 24 : bool retval = JS::ToUint32(cx, value, &tmp);
145 : 24 : *out = tmp;
146 : 24 : return retval;
147 : : }
148 : :
149 : : template <>
150 : 537 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c(
151 : : JSContext* cx, const JS::HandleValue& value, int64_t* out) {
152 [ - + ]: 537 : if (value.isBigInt()) {
153 : 0 : *out = JS::ToBigInt64(value.toBigInt());
154 : 0 : return true;
155 : : }
156 : 537 : return JS::ToInt64(cx, value, out);
157 : : }
158 : :
159 : : template <>
160 : 7 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c(
161 : : JSContext* cx, const JS::HandleValue& value, uint64_t* out) {
162 [ - + ]: 7 : if (value.isBigInt()) {
163 : 0 : *out = JS::ToBigUint64(value.toBigInt());
164 : 0 : return true;
165 : : }
166 : 7 : return JS::ToUint64(cx, value, out);
167 : : }
168 : :
169 : : template <>
170 : 11460 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c(
171 : : JSContext* cx, const JS::HandleValue& value, double* out) {
172 : 11460 : return JS::ToNumber(cx, value, out);
173 : : }
174 : :
175 : : template <>
176 : 12 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c<GI_TYPE_TAG_BOOLEAN>(
177 : : JSContext*, const JS::HandleValue& value, gboolean* out) {
178 : 12 : *out = !!JS::ToBoolean(value);
179 : 12 : return true;
180 : : }
181 : :
182 : : template <>
183 : 5 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c<GI_TYPE_TAG_GTYPE>(
184 : : JSContext* cx, const JS::HandleValue& value, GType* out) {
185 [ + + ]: 5 : if (!value.isObject())
186 : 2 : return false;
187 : :
188 : 3 : JS::RootedObject elem_obj(cx);
189 : 3 : elem_obj = &value.toObject();
190 : :
191 [ - + ]: 3 : if (!gjs_gtype_get_actual_gtype(cx, elem_obj, out))
192 : 0 : return false;
193 : :
194 [ - + ]: 3 : if (*out == G_TYPE_INVALID)
195 : 0 : return false;
196 : :
197 : 3 : return true;
198 : 3 : }
199 : :
200 : : template <>
201 : 10 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c(
202 : : JSContext* cx, const JS::HandleValue& value, GValue* out) {
203 : 10 : *out = G_VALUE_INIT;
204 : 10 : return gjs_value_to_g_value(cx, value, out);
205 : : }
206 : :
207 : : template <>
208 : 91 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c(
209 : : JSContext* cx, const JS::HandleValue& value, char** out) {
210 : 91 : JS::UniqueChars tmp_result = gjs_string_to_utf8(cx, value);
211 : :
212 [ + + ]: 91 : if (!tmp_result)
213 : 1 : return false;
214 : :
215 : 90 : *out = g_strdup(tmp_result.get());
216 : 90 : return true;
217 : 91 : }
218 : :
219 : : template <typename BigT>
220 : 1264 : [[nodiscard]] inline constexpr BigT max_safe_big_number() {
221 : 1264 : return (BigT(1) << std::numeric_limits<double>::digits) - 1;
222 : : }
223 : :
224 : : template <typename BigT>
225 : 1152 : [[nodiscard]] inline constexpr BigT min_safe_big_number() {
226 : : if constexpr (std::is_signed_v<BigT>)
227 : 124 : return -(max_safe_big_number<BigT>());
228 : :
229 : 1028 : return std::numeric_limits<BigT>::lowest();
230 : : }
231 : :
232 : : template <typename WantedType, typename T>
233 : 12983 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c_checked(
234 : : JSContext* cx, const JS::HandleValue& value, T* out, bool* out_of_range) {
235 : : static_assert(std::numeric_limits<T>::max() >=
236 : : std::numeric_limits<WantedType>::max() &&
237 : : std::numeric_limits<T>::lowest() <=
238 : : std::numeric_limits<WantedType>::lowest(),
239 : : "Container can't contain wanted type");
240 : :
241 : : if constexpr (std::is_same_v<WantedType, uint64_t> ||
242 : : std::is_same_v<WantedType, int64_t>) {
243 [ + - ]: 1461 : if (out_of_range) {
244 : 1461 : JS::BigInt* bi = nullptr;
245 : 1461 : *out_of_range = false;
246 : :
247 [ + + ]: 1461 : if (value.isBigInt()) {
248 : 60 : bi = value.toBigInt();
249 [ + + ]: 1401 : } else if (value.isNumber()) {
250 : 1395 : double number = value.toNumber();
251 [ + + ]: 1395 : if (!std::isfinite(number)) {
252 : 15 : *out = 0;
253 : 15 : return true;
254 : : }
255 : 1380 : number = std::trunc(number);
256 : 1380 : bi = JS::NumberToBigInt(cx, number);
257 [ - + ]: 1380 : if (!bi)
258 : 0 : return false;
259 : : }
260 : :
261 [ + + ]: 1446 : if (bi) {
262 : 1440 : *out_of_range = Gjs::bigint_is_out_of_range(bi, out);
263 : 1440 : return true;
264 : : }
265 : : }
266 : : }
267 : :
268 : : if constexpr (std::is_same_v<WantedType, T>)
269 : 14 : return js_value_to_c(cx, value, out);
270 : :
271 : : // JS::ToIntNN() converts undefined, NaN, infinity to 0
272 : : if constexpr (std::is_integral_v<WantedType>) {
273 [ + + + + : 22839 : if (value.isUndefined() ||
+ + ]
274 [ + + ]: 11433 : (value.isDouble() && !std::isfinite(value.toDouble()))) {
275 : 27 : *out = 0;
276 : 27 : return true;
277 : : }
278 : : }
279 : :
280 : : if constexpr (std::is_arithmetic_v<T>) {
281 : 11487 : bool ret = js_value_to_c(cx, value, out);
282 [ + - ]: 11487 : if (out_of_range) {
283 : : // Infinity and NaN preserved between floating point types
284 : : if constexpr (std::is_floating_point_v<WantedType> &&
285 : : std::is_floating_point_v<T>) {
286 [ + + ]: 108 : if (!std::isfinite(*out)) {
287 : 5 : *out_of_range = false;
288 : 5 : return ret;
289 : : }
290 : : }
291 : :
292 : 11482 : *out_of_range =
293 : 11482 : (*out >
294 [ + + ]: 22962 : static_cast<T>(std::numeric_limits<WantedType>::max()) ||
295 : 11480 : *out <
296 [ + + ]: 11480 : static_cast<T>(std::numeric_limits<WantedType>::lowest()));
297 : :
298 : : if constexpr (std::is_integral_v<WantedType> &&
299 : : std::is_floating_point_v<T>)
300 : 11219 : *out_of_range |= std::isnan(*out);
301 : : }
302 : 11482 : return ret;
303 : : }
304 : : }
305 : :
306 : : template <typename WantedType>
307 : 1404 : GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c_checked(
308 : : JSContext* cx, const JS::HandleValue& value, TypeWrapper<WantedType>* out,
309 : : bool* out_of_range) {
310 : : static_assert(std::is_integral_v<WantedType>);
311 : :
312 : : WantedType wanted_out;
313 [ - + ]: 1404 : if (!js_value_to_c_checked<WantedType>(cx, value, &wanted_out,
314 : : out_of_range))
315 : 0 : return false;
316 : :
317 : 1404 : *out = TypeWrapper<WantedType>{wanted_out};
318 : :
319 : 1404 : return true;
320 : : }
321 : :
322 : : } // namespace Gjs
|