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 : : // SPDX-FileCopyrightText: 2012 Giovanni Campagna <scampa.giovanni@gmail.com>
5 : :
6 : : #include <config.h>
7 : :
8 : : #include <string.h> // for strlen
9 : :
10 : : #include <glib.h>
11 : :
12 : : #include <js/CallAndConstruct.h>
13 : : #include <js/CallArgs.h> // for JSNative
14 : : #include <js/Class.h>
15 : : #include <js/ComparisonOperators.h>
16 : : #include <js/ErrorReport.h> // for JSEXN_TYPEERR
17 : : #include <js/Object.h> // for GetClass
18 : : #include <js/PropertyAndElement.h> // for JS_DefineFunctions, JS_DefinePro...
19 : : #include <js/Realm.h> // for GetRealmObjectPrototype
20 : : #include <js/RootingAPI.h>
21 : : #include <js/TypeDecls.h>
22 : : #include <js/Value.h>
23 : : #include <jsapi.h> // for JS_GetFunctionObject, JS_GetPrototype
24 : : #include <jsfriendapi.h> // for GetFunctionNativeReserved, NewFun...
25 : :
26 : : #include "gjs/atoms.h"
27 : : #include "gjs/auto.h"
28 : : #include "gjs/context-private.h"
29 : : #include "gjs/jsapi-util.h"
30 : : #include "gjs/macros.h"
31 : :
32 : : struct JSFunctionSpec;
33 : : struct JSPropertySpec;
34 : : namespace JS {
35 : : class HandleValueArray;
36 : : }
37 : :
38 : : /* Reserved slots of JSNative accessor wrappers */
39 : : enum {
40 : : DYNAMIC_PROPERTY_PRIVATE_SLOT,
41 : : };
42 : :
43 : 1752 : bool gjs_init_class_dynamic(JSContext* context, JS::HandleObject in_object,
44 : : JS::HandleObject parent_proto, const char* ns_name,
45 : : const char* class_name, const JSClass* clasp,
46 : : JSNative constructor_native, unsigned nargs,
47 : : JSPropertySpec* proto_ps, JSFunctionSpec* proto_fs,
48 : : JSPropertySpec* static_ps,
49 : : JSFunctionSpec* static_fs,
50 : : JS::MutableHandleObject prototype,
51 : : JS::MutableHandleObject constructor) {
52 : : /* Without a name, JS_NewObject fails */
53 : 1752 : g_assert (clasp->name != NULL);
54 : :
55 : : /* gjs_init_class_dynamic only makes sense for instantiable classes,
56 : : use JS_InitClass for static classes like Math */
57 : 1752 : g_assert (constructor_native != NULL);
58 : :
59 : : /* Class initialization consists of five parts:
60 : : - building a prototype
61 : : - defining prototype properties and functions
62 : : - building a constructor and defining it on the right object
63 : : - defining constructor properties and functions
64 : : - linking the constructor and the prototype, so that
65 : : JS_NewObjectForConstructor can find it
66 : : */
67 : :
68 [ + + ]: 1752 : if (parent_proto) {
69 : 573 : prototype.set(JS_NewObjectWithGivenProto(context, clasp, parent_proto));
70 : : } else {
71 : : /* JS_NewObject will use Object.prototype as the prototype if the
72 : : * clasp's constructor is not a built-in class.
73 : : */
74 : 1179 : prototype.set(JS_NewObject(context, clasp));
75 : : }
76 [ - + ]: 1752 : if (!prototype)
77 : 0 : return false;
78 : :
79 [ + + - + : 1752 : if (proto_ps && !JS_DefineProperties(context, prototype, proto_ps))
- + ]
80 : 0 : return false;
81 [ + + - + : 1752 : if (proto_fs && !JS_DefineFunctions(context, prototype, proto_fs))
- + ]
82 : 0 : return false;
83 : :
84 : : Gjs::AutoChar full_function_name{
85 : 1752 : g_strdup_printf("%s_%s", ns_name, class_name)};
86 : : JSFunction* constructor_fun =
87 : 1752 : JS_NewFunction(context, constructor_native, nargs, JSFUN_CONSTRUCTOR,
88 : : full_function_name);
89 [ - + ]: 1752 : if (!constructor_fun)
90 : 0 : return false;
91 : :
92 : 1752 : constructor.set(JS_GetFunctionObject(constructor_fun));
93 : :
94 [ - + - - : 1752 : if (static_ps && !JS_DefineProperties(context, constructor, static_ps))
- + ]
95 : 0 : return false;
96 [ + + - + : 1752 : if (static_fs && !JS_DefineFunctions(context, constructor, static_fs))
- + ]
97 : 0 : return false;
98 : :
99 [ - + ]: 1752 : if (!JS_LinkConstructorAndPrototype(context, constructor, prototype))
100 : 0 : return false;
101 : :
102 : : /* The constructor defined by JS_InitClass has no property attributes, but this
103 : : is a more useful default for gjs */
104 : 3504 : return JS_DefineProperty(context, in_object, class_name, constructor,
105 : 1752 : GJS_MODULE_PROP_FLAGS);
106 : 1752 : }
107 : :
108 : 0 : [[nodiscard]] static const char* format_dynamic_class_name(const char* name) {
109 [ # # # # : 0 : if (g_str_has_prefix(name, "_private_"))
# # ]
110 : 0 : return name + strlen("_private_");
111 : : else
112 : 0 : return name;
113 : : }
114 : :
115 : : bool
116 : 350 : gjs_typecheck_instance(JSContext *context,
117 : : JS::HandleObject obj,
118 : : const JSClass *static_clasp,
119 : : bool throw_error)
120 : : {
121 [ - + ]: 350 : if (!JS_InstanceOf(context, obj, static_clasp, NULL)) {
122 [ # # ]: 0 : if (throw_error) {
123 : 0 : const JSClass* obj_class = JS::GetClass(obj);
124 : :
125 : 0 : gjs_throw_custom(context, JSEXN_TYPEERR, nullptr,
126 : : "Object %p is not a subclass of %s, it's a %s",
127 : 0 : obj.get(), static_clasp->name,
128 : 0 : format_dynamic_class_name(obj_class->name));
129 : : }
130 : :
131 : 0 : return false;
132 : : }
133 : :
134 : 350 : return true;
135 : : }
136 : :
137 : : JSObject*
138 : 3 : gjs_construct_object_dynamic(JSContext *context,
139 : : JS::HandleObject proto,
140 : : const JS::HandleValueArray& args)
141 : : {
142 : 3 : const GjsAtoms& atoms = GjsContextPrivate::atoms(context);
143 : 3 : JS::RootedObject constructor(context);
144 : :
145 [ - + ]: 3 : if (!gjs_object_require_property(context, proto, "prototype",
146 : : atoms.constructor(), &constructor))
147 : 0 : return NULL;
148 : :
149 : 3 : JS::RootedValue v_constructor(context, JS::ObjectValue(*constructor));
150 : 3 : JS::RootedObject object(context);
151 [ - + ]: 3 : if (!JS::Construct(context, v_constructor, args, &object))
152 : 0 : return nullptr;
153 : :
154 : 3 : return object;
155 : 3 : }
156 : :
157 : : GJS_JSAPI_RETURN_CONVENTION
158 : : static JSObject *
159 : 4544 : define_native_accessor_wrapper(JSContext *cx,
160 : : JSNative call,
161 : : unsigned nargs,
162 : : const char *func_name,
163 : : JS::HandleValue private_slot)
164 : : {
165 : 4544 : JSFunction *func = js::NewFunctionWithReserved(cx, call, nargs, 0, func_name);
166 [ - + ]: 4544 : if (!func)
167 : 0 : return nullptr;
168 : :
169 : 4544 : JSObject *func_obj = JS_GetFunctionObject(func);
170 : 4544 : js::SetFunctionNativeReserved(func_obj, DYNAMIC_PROPERTY_PRIVATE_SLOT,
171 : : private_slot);
172 : 4544 : return func_obj;
173 : : }
174 : :
175 : : /**
176 : : * gjs_define_property_dynamic:
177 : : * @cx: the #JSContext
178 : : * @proto: the prototype of the object, on which to define the property
179 : : * @prop_name: name of the property or field in GObject, visible to JS code
180 : : * @func_namespace: string from which the internal names for the getter and
181 : : * setter functions are built, not visible to JS code
182 : : * @getter: getter function
183 : : * @setter: setter function
184 : : * @private_slot: private data in the form of a #JS::Value that the getter and
185 : : * setter will have access to
186 : : * @flags: additional flags to define the property with (other than the ones
187 : : * required for a property with native getter/setter)
188 : : *
189 : : * When defining properties in a GBoxed or GObject, we can't have a separate
190 : : * getter and setter for each one, since the properties are defined dynamically.
191 : : * Therefore we must have one getter and setter for all the properties we define
192 : : * on all the types. In order to have that, we must provide the getter and
193 : : * setter with private data, e.g. the field index for GBoxed, in a "reserved
194 : : * slot" for which we must unfortunately use the jsfriendapi.
195 : : *
196 : : * Returns: %true on success, %false if an exception is pending on @cx.
197 : : */
198 : 2272 : bool gjs_define_property_dynamic(JSContext* cx, JS::HandleObject proto,
199 : : const char* prop_name, JS::HandleId id,
200 : : const char* func_namespace, JSNative getter,
201 : : JS::HandleValue getter_slot, JSNative setter,
202 : : JS::HandleValue setter_slot, unsigned flags) {
203 : : Gjs::AutoChar getter_name{
204 : 2272 : g_strconcat(func_namespace, "_get::", prop_name, nullptr)};
205 : : Gjs::AutoChar setter_name{
206 : 2272 : g_strconcat(func_namespace, "_set::", prop_name, nullptr)};
207 : :
208 : : JS::RootedObject getter_obj(
209 : 2272 : cx, define_native_accessor_wrapper(cx, getter, 0, getter_name,
210 : 2272 : getter_slot));
211 [ - + ]: 2272 : if (!getter_obj)
212 : 0 : return false;
213 : :
214 : : JS::RootedObject setter_obj(
215 : 2272 : cx, define_native_accessor_wrapper(cx, setter, 1, setter_name,
216 : 2272 : setter_slot));
217 [ - + ]: 2272 : if (!setter_obj)
218 : 0 : return false;
219 : :
220 [ - + ]: 2272 : if (id.isVoid()) {
221 : 0 : return JS_DefineProperty(cx, proto, prop_name, getter_obj, setter_obj,
222 : 0 : flags);
223 : : }
224 : :
225 : 2272 : return JS_DefinePropertyById(cx, proto, id, getter_obj, setter_obj, flags);
226 : 2272 : }
227 : :
228 : : /**
229 : : * gjs_dynamic_property_private_slot:
230 : : * @accessor_obj: the getter or setter as a function object, i.e.
231 : : * `&args.callee()` in the #JSNative function
232 : : *
233 : : * For use in dynamic property getters and setters (see
234 : : * gjs_define_property_dynamic()) to retrieve the private data passed there.
235 : : *
236 : : * Returns: the JS::Value that was passed to gjs_define_property_dynamic().
237 : : */
238 : : JS::Value
239 : 3085 : gjs_dynamic_property_private_slot(JSObject *accessor_obj)
240 : : {
241 : 3085 : return js::GetFunctionNativeReserved(accessor_obj,
242 : 3085 : DYNAMIC_PROPERTY_PRIVATE_SLOT);
243 : : }
244 : :
245 : : /**
246 : : * gjs_object_in_prototype_chain:
247 : : * @cx:
248 : : * @proto: The prototype which we are checking if @check_obj has in its chain
249 : : * @check_obj: The object to check
250 : : * @is_in_chain: (out): Whether @check_obj has @proto in its prototype chain
251 : : *
252 : : * Similar to JS_HasInstance() but takes into account abstract classes defined
253 : : * with JS_InitClass(), which JS_HasInstance() does not. Abstract classes don't
254 : : * have constructors, and JS_HasInstance() requires a constructor.
255 : : *
256 : : * Returns: false if an exception was thrown, true otherwise.
257 : : */
258 : 88 : bool gjs_object_in_prototype_chain(JSContext* cx, JS::HandleObject proto,
259 : : JS::HandleObject check_obj,
260 : : bool* is_in_chain) {
261 : 88 : JS::RootedObject object_prototype(cx, JS::GetRealmObjectPrototype(cx));
262 [ - + ]: 88 : if (!object_prototype)
263 : 0 : return false;
264 : :
265 : 88 : JS::RootedObject proto_iter(cx);
266 [ - + ]: 88 : if (!JS_GetPrototype(cx, check_obj, &proto_iter))
267 : 0 : return false;
268 [ + + ]: 178 : while (proto_iter != object_prototype) {
269 [ + + ]: 174 : if (proto_iter == proto) {
270 : 84 : *is_in_chain = true;
271 : 84 : return true;
272 : : }
273 [ - + ]: 90 : if (!JS_GetPrototype(cx, proto_iter, &proto_iter))
274 : 0 : return false;
275 : : }
276 : 4 : *is_in_chain = false;
277 : 4 : return true;
278 : 88 : }
|