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