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: 2008 litl, LLC
4 : :
5 : : #include <config.h>
6 : :
7 : : #include <stdint.h>
8 : :
9 : : #include <string> // for string methods
10 : :
11 : : #include <girepository/girepository.h>
12 : : #include <glib-object.h>
13 : :
14 : : #include <js/CallAndConstruct.h>
15 : : #include <js/CallArgs.h>
16 : : #include <js/Class.h>
17 : : #include <js/ColumnNumber.h>
18 : : #include <js/Exception.h>
19 : : #include <js/PropertyAndElement.h>
20 : : #include <js/PropertyDescriptor.h> // for JSPROP_ENUMERATE
21 : : #include <js/PropertySpec.h> // for JS_PSG
22 : : #include <js/RootingAPI.h>
23 : : #include <js/SavedFrameAPI.h>
24 : : #include <js/Stack.h> // for BuildStackString, CaptureCurrentStack
25 : : #include <js/TypeDecls.h>
26 : : #include <js/Utility.h> // for UniqueChars
27 : : #include <js/Value.h>
28 : : #include <js/ValueArray.h>
29 : : #include <jsapi.h> // for InformalValueTypeName, JS_GetClassObject
30 : : #include <jspubtd.h> // for JSProtoKey, JSProto_Error, JSProt...
31 : : #include <mozilla/Maybe.h>
32 : :
33 : : #include "gi/arg-inl.h"
34 : : #include "gi/enumeration.h"
35 : : #include "gi/gerror.h"
36 : : #include "gi/info.h"
37 : : #include "gi/repo.h"
38 : : #include "gi/struct.h"
39 : : #include "gjs/atoms.h"
40 : : #include "gjs/auto.h"
41 : : #include "gjs/context-private.h"
42 : : #include "gjs/error-types.h"
43 : : #include "gjs/gerror-result.h"
44 : : #include "gjs/jsapi-util.h"
45 : : #include "gjs/macros.h"
46 : : #include "gjs/mem-private.h"
47 : : #include "util/log.h"
48 : :
49 : : using mozilla::Maybe;
50 : :
51 : 14 : ErrorPrototype::ErrorPrototype(const GI::EnumInfo info, GType gtype)
52 : : : GIWrapperPrototype(info, gtype),
53 : 14 : m_domain(g_quark_from_string(info.error_domain())) {
54 : 14 : GJS_INC_COUNTER(gerror_prototype);
55 : 14 : }
56 : :
57 : 14 : ErrorPrototype::~ErrorPrototype() { GJS_DEC_COUNTER(gerror_prototype); }
58 : :
59 : 55 : ErrorInstance::ErrorInstance(ErrorPrototype* prototype, JS::HandleObject obj)
60 : 55 : : GIWrapperInstance(prototype, obj) {
61 : 55 : GJS_INC_COUNTER(gerror_instance);
62 : 55 : }
63 : :
64 : 55 : ErrorInstance::~ErrorInstance() { GJS_DEC_COUNTER(gerror_instance); }
65 : :
66 : : /**
67 : : * ErrorBase::domain:
68 : : *
69 : : * Fetches ErrorPrototype::domain() for instances as well as prototypes.
70 : : */
71 : 39 : GQuark ErrorBase::domain() const { return get_prototype()->domain(); }
72 : :
73 : : // See GIWrapperBase::constructor().
74 : 11 : bool ErrorInstance::constructor_impl(JSContext* cx, JS::HandleObject object,
75 : : const JS::CallArgs& args) {
76 [ + - - + : 11 : if (args.length() != 1 || !args[0].isObject()) {
- + ]
77 : 0 : gjs_throw(cx,
78 : : "Invalid parameters passed to GError constructor, expected "
79 : : "one object");
80 : 0 : return false;
81 : : }
82 : :
83 : 11 : JS::RootedObject params_obj{cx, &args[0].toObject()};
84 : 11 : JS::UniqueChars message;
85 : 11 : const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
86 [ - + ]: 11 : if (!gjs_object_require_property(cx, params_obj, "GError constructor",
87 : : atoms.message(), &message))
88 : 0 : return false;
89 : :
90 : : int32_t code;
91 [ - + ]: 11 : if (!gjs_object_require_property(cx, params_obj, "GError constructor",
92 : : atoms.code(), &code))
93 : 0 : return false;
94 : :
95 : 11 : m_ptr = g_error_new_literal(domain(), code, message.get());
96 : :
97 : : // We assume this error will be thrown in the same line as the constructor
98 : 11 : return gjs_define_error_properties(cx, object);
99 : 11 : }
100 : :
101 : : /**
102 : : * ErrorBase::get_domain:
103 : : *
104 : : * JSNative property getter for `domain`. This property works on prototypes as
105 : : * well as instances.
106 : : */
107 : 5 : bool ErrorBase::get_domain(JSContext* cx, unsigned argc, JS::Value* vp) {
108 [ - + - + ]: 5 : GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ErrorBase, priv);
109 : 5 : args.rval().setInt32(priv->domain());
110 : 5 : return true;
111 : 5 : }
112 : :
113 : : // JSNative property getter for `message`.
114 : 6 : bool ErrorBase::get_message(JSContext* cx, unsigned argc, JS::Value* vp) {
115 [ - + - + ]: 6 : GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ErrorBase, priv);
116 [ - + ]: 6 : if (!priv->check_is_instance(cx, "get a field"))
117 : 0 : return false;
118 : :
119 : 6 : return gjs_string_from_utf8(cx, priv->to_instance()->message(),
120 : 6 : args.rval());
121 : 6 : }
122 : :
123 : : // JSNative property getter for `code`.
124 : 7 : bool ErrorBase::get_code(JSContext* cx, unsigned argc, JS::Value* vp) {
125 [ - + - + ]: 7 : GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, obj, ErrorBase, priv);
126 [ - + ]: 7 : if (!priv->check_is_instance(cx, "get a field"))
127 : 0 : return false;
128 : :
129 : 7 : args.rval().setInt32(priv->to_instance()->code());
130 : 7 : return true;
131 : 7 : }
132 : :
133 : : // JSNative implementation of `toString()`.
134 : 10 : bool ErrorBase::to_string(JSContext* cx, unsigned argc, JS::Value* vp) {
135 [ - + ]: 10 : GJS_GET_THIS(cx, argc, vp, rec, self);
136 : :
137 : 10 : Gjs::AutoChar descr;
138 : :
139 : : // An error created via `new GLib.Error` will have a Struct* private
140 : : // pointer, not an Error*, so we can't call regular to_string() on it.
141 [ + + ]: 10 : if (StructBase::typecheck(cx, self, G_TYPE_ERROR, GjsTypecheckNoThrow{})) {
142 : 2 : auto* gerror = StructBase::to_c_ptr<GError>(cx, self);
143 [ - + ]: 2 : if (!gerror)
144 : 0 : return false;
145 : : descr =
146 : : g_strdup_printf("GLib.Error %s: %s",
147 : 2 : g_quark_to_string(gerror->domain), gerror->message);
148 : :
149 : 2 : return gjs_string_from_utf8(cx, descr, rec.rval());
150 : : }
151 : :
152 : : ErrorBase* priv;
153 [ - + ]: 8 : if (!for_js_typecheck(cx, self, &priv, &rec))
154 : 0 : return false;
155 : :
156 : : // We follow the same pattern as standard JS errors, at the expense of
157 : : // hiding some useful information
158 : :
159 [ - + ]: 8 : if (priv->is_prototype()) {
160 : 0 : descr = g_strdup(priv->format_name().c_str());
161 : : } else {
162 : 16 : descr = g_strdup_printf("%s: %s", priv->format_name().c_str(),
163 : 16 : priv->to_instance()->message());
164 : : }
165 : :
166 : 8 : return gjs_string_from_utf8(cx, descr, rec.rval());
167 : 10 : }
168 : :
169 : : // JSNative implementation of `valueOf()`.
170 : 23 : bool ErrorBase::value_of(JSContext* cx, unsigned argc, JS::Value* vp) {
171 [ - + ]: 23 : GJS_GET_THIS(cx, argc, vp, rec, self);
172 : 23 : JS::RootedObject prototype{cx};
173 : 23 : const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
174 : :
175 [ - + ]: 23 : if (!gjs_object_require_property(cx, self, "constructor", atoms.prototype(),
176 : 23 : &prototype)) {
177 : : // This error message will be more informative
178 : 0 : JS_ClearPendingException(cx);
179 : 0 : gjs_throw(cx,
180 : : "GLib.Error.valueOf() called on something that is not a "
181 : : "constructor");
182 : 0 : return false;
183 : : }
184 : :
185 : : ErrorBase* priv;
186 [ - + ]: 23 : if (!for_js_typecheck(cx, prototype, &priv, &rec))
187 : 0 : return false;
188 : :
189 : 23 : rec.rval().setInt32(priv->domain());
190 : 23 : return true;
191 : 23 : }
192 : :
193 : : const struct JSClassOps ErrorBase::class_ops = {
194 : : nullptr, // addProperty
195 : : nullptr, // deleteProperty
196 : : nullptr, // enumerate
197 : : nullptr, // newEnumerate
198 : : nullptr, // resolve
199 : : nullptr, // mayResolve
200 : : &ErrorBase::finalize,
201 : : };
202 : :
203 : : const struct JSClass ErrorBase::klass = {
204 : : "GLib_Error",
205 : : JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_BACKGROUND_FINALIZE |
206 : : JSCLASS_IS_DOMJSCLASS, // needed for Error.isError()
207 : : &ErrorBase::class_ops};
208 : :
209 : : // We need to shadow all fields of GError, to prevent calling the getter from
210 : : // GBoxed (which would trash memory accessing the instance private data)
211 : : JSPropertySpec ErrorBase::proto_properties[] = {
212 : : JS_PSG("domain", &ErrorBase::get_domain, GJS_MODULE_PROP_FLAGS),
213 : : JS_PSG("code", &ErrorBase::get_code, GJS_MODULE_PROP_FLAGS),
214 : : JS_PSG("message", &ErrorBase::get_message, GJS_MODULE_PROP_FLAGS),
215 : : JS_PS_END};
216 : :
217 : : JSFunctionSpec ErrorBase::static_methods[] = {
218 : : JS_FN("valueOf", &ErrorBase::value_of, 0, GJS_MODULE_PROP_FLAGS),
219 : : JS_FS_END};
220 : :
221 : : // Overrides GIWrapperPrototype::get_parent_proto().
222 : 14 : bool ErrorPrototype::get_parent_proto(JSContext* cx,
223 : : JS::MutableHandleObject proto) const {
224 : 14 : GI::Repository repo{};
225 : 14 : repo.require("GLib", "2.0").unwrap();
226 : :
227 : : GI::AutoBaseInfo glib_error_info{
228 : 14 : repo.find_by_name("GLib", "Error").value()};
229 : :
230 : 14 : proto.set(gjs_lookup_generic_prototype(cx, glib_error_info));
231 : 14 : return !!proto;
232 : 14 : }
233 : :
234 : 14 : bool ErrorPrototype::define_class(JSContext* cx, JS::HandleObject in_object,
235 : : const GI::EnumInfo info) {
236 : 14 : JS::RootedObject prototype{cx}, constructor{cx};
237 [ - + ]: 14 : if (!ErrorPrototype::create_class(cx, in_object, info, G_TYPE_ERROR,
238 : 14 : &constructor, &prototype))
239 : 0 : return false;
240 : :
241 : : // Define a toString() on the prototype, as it does not exist on the
242 : : // prototype of GLib.Error; and create_class() will not define it since we
243 : : // supply a parent in get_parent_proto().
244 : 14 : const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
245 : 14 : return JS_DefineFunctionById(cx, prototype, atoms.to_string(),
246 : : &ErrorBase::to_string, 0,
247 [ + - + - ]: 28 : GJS_MODULE_PROP_FLAGS) &&
248 : 28 : gjs_define_enum_values(cx, constructor, info);
249 : 14 : }
250 : :
251 : : [[nodiscard]]
252 : 53 : static Maybe<const GI::EnumInfo> find_error_domain_info(
253 : : const GI::Repository& repo, GQuark domain) {
254 : : // first an attempt without loading extra libraries
255 : 53 : Maybe<GI::AutoEnumInfo> info = repo.find_by_error_domain(domain);
256 [ + + ]: 53 : if (info)
257 : 44 : return info;
258 : :
259 : : // load standard stuff
260 : 9 : repo.require("GLib", "2.0").unwrap();
261 : 9 : repo.require("GObject", "2.0").unwrap();
262 : 9 : repo.require("Gio", "2.0").unwrap();
263 : 9 : info = repo.find_by_error_domain(domain);
264 [ - + ]: 9 : if (info)
265 : 0 : return info;
266 : :
267 : : // last attempt: load GIRepository (for invoke errors, rarely needed)
268 : 9 : repo.require("GIRepository", "3.0").unwrap();
269 : 9 : return repo.find_by_error_domain(domain);
270 : 53 : }
271 : :
272 : : // define properties that JS Error exposes, such as fileName, lineNumber and
273 : : // stack
274 : : GJS_JSAPI_RETURN_CONVENTION
275 : 73 : bool gjs_define_error_properties(JSContext* cx, JS::HandleObject obj) {
276 : 73 : JS::RootedObject frame(cx);
277 : 73 : JS::RootedString stack(cx);
278 : 73 : JS::RootedString source(cx);
279 : : uint32_t line;
280 : 73 : JS::TaggedColumnNumberOneOrigin tagged_column;
281 : :
282 [ + - - + ]: 219 : if (!JS::CaptureCurrentStack(cx, &frame) ||
283 [ - + + - ]: 146 : !JS::BuildStackString(cx, nullptr, frame, &stack))
284 : 0 : return false;
285 : :
286 : 73 : auto ok = JS::SavedFrameResult::Ok;
287 [ + - ]: 146 : if (JS::GetSavedFrameSource(cx, nullptr, frame, &source) != ok ||
288 [ + - - + ]: 146 : JS::GetSavedFrameLine(cx, nullptr, frame, &line) != ok ||
289 [ - + ]: 146 : JS::GetSavedFrameColumn(cx, nullptr, frame, &tagged_column) != ok) {
290 : 0 : gjs_throw(cx, "Error getting saved frame information");
291 : 0 : return false;
292 : : }
293 : :
294 : 73 : const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
295 : 73 : return JS_DefinePropertyById(cx, obj, atoms.stack(), stack,
296 [ + - ]: 73 : JSPROP_ENUMERATE) &&
297 : 73 : JS_DefinePropertyById(cx, obj, atoms.file_name(), source,
298 [ + - ]: 73 : JSPROP_ENUMERATE) &&
299 : 73 : JS_DefinePropertyById(cx, obj, atoms.line_number(), line,
300 [ + - + - ]: 146 : JSPROP_ENUMERATE) &&
301 : 73 : JS_DefinePropertyById(cx, obj, atoms.column_number(),
302 : : tagged_column.oneOriginValue(),
303 : 73 : JSPROP_ENUMERATE);
304 : 73 : }
305 : :
306 : : [[nodiscard]]
307 : 13 : static JSProtoKey proto_key_from_error_enum(int val) {
308 [ - - - + : 13 : switch (val) {
- + - + ]
309 : 0 : case GJS_JS_ERROR_EVAL_ERROR:
310 : 0 : return JSProto_EvalError;
311 : 0 : case GJS_JS_ERROR_INTERNAL_ERROR:
312 : 0 : return JSProto_InternalError;
313 : 0 : case GJS_JS_ERROR_RANGE_ERROR:
314 : 0 : return JSProto_RangeError;
315 : 1 : case GJS_JS_ERROR_REFERENCE_ERROR:
316 : 1 : return JSProto_ReferenceError;
317 : 0 : case GJS_JS_ERROR_SYNTAX_ERROR:
318 : 0 : return JSProto_SyntaxError;
319 : 2 : case GJS_JS_ERROR_TYPE_ERROR:
320 : 2 : return JSProto_TypeError;
321 : 0 : case GJS_JS_ERROR_URI_ERROR:
322 : 0 : return JSProto_URIError;
323 : 10 : case GJS_JS_ERROR_ERROR:
324 : : default:
325 : 10 : return JSProto_Error;
326 : : }
327 : : }
328 : :
329 : : GJS_JSAPI_RETURN_CONVENTION
330 : 13 : static JSObject* gjs_error_from_js_gerror(JSContext* cx, GError* gerror) {
331 : 13 : JS::RootedValueArray<1> error_args(cx);
332 [ - + ]: 13 : if (!gjs_string_from_utf8(cx, gerror->message, error_args[0]))
333 : 0 : return nullptr;
334 : :
335 : 13 : JSProtoKey error_kind = proto_key_from_error_enum(gerror->code);
336 : 13 : JS::RootedObject error_constructor(cx);
337 [ - + ]: 13 : if (!JS_GetClassObject(cx, error_kind, &error_constructor))
338 : 0 : return nullptr;
339 : :
340 : : JS::RootedValue v_error_constructor(cx,
341 : 13 : JS::ObjectValue(*error_constructor));
342 : 13 : JS::RootedObject error(cx);
343 [ - + ]: 13 : if (!JS::Construct(cx, v_error_constructor, error_args, &error))
344 : 0 : return nullptr;
345 : :
346 : 13 : return error;
347 : 13 : }
348 : :
349 : 66 : JSObject* ErrorInstance::object_for_c_ptr(JSContext* cx, GError* gerror) {
350 [ - + ]: 66 : if (!gerror)
351 : 0 : return nullptr;
352 : :
353 [ + + ]: 66 : if (gerror->domain == GJS_JS_ERROR)
354 : 13 : return gjs_error_from_js_gerror(cx, gerror);
355 : :
356 : 53 : GI::Repository repo;
357 : 53 : Maybe<GI::AutoEnumInfo> info = find_error_domain_info(repo, gerror->domain);
358 : :
359 [ + + ]: 53 : if (!info) {
360 : : // We don't have error domain metadata. Marshal the error as a plain
361 : : // GError
362 : : GI::AutoStructInfo glib_boxed{
363 : 9 : repo.find_by_name<GI::InfoTag::STRUCT>("GLib", "Error").value()};
364 : 9 : return StructInstance::new_for_c_struct(cx, glib_boxed, gerror);
365 : 9 : }
366 : :
367 : : gjs_debug_marshal(GJS_DEBUG_GBOXED, "Wrapping struct %s with JSObject",
368 : : info->name());
369 : :
370 : 44 : JS::RootedObject obj{cx, gjs_new_object_with_generic_prototype(cx, *info)};
371 [ - + ]: 44 : if (!obj)
372 : 0 : return nullptr;
373 : :
374 : 44 : ErrorInstance* priv = ErrorInstance::new_for_js_object(cx, obj);
375 : 44 : priv->copy_gerror(gerror);
376 : :
377 : 44 : return obj;
378 : 53 : }
379 : :
380 : 16 : GError* ErrorBase::to_c_ptr(JSContext* cx, JS::HandleObject obj) {
381 : : // If this is a plain GBoxed (i.e. a GError without metadata), delegate
382 : : // marshalling.
383 [ + + ]: 16 : if (StructBase::typecheck(cx, obj, G_TYPE_ERROR, GjsTypecheckNoThrow{}))
384 : 2 : return StructBase::to_c_ptr<GError>(cx, obj);
385 : :
386 : 14 : return GIWrapperBase::to_c_ptr<GError>(cx, obj);
387 : : }
388 : :
389 : 11 : bool ErrorBase::transfer_to_gi_argument(JSContext* cx, JS::HandleObject obj,
390 : : GIArgument* arg,
391 : : GIDirection transfer_direction,
392 : : GITransfer transfer_ownership) {
393 : 11 : g_assert(transfer_direction != GI_DIRECTION_INOUT &&
394 : : "transfer_to_gi_argument() must choose between in or out");
395 : :
396 [ - + ]: 11 : if (!ErrorBase::typecheck(cx, obj)) {
397 : 0 : gjs_arg_unset(arg);
398 : 0 : return false;
399 : : }
400 : :
401 : 11 : gjs_arg_set(arg, ErrorBase::to_c_ptr(cx, obj));
402 [ - + ]: 11 : if (!gjs_arg_get<void*>(arg))
403 : 0 : return false;
404 : :
405 [ + - + + ]: 11 : if ((transfer_direction == GI_DIRECTION_IN &&
406 [ - + ]: 10 : transfer_ownership != GI_TRANSFER_NOTHING) ||
407 [ # # ]: 0 : (transfer_direction == GI_DIRECTION_OUT &&
408 : : transfer_ownership == GI_TRANSFER_EVERYTHING)) {
409 : 1 : gjs_arg_set(arg, ErrorInstance::copy_ptr(cx, G_TYPE_ERROR,
410 : : gjs_arg_get<void*>(arg)));
411 [ - + ]: 1 : if (!gjs_arg_get<void*>(arg))
412 : 0 : return false;
413 : : }
414 : :
415 : 11 : return true;
416 : : }
417 : :
418 : : // Overrides GIWrapperBase::typecheck()
419 : 11 : bool ErrorBase::typecheck(JSContext* cx, JS::HandleObject obj) {
420 [ + + ]: 11 : if (StructBase::typecheck(cx, obj, G_TYPE_ERROR, GjsTypecheckNoThrow{}))
421 : 2 : return true;
422 : 9 : return GIWrapperBase::typecheck(cx, obj, G_TYPE_ERROR);
423 : : }
424 : :
425 : 9 : bool ErrorBase::typecheck(JSContext* cx, JS::HandleObject obj,
426 : : GjsTypecheckNoThrow no_throw) {
427 [ - + ]: 9 : if (StructBase::typecheck(cx, obj, G_TYPE_ERROR, no_throw))
428 : 0 : return true;
429 : 9 : return GIWrapperBase::typecheck(cx, obj, G_TYPE_ERROR, no_throw);
430 : : }
431 : :
432 : : GJS_JSAPI_RETURN_CONVENTION
433 : 9 : static GError* gerror_from_error_impl(JSContext* cx, JS::HandleObject obj) {
434 [ + + ]: 9 : if (ErrorBase::typecheck(cx, obj, GjsTypecheckNoThrow())) {
435 : : // This is already a GError, just copy it
436 : 2 : GError* inner = ErrorBase::to_c_ptr(cx, obj);
437 [ - + ]: 2 : if (!inner)
438 : 0 : return nullptr;
439 : 2 : return g_error_copy(inner);
440 : : }
441 : :
442 : : // Try to make something useful from the error name and message (in case
443 : : // this is a JS error)
444 : 7 : const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
445 : 7 : JS::RootedValue v_name(cx);
446 [ - + ]: 7 : if (!JS_GetPropertyById(cx, obj, atoms.name(), &v_name))
447 : 0 : return nullptr;
448 : :
449 : 7 : JS::RootedValue v_message(cx);
450 [ - + ]: 7 : if (!JS_GetPropertyById(cx, obj, atoms.message(), &v_message))
451 : 0 : return nullptr;
452 : :
453 [ + + + + : 7 : if (!v_name.isString() || !v_message.isString()) {
+ + ]
454 : 3 : return g_error_new_literal(
455 : : GJS_JS_ERROR, GJS_JS_ERROR_ERROR,
456 : 3 : "Object thrown with unexpected name or message property");
457 : : }
458 : :
459 : 4 : JS::UniqueChars name = gjs_string_to_utf8(cx, v_name);
460 [ - + ]: 4 : if (!name)
461 : 0 : return nullptr;
462 : :
463 : 4 : JS::UniqueChars message = gjs_string_to_utf8(cx, v_message);
464 [ - + ]: 4 : if (!message)
465 : 0 : return nullptr;
466 : :
467 : 4 : Gjs::AutoTypeClass<GEnumClass> klass{GJS_TYPE_JS_ERROR};
468 : 4 : const GEnumValue* value = g_enum_get_value_by_name(klass, name.get());
469 : : int code;
470 [ + - ]: 4 : if (value)
471 : 4 : code = value->value;
472 : : else
473 : 0 : code = GJS_JS_ERROR_ERROR;
474 : :
475 : 4 : return g_error_new_literal(GJS_JS_ERROR, code, message.get());
476 : 7 : }
477 : :
478 : : /**
479 : : * gjs_gerror_make_from_thrown_value:
480 : : *
481 : : * Attempts to convert a JavaScript thrown value (pending on @cx) into a
482 : : * #GError. This function is infallible and will always return a #GError with
483 : : * some message, even if the exception value couldn't be converted.
484 : : *
485 : : * Clears the pending exception on @cx.
486 : : *
487 : : * Returns: (transfer full): a new #GError
488 : : */
489 : 16 : GError* gjs_gerror_make_from_thrown_value(JSContext* cx) {
490 : 16 : g_assert(JS_IsExceptionPending(cx) &&
491 : : "Should be called when an exception is pending");
492 : :
493 : 16 : JS::RootedValue exc(cx);
494 : 16 : JS_GetPendingException(cx, &exc);
495 : 16 : JS_ClearPendingException(cx); // don't log
496 : :
497 [ + + ]: 16 : if (!exc.isObject()) {
498 : 7 : return g_error_new(GJS_JS_ERROR, GJS_JS_ERROR_ERROR,
499 : : "Non-exception %s value %s thrown",
500 : : JS::InformalValueTypeName(exc),
501 : 14 : gjs_debug_value(exc).c_str());
502 : : }
503 : :
504 : 9 : JS::RootedObject obj(cx, &exc.toObject());
505 : 9 : GError* retval = gerror_from_error_impl(cx, obj);
506 [ + - ]: 9 : if (retval)
507 : 9 : return retval;
508 : :
509 : : // Make a GError with an InternalError even if it wasn't possible to convert
510 : : // the exception into one
511 : 0 : gjs_log_exception(cx); // log the inner exception
512 : 0 : return g_error_new_literal(GJS_JS_ERROR, GJS_JS_ERROR_INTERNAL_ERROR,
513 : 0 : "Failed to convert JS thrown value into GError");
514 : 16 : }
515 : :
516 : : /**
517 : : * gjs_throw_gerror:
518 : : *
519 : : * Converts a GError into a JavaScript exception. Differently from gjs_throw(),
520 : : * it will overwrite an existing exception, as it is used to report errors from
521 : : * C functions.
522 : : *
523 : : * Returns: false, for convenience in returning from the calling function.
524 : : */
525 : 49 : bool gjs_throw_gerror(JSContext* cx, Gjs::AutoError const& error) {
526 : : // return false even if the GError is null, as presumably something failed
527 : : // in the calling code, and the caller expects to throw.
528 : 49 : g_return_val_if_fail(error, false);
529 : :
530 : 49 : JS::RootedObject err_obj(cx, ErrorInstance::object_for_c_ptr(cx, error));
531 [ + - - + : 49 : if (!err_obj || !gjs_define_error_properties(cx, err_obj))
- + ]
532 : 0 : return false;
533 : :
534 : 49 : JS::RootedValue err(cx, JS::ObjectValue(*err_obj));
535 : 49 : JS_SetPendingException(cx, err);
536 : :
537 : 49 : return false;
538 : 49 : }
|