Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 : * vim: set ts=4 sw=4 et tw=80:
3 : *
4 : * This Source Code Form is subject to the terms of the Mozilla Public
5 : * License, v. 2.0. If a copy of the MPL was not distributed with this
6 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 :
8 : #include "WrapperOwner.h"
9 : #include "JavaScriptLogging.h"
10 : #include "mozilla/Unused.h"
11 : #include "mozilla/dom/BindingUtils.h"
12 : #include "jsfriendapi.h"
13 : #include "js/CharacterEncoding.h"
14 : #include "xpcprivate.h"
15 : #include "CPOWTimer.h"
16 : #include "WrapperFactory.h"
17 :
18 : #include "nsIDocShellTreeItem.h"
19 : #include "nsIDocument.h"
20 :
21 : using namespace js;
22 : using namespace JS;
23 : using namespace mozilla;
24 : using namespace mozilla::jsipc;
25 :
26 0 : struct AuxCPOWData
27 : {
28 : ObjectId id;
29 : bool isCallable;
30 : bool isConstructor;
31 : bool isDOMObject;
32 :
33 : // The object tag is just some auxilliary information that clients can use
34 : // however they see fit.
35 : nsCString objectTag;
36 :
37 : // The class name for WrapperOwner::className, below.
38 : nsCString className;
39 :
40 0 : AuxCPOWData(ObjectId id,
41 : bool isCallable,
42 : bool isConstructor,
43 : bool isDOMObject,
44 : const nsACString& objectTag)
45 0 : : id(id),
46 : isCallable(isCallable),
47 : isConstructor(isConstructor),
48 : isDOMObject(isDOMObject),
49 0 : objectTag(objectTag)
50 0 : {}
51 : };
52 :
53 0 : WrapperOwner::WrapperOwner()
54 0 : : inactive_(false)
55 : {
56 0 : }
57 :
58 : static inline AuxCPOWData*
59 0 : AuxCPOWDataOf(JSObject* obj)
60 : {
61 0 : MOZ_ASSERT(IsCPOW(obj));
62 0 : return static_cast<AuxCPOWData*>(GetProxyReservedSlot(obj, 1).toPrivate());
63 : }
64 :
65 : static inline WrapperOwner*
66 0 : OwnerOf(JSObject* obj)
67 : {
68 0 : MOZ_ASSERT(IsCPOW(obj));
69 0 : return reinterpret_cast<WrapperOwner*>(GetProxyReservedSlot(obj, 0).toPrivate());
70 : }
71 :
72 : ObjectId
73 0 : WrapperOwner::idOfUnchecked(JSObject* obj)
74 : {
75 0 : MOZ_ASSERT(IsCPOW(obj));
76 :
77 0 : AuxCPOWData* aux = AuxCPOWDataOf(obj);
78 0 : MOZ_ASSERT(!aux->id.isNull());
79 0 : return aux->id;
80 : }
81 :
82 : ObjectId
83 0 : WrapperOwner::idOf(JSObject* obj)
84 : {
85 0 : ObjectId objId = idOfUnchecked(obj);
86 0 : MOZ_ASSERT(hasCPOW(objId, obj));
87 0 : return objId;
88 : }
89 :
90 : class CPOWProxyHandler : public BaseProxyHandler
91 : {
92 : public:
93 : constexpr CPOWProxyHandler()
94 : : BaseProxyHandler(&family) {}
95 :
96 0 : virtual bool finalizeInBackground(const Value& priv) const override {
97 0 : return false;
98 : }
99 :
100 : virtual bool getOwnPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
101 : MutableHandle<PropertyDescriptor> desc) const override;
102 : virtual bool defineProperty(JSContext* cx, HandleObject proxy, HandleId id,
103 : Handle<PropertyDescriptor> desc,
104 : ObjectOpResult& result) const override;
105 : virtual bool ownPropertyKeys(JSContext* cx, HandleObject proxy,
106 : AutoIdVector& props) const override;
107 : virtual bool delete_(JSContext* cx, HandleObject proxy, HandleId id,
108 : ObjectOpResult& result) const override;
109 : virtual JSObject* enumerate(JSContext* cx, HandleObject proxy) const override;
110 : virtual bool preventExtensions(JSContext* cx, HandleObject proxy,
111 : ObjectOpResult& result) const override;
112 : virtual bool isExtensible(JSContext* cx, HandleObject proxy, bool* extensible) const override;
113 : virtual bool has(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const override;
114 : virtual bool get(JSContext* cx, HandleObject proxy, HandleValue receiver,
115 : HandleId id, MutableHandleValue vp) const override;
116 : virtual bool set(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::HandleValue v,
117 : JS::HandleValue receiver, JS::ObjectOpResult& result) const override;
118 : virtual bool call(JSContext* cx, HandleObject proxy, const CallArgs& args) const override;
119 : virtual bool construct(JSContext* cx, HandleObject proxy, const CallArgs& args) const override;
120 :
121 : virtual bool getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
122 : MutableHandle<PropertyDescriptor> desc) const override;
123 : virtual bool hasOwn(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const override;
124 : virtual bool getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject proxy,
125 : AutoIdVector& props) const override;
126 : virtual bool hasInstance(JSContext* cx, HandleObject proxy,
127 : MutableHandleValue v, bool* bp) const override;
128 : virtual bool getBuiltinClass(JSContext* cx, HandleObject obj, js::ESClass* cls) const override;
129 : virtual bool isArray(JSContext* cx, HandleObject obj,
130 : IsArrayAnswer* answer) const override;
131 : virtual const char* className(JSContext* cx, HandleObject proxy) const override;
132 : virtual RegExpShared* regexp_toShared(JSContext* cx, HandleObject proxy) const override;
133 : virtual void finalize(JSFreeOp* fop, JSObject* proxy) const override;
134 : virtual size_t objectMoved(JSObject* proxy, JSObject* old) const override;
135 : virtual bool isCallable(JSObject* obj) const override;
136 : virtual bool isConstructor(JSObject* obj) const override;
137 : virtual bool getPrototype(JSContext* cx, HandleObject proxy, MutableHandleObject protop) const override;
138 : virtual bool getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary,
139 : MutableHandleObject protop) const override;
140 :
141 : static const char family;
142 : static const CPOWProxyHandler singleton;
143 : };
144 :
145 : const char CPOWProxyHandler::family = 0;
146 : const CPOWProxyHandler CPOWProxyHandler::singleton;
147 :
148 : #define FORWARD(call, args, failRetVal) \
149 : AUTO_PROFILER_LABEL(__func__, JS); \
150 : WrapperOwner* owner = OwnerOf(proxy); \
151 : if (!owner->active()) { \
152 : JS_ReportErrorASCII(cx, "cannot use a CPOW whose process is gone"); \
153 : return failRetVal; \
154 : } \
155 : if (!owner->allowMessage(cx)) { \
156 : return failRetVal; \
157 : } \
158 : { \
159 : CPOWTimer timer(cx); \
160 : return owner->call args; \
161 : }
162 :
163 : bool
164 0 : CPOWProxyHandler::getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
165 : MutableHandle<PropertyDescriptor> desc) const
166 : {
167 0 : FORWARD(getPropertyDescriptor, (cx, proxy, id, desc), false);
168 : }
169 :
170 : bool
171 0 : WrapperOwner::getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
172 : MutableHandle<PropertyDescriptor> desc)
173 : {
174 0 : ObjectId objId = idOf(proxy);
175 :
176 0 : JSIDVariant idVar;
177 0 : if (!toJSIDVariant(cx, id, &idVar))
178 : return false;
179 :
180 0 : ReturnStatus status;
181 0 : PPropertyDescriptor result;
182 0 : if (!SendGetPropertyDescriptor(objId, idVar, &status, &result))
183 0 : return ipcfail(cx);
184 :
185 0 : LOG_STACK();
186 :
187 0 : if (!ok(cx, status))
188 : return false;
189 :
190 0 : return toDescriptor(cx, result, desc);
191 : }
192 :
193 : bool
194 0 : CPOWProxyHandler::getOwnPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
195 : MutableHandle<PropertyDescriptor> desc) const
196 : {
197 0 : FORWARD(getOwnPropertyDescriptor, (cx, proxy, id, desc), false);
198 : }
199 :
200 : bool
201 0 : WrapperOwner::getOwnPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
202 : MutableHandle<PropertyDescriptor> desc)
203 : {
204 0 : ObjectId objId = idOf(proxy);
205 :
206 0 : JSIDVariant idVar;
207 0 : if (!toJSIDVariant(cx, id, &idVar))
208 : return false;
209 :
210 0 : ReturnStatus status;
211 0 : PPropertyDescriptor result;
212 0 : if (!SendGetOwnPropertyDescriptor(objId, idVar, &status, &result))
213 0 : return ipcfail(cx);
214 :
215 0 : LOG_STACK();
216 :
217 0 : if (!ok(cx, status))
218 : return false;
219 :
220 0 : return toDescriptor(cx, result, desc);
221 : }
222 :
223 : bool
224 0 : CPOWProxyHandler::defineProperty(JSContext* cx, HandleObject proxy, HandleId id,
225 : Handle<PropertyDescriptor> desc,
226 : ObjectOpResult& result) const
227 : {
228 0 : FORWARD(defineProperty, (cx, proxy, id, desc, result), false);
229 : }
230 :
231 : bool
232 0 : WrapperOwner::defineProperty(JSContext* cx, HandleObject proxy, HandleId id,
233 : Handle<PropertyDescriptor> desc,
234 : ObjectOpResult& result)
235 : {
236 0 : ObjectId objId = idOf(proxy);
237 :
238 0 : JSIDVariant idVar;
239 0 : if (!toJSIDVariant(cx, id, &idVar))
240 : return false;
241 :
242 0 : PPropertyDescriptor descriptor;
243 0 : if (!fromDescriptor(cx, desc, &descriptor))
244 : return false;
245 :
246 0 : ReturnStatus status;
247 0 : if (!SendDefineProperty(objId, idVar, descriptor, &status))
248 0 : return ipcfail(cx);
249 :
250 0 : LOG_STACK();
251 :
252 0 : return ok(cx, status, result);
253 : }
254 :
255 : bool
256 0 : CPOWProxyHandler::ownPropertyKeys(JSContext* cx, HandleObject proxy,
257 : AutoIdVector& props) const
258 : {
259 0 : FORWARD(ownPropertyKeys, (cx, proxy, props), false);
260 : }
261 :
262 : bool
263 0 : WrapperOwner::ownPropertyKeys(JSContext* cx, HandleObject proxy, AutoIdVector& props)
264 : {
265 0 : return getPropertyKeys(cx, proxy, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, props);
266 : }
267 :
268 : bool
269 0 : CPOWProxyHandler::delete_(JSContext* cx, HandleObject proxy, HandleId id,
270 : ObjectOpResult& result) const
271 : {
272 0 : FORWARD(delete_, (cx, proxy, id, result), false);
273 : }
274 :
275 : bool
276 0 : WrapperOwner::delete_(JSContext* cx, HandleObject proxy, HandleId id, ObjectOpResult& result)
277 : {
278 0 : ObjectId objId = idOf(proxy);
279 :
280 0 : JSIDVariant idVar;
281 0 : if (!toJSIDVariant(cx, id, &idVar))
282 : return false;
283 :
284 0 : ReturnStatus status;
285 0 : if (!SendDelete(objId, idVar, &status))
286 0 : return ipcfail(cx);
287 :
288 0 : LOG_STACK();
289 :
290 0 : return ok(cx, status, result);
291 : }
292 :
293 : JSObject*
294 0 : CPOWProxyHandler::enumerate(JSContext* cx, HandleObject proxy) const
295 : {
296 : // Using a CPOW for the Iterator would slow down for .. in performance, instead
297 : // call the base hook, that will use our implementation of getOwnEnumerablePropertyKeys
298 : // and follow the proto chain.
299 0 : return BaseProxyHandler::enumerate(cx, proxy);
300 : }
301 :
302 : bool
303 0 : CPOWProxyHandler::has(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const
304 : {
305 0 : FORWARD(has, (cx, proxy, id, bp), false);
306 : }
307 :
308 : bool
309 0 : WrapperOwner::has(JSContext* cx, HandleObject proxy, HandleId id, bool* bp)
310 : {
311 0 : ObjectId objId = idOf(proxy);
312 :
313 0 : JSIDVariant idVar;
314 0 : if (!toJSIDVariant(cx, id, &idVar))
315 : return false;
316 :
317 0 : ReturnStatus status;
318 0 : if (!SendHas(objId, idVar, &status, bp))
319 0 : return ipcfail(cx);
320 :
321 0 : LOG_STACK();
322 :
323 0 : return ok(cx, status);
324 : }
325 :
326 : bool
327 0 : CPOWProxyHandler::hasOwn(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const
328 : {
329 0 : FORWARD(hasOwn, (cx, proxy, id, bp), false);
330 : }
331 :
332 : bool
333 0 : WrapperOwner::hasOwn(JSContext* cx, HandleObject proxy, HandleId id, bool* bp)
334 : {
335 0 : ObjectId objId = idOf(proxy);
336 :
337 0 : JSIDVariant idVar;
338 0 : if (!toJSIDVariant(cx, id, &idVar))
339 : return false;
340 :
341 0 : ReturnStatus status;
342 0 : if (!SendHasOwn(objId, idVar, &status, bp))
343 0 : return ipcfail(cx);
344 :
345 0 : LOG_STACK();
346 :
347 0 : return !!ok(cx, status);
348 : }
349 :
350 : bool
351 0 : CPOWProxyHandler::get(JSContext* cx, HandleObject proxy, HandleValue receiver,
352 : HandleId id, MutableHandleValue vp) const
353 : {
354 0 : FORWARD(get, (cx, proxy, receiver, id, vp), false);
355 : }
356 :
357 : static bool
358 0 : CPOWDOMQI(JSContext* cx, unsigned argc, Value* vp)
359 : {
360 0 : CallArgs args = CallArgsFromVp(argc, vp);
361 0 : if (!args.thisv().isObject() || !IsCPOW(&args.thisv().toObject())) {
362 0 : JS_ReportErrorASCII(cx, "bad this object passed to special QI");
363 0 : return false;
364 : }
365 :
366 0 : RootedObject proxy(cx, &args.thisv().toObject());
367 0 : FORWARD(DOMQI, (cx, proxy, args), false);
368 : }
369 :
370 : static bool
371 0 : CPOWToString(JSContext* cx, unsigned argc, Value* vp)
372 : {
373 0 : CallArgs args = CallArgsFromVp(argc, vp);
374 0 : RootedObject callee(cx, &args.callee());
375 0 : RootedValue cpowValue(cx);
376 0 : if (!JS_GetProperty(cx, callee, "__cpow__", &cpowValue))
377 : return false;
378 :
379 0 : if (!cpowValue.isObject() || !IsCPOW(&cpowValue.toObject())) {
380 0 : JS_ReportErrorASCII(cx, "CPOWToString called on an incompatible object");
381 0 : return false;
382 : }
383 :
384 0 : RootedObject proxy(cx, &cpowValue.toObject());
385 0 : FORWARD(toString, (cx, proxy, args), false);
386 : }
387 :
388 : bool
389 0 : WrapperOwner::toString(JSContext* cx, HandleObject cpow, JS::CallArgs& args)
390 : {
391 : // Ask the other side to call its toString method. Update the callee so that
392 : // it points to the CPOW and not to the synthesized CPOWToString function.
393 0 : args.setCallee(ObjectValue(*cpow));
394 0 : if (!callOrConstruct(cx, cpow, args, false))
395 : return false;
396 :
397 0 : if (!args.rval().isString())
398 : return true;
399 :
400 0 : RootedString cpowResult(cx, args.rval().toString());
401 0 : nsAutoJSString toStringResult;
402 0 : if (!toStringResult.init(cx, cpowResult))
403 : return false;
404 :
405 : // We don't want to wrap toString() results for things like the location
406 : // object, where toString() is supposed to return a URL and nothing else.
407 0 : nsAutoString result;
408 0 : if (toStringResult[0] == '[') {
409 0 : result.AppendLiteral("[object CPOW ");
410 0 : result += toStringResult;
411 0 : result.AppendLiteral("]");
412 : } else {
413 : result += toStringResult;
414 : }
415 :
416 0 : JSString* str = JS_NewUCStringCopyN(cx, result.get(), result.Length());
417 0 : if (!str)
418 : return false;
419 :
420 0 : args.rval().setString(str);
421 0 : return true;
422 : }
423 :
424 : bool
425 0 : WrapperOwner::DOMQI(JSContext* cx, JS::HandleObject proxy, JS::CallArgs& args)
426 : {
427 : // Someone's calling us, handle nsISupports specially to avoid unnecessary
428 : // CPOW traffic.
429 0 : HandleValue id = args[0];
430 0 : if (id.isObject()) {
431 0 : RootedObject idobj(cx, &id.toObject());
432 0 : nsCOMPtr<nsIJSID> jsid;
433 :
434 0 : nsresult rv = UnwrapArg<nsIJSID>(cx, idobj, getter_AddRefs(jsid));
435 0 : if (NS_SUCCEEDED(rv)) {
436 0 : MOZ_ASSERT(jsid, "bad wrapJS");
437 0 : const nsID* idptr = jsid->GetID();
438 0 : if (idptr->Equals(NS_GET_IID(nsISupports))) {
439 0 : args.rval().set(args.thisv());
440 0 : return true;
441 : }
442 :
443 : // Webidl-implemented DOM objects never have nsIClassInfo.
444 0 : if (idptr->Equals(NS_GET_IID(nsIClassInfo)))
445 0 : return Throw(cx, NS_ERROR_NO_INTERFACE);
446 : }
447 : }
448 :
449 : // It wasn't nsISupports, call into the other process to do the QI for us
450 : // (since we don't know what other interfaces our object supports). Note
451 : // that we have to use JS_GetPropertyDescriptor here to avoid infinite
452 : // recursion back into CPOWDOMQI via WrapperOwner::get().
453 : // We could stash the actual QI function on our own function object to avoid
454 : // if we're called multiple times, but since we're transient, there's no
455 : // point right now.
456 0 : JS::Rooted<PropertyDescriptor> propDesc(cx);
457 0 : if (!JS_GetPropertyDescriptor(cx, proxy, "QueryInterface", &propDesc))
458 : return false;
459 :
460 0 : if (!propDesc.value().isObject()) {
461 0 : MOZ_ASSERT_UNREACHABLE("We didn't get QueryInterface off a node");
462 : return Throw(cx, NS_ERROR_UNEXPECTED);
463 : }
464 0 : return JS_CallFunctionValue(cx, proxy, propDesc.value(), args, args.rval());
465 : }
466 :
467 : bool
468 0 : WrapperOwner::get(JSContext* cx, HandleObject proxy, HandleValue receiver,
469 : HandleId id, MutableHandleValue vp)
470 : {
471 0 : ObjectId objId = idOf(proxy);
472 :
473 0 : JSVariant receiverVar;
474 0 : if (!toVariant(cx, receiver, &receiverVar))
475 : return false;
476 :
477 0 : JSIDVariant idVar;
478 0 : if (!toJSIDVariant(cx, id, &idVar))
479 : return false;
480 :
481 0 : AuxCPOWData* data = AuxCPOWDataOf(proxy);
482 0 : if (data->isDOMObject &&
483 0 : idVar.type() == JSIDVariant::TnsString &&
484 0 : idVar.get_nsString().EqualsLiteral("QueryInterface"))
485 : {
486 : // Handle QueryInterface on DOM Objects specially since we can assume
487 : // certain things about their implementation.
488 0 : RootedFunction qi(cx, JS_NewFunction(cx, CPOWDOMQI, 1, 0,
489 0 : "QueryInterface"));
490 0 : if (!qi)
491 : return false;
492 :
493 0 : vp.set(ObjectValue(*JS_GetFunctionObject(qi)));
494 0 : return true;
495 : }
496 :
497 0 : JSVariant val;
498 0 : ReturnStatus status;
499 0 : if (!SendGet(objId, receiverVar, idVar, &status, &val))
500 0 : return ipcfail(cx);
501 :
502 0 : LOG_STACK();
503 :
504 0 : if (!ok(cx, status))
505 : return false;
506 :
507 0 : if (!fromVariant(cx, val, vp))
508 : return false;
509 :
510 0 : if (idVar.type() == JSIDVariant::TnsString &&
511 0 : idVar.get_nsString().EqualsLiteral("toString")) {
512 0 : RootedFunction toString(cx, JS_NewFunction(cx, CPOWToString, 0, 0,
513 0 : "toString"));
514 0 : if (!toString)
515 0 : return false;
516 :
517 0 : RootedObject toStringObj(cx, JS_GetFunctionObject(toString));
518 :
519 0 : if (!JS_DefineProperty(cx, toStringObj, "__cpow__", vp, JSPROP_PERMANENT | JSPROP_READONLY))
520 0 : return false;
521 :
522 0 : vp.set(ObjectValue(*toStringObj));
523 : }
524 :
525 : return true;
526 : }
527 :
528 : bool
529 0 : CPOWProxyHandler::set(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::HandleValue v,
530 : JS::HandleValue receiver, JS::ObjectOpResult& result) const
531 : {
532 0 : FORWARD(set, (cx, proxy, id, v, receiver, result), false);
533 : }
534 :
535 : bool
536 0 : WrapperOwner::set(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::HandleValue v,
537 : JS::HandleValue receiver, JS::ObjectOpResult& result)
538 : {
539 0 : ObjectId objId = idOf(proxy);
540 :
541 0 : JSIDVariant idVar;
542 0 : if (!toJSIDVariant(cx, id, &idVar))
543 : return false;
544 :
545 0 : JSVariant val;
546 0 : if (!toVariant(cx, v, &val))
547 : return false;
548 :
549 0 : JSVariant receiverVar;
550 0 : if (!toVariant(cx, receiver, &receiverVar))
551 : return false;
552 :
553 0 : ReturnStatus status;
554 0 : if (!SendSet(objId, idVar, val, receiverVar, &status))
555 0 : return ipcfail(cx);
556 :
557 0 : LOG_STACK();
558 :
559 0 : return ok(cx, status, result);
560 : }
561 :
562 : bool
563 0 : CPOWProxyHandler::getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject proxy,
564 : AutoIdVector& props) const
565 : {
566 0 : FORWARD(getOwnEnumerablePropertyKeys, (cx, proxy, props), false);
567 : }
568 :
569 : bool
570 0 : WrapperOwner::getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject proxy, AutoIdVector& props)
571 : {
572 0 : return getPropertyKeys(cx, proxy, JSITER_OWNONLY, props);
573 : }
574 :
575 : bool
576 0 : CPOWProxyHandler::preventExtensions(JSContext* cx, HandleObject proxy, ObjectOpResult& result) const
577 : {
578 0 : FORWARD(preventExtensions, (cx, proxy, result), false);
579 : }
580 :
581 : bool
582 0 : WrapperOwner::preventExtensions(JSContext* cx, HandleObject proxy, ObjectOpResult& result)
583 : {
584 0 : ObjectId objId = idOf(proxy);
585 :
586 0 : ReturnStatus status;
587 0 : if (!SendPreventExtensions(objId, &status))
588 0 : return ipcfail(cx);
589 :
590 0 : LOG_STACK();
591 :
592 0 : return ok(cx, status, result);
593 : }
594 :
595 : bool
596 0 : CPOWProxyHandler::isExtensible(JSContext* cx, HandleObject proxy, bool* extensible) const
597 : {
598 0 : FORWARD(isExtensible, (cx, proxy, extensible), false);
599 : }
600 :
601 : bool
602 0 : WrapperOwner::isExtensible(JSContext* cx, HandleObject proxy, bool* extensible)
603 : {
604 0 : ObjectId objId = idOf(proxy);
605 :
606 0 : ReturnStatus status;
607 0 : if (!SendIsExtensible(objId, &status, extensible))
608 0 : return ipcfail(cx);
609 :
610 0 : LOG_STACK();
611 :
612 0 : return ok(cx, status);
613 : }
614 :
615 : bool
616 0 : CPOWProxyHandler::call(JSContext* cx, HandleObject proxy, const CallArgs& args) const
617 : {
618 0 : FORWARD(callOrConstruct, (cx, proxy, args, false), false);
619 : }
620 :
621 : bool
622 0 : CPOWProxyHandler::construct(JSContext* cx, HandleObject proxy, const CallArgs& args) const
623 : {
624 0 : FORWARD(callOrConstruct, (cx, proxy, args, true), false);
625 : }
626 :
627 : bool
628 0 : WrapperOwner::callOrConstruct(JSContext* cx, HandleObject proxy, const CallArgs& args,
629 : bool construct)
630 : {
631 0 : ObjectId objId = idOf(proxy);
632 :
633 0 : InfallibleTArray<JSParam> vals;
634 0 : AutoValueVector outobjects(cx);
635 :
636 0 : RootedValue v(cx);
637 0 : for (size_t i = 0; i < args.length() + 2; i++) {
638 : // The |this| value for constructors is a magic value that we won't be
639 : // able to convert, so skip it.
640 0 : if (i == 1 && construct)
641 0 : v = UndefinedValue();
642 : else
643 0 : v = args.base()[i];
644 0 : if (v.isObject()) {
645 0 : RootedObject obj(cx, &v.toObject());
646 0 : if (xpc::IsOutObject(cx, obj)) {
647 : // Make sure it is not an in-out object.
648 : bool found;
649 0 : if (!JS_HasProperty(cx, obj, "value", &found))
650 0 : return false;
651 0 : if (found) {
652 0 : JS_ReportErrorASCII(cx, "in-out objects cannot be sent via CPOWs yet");
653 0 : return false;
654 : }
655 :
656 0 : vals.AppendElement(JSParam(void_t()));
657 0 : if (!outobjects.append(ObjectValue(*obj)))
658 : return false;
659 0 : continue;
660 : }
661 : }
662 0 : JSVariant val;
663 0 : if (!toVariant(cx, v, &val))
664 0 : return false;
665 0 : vals.AppendElement(JSParam(val));
666 : }
667 :
668 0 : JSVariant result;
669 0 : ReturnStatus status;
670 0 : InfallibleTArray<JSParam> outparams;
671 0 : if (!SendCallOrConstruct(objId, vals, construct, &status, &result, &outparams))
672 0 : return ipcfail(cx);
673 :
674 0 : LOG_STACK();
675 :
676 0 : if (!ok(cx, status))
677 : return false;
678 :
679 0 : if (outparams.Length() != outobjects.length())
680 0 : return ipcfail(cx);
681 :
682 0 : RootedObject obj(cx);
683 0 : for (size_t i = 0; i < outparams.Length(); i++) {
684 : // Don't bother doing anything for outparams that weren't set.
685 0 : if (outparams[i].type() == JSParam::Tvoid_t)
686 : continue;
687 :
688 : // Take the value the child process returned, and set it on the XPC
689 : // object.
690 0 : if (!fromVariant(cx, outparams[i], &v))
691 : return false;
692 :
693 0 : obj = &outobjects[i].toObject();
694 0 : if (!JS_SetProperty(cx, obj, "value", v))
695 : return false;
696 : }
697 :
698 0 : if (!fromVariant(cx, result, args.rval()))
699 : return false;
700 :
701 0 : return true;
702 : }
703 :
704 : bool
705 0 : CPOWProxyHandler::hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v, bool* bp) const
706 : {
707 0 : FORWARD(hasInstance, (cx, proxy, v, bp), false);
708 : }
709 :
710 : bool
711 0 : WrapperOwner::hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v, bool* bp)
712 : {
713 0 : ObjectId objId = idOf(proxy);
714 :
715 0 : JSVariant vVar;
716 0 : if (!toVariant(cx, v, &vVar))
717 : return false;
718 :
719 0 : ReturnStatus status;
720 0 : if (!SendHasInstance(objId, vVar, &status, bp))
721 0 : return ipcfail(cx);
722 :
723 0 : LOG_STACK();
724 :
725 0 : return ok(cx, status);
726 : }
727 :
728 : bool
729 0 : CPOWProxyHandler::getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls) const
730 : {
731 0 : FORWARD(getBuiltinClass, (cx, proxy, cls), false);
732 : }
733 :
734 : bool
735 0 : WrapperOwner::getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls)
736 : {
737 0 : ObjectId objId = idOf(proxy);
738 :
739 0 : uint32_t classValue = uint32_t(ESClass::Other);
740 0 : ReturnStatus status;
741 0 : if (!SendGetBuiltinClass(objId, &status, &classValue))
742 0 : return ipcfail(cx);
743 0 : *cls = ESClass(classValue);
744 :
745 0 : LOG_STACK();
746 :
747 0 : return ok(cx, status);
748 : }
749 :
750 : bool
751 0 : CPOWProxyHandler::isArray(JSContext* cx, HandleObject proxy,
752 : IsArrayAnswer* answer) const
753 : {
754 0 : FORWARD(isArray, (cx, proxy, answer), false);
755 : }
756 :
757 : bool
758 0 : WrapperOwner::isArray(JSContext* cx, HandleObject proxy, IsArrayAnswer* answer)
759 : {
760 0 : ObjectId objId = idOf(proxy);
761 :
762 : uint32_t ans;
763 0 : ReturnStatus status;
764 0 : if (!SendIsArray(objId, &status, &ans))
765 0 : return ipcfail(cx);
766 :
767 0 : LOG_STACK();
768 :
769 0 : *answer = IsArrayAnswer(ans);
770 0 : MOZ_ASSERT(*answer == IsArrayAnswer::Array ||
771 : *answer == IsArrayAnswer::NotArray ||
772 : *answer == IsArrayAnswer::RevokedProxy);
773 :
774 0 : return ok(cx, status);
775 : }
776 :
777 : const char*
778 0 : CPOWProxyHandler::className(JSContext* cx, HandleObject proxy) const
779 : {
780 0 : WrapperOwner* parent = OwnerOf(proxy);
781 0 : if (!parent->active())
782 : return "<dead CPOW>";
783 0 : return parent->className(cx, proxy);
784 : }
785 :
786 : const char*
787 0 : WrapperOwner::className(JSContext* cx, HandleObject proxy)
788 : {
789 0 : AuxCPOWData* data = AuxCPOWDataOf(proxy);
790 0 : if (data->className.IsEmpty()) {
791 0 : ObjectId objId = idOf(proxy);
792 :
793 0 : if (!SendClassName(objId, &data->className))
794 0 : return "<error>";
795 :
796 0 : LOG_STACK();
797 : }
798 :
799 0 : return data->className.get();
800 : }
801 :
802 : bool
803 0 : CPOWProxyHandler::getPrototype(JSContext* cx, HandleObject proxy, MutableHandleObject objp) const
804 : {
805 0 : FORWARD(getPrototype, (cx, proxy, objp), false);
806 : }
807 :
808 : bool
809 0 : WrapperOwner::getPrototype(JSContext* cx, HandleObject proxy, MutableHandleObject objp)
810 : {
811 0 : ObjectId objId = idOf(proxy);
812 :
813 0 : ObjectOrNullVariant val;
814 0 : ReturnStatus status;
815 0 : if (!SendGetPrototype(objId, &status, &val))
816 0 : return ipcfail(cx);
817 :
818 0 : LOG_STACK();
819 :
820 0 : if (!ok(cx, status))
821 : return false;
822 :
823 0 : objp.set(fromObjectOrNullVariant(cx, val));
824 :
825 0 : return true;
826 : }
827 :
828 : bool
829 0 : CPOWProxyHandler::getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary,
830 : MutableHandleObject objp) const
831 : {
832 0 : FORWARD(getPrototypeIfOrdinary, (cx, proxy, isOrdinary, objp), false);
833 : }
834 :
835 : bool
836 0 : WrapperOwner::getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary,
837 : MutableHandleObject objp)
838 : {
839 0 : ObjectId objId = idOf(proxy);
840 :
841 0 : ObjectOrNullVariant val;
842 0 : ReturnStatus status;
843 0 : if (!SendGetPrototypeIfOrdinary(objId, &status, isOrdinary, &val))
844 0 : return ipcfail(cx);
845 :
846 0 : LOG_STACK();
847 :
848 0 : if (!ok(cx, status))
849 : return false;
850 :
851 0 : objp.set(fromObjectOrNullVariant(cx, val));
852 :
853 0 : return true;
854 : }
855 :
856 : RegExpShared*
857 0 : CPOWProxyHandler::regexp_toShared(JSContext* cx, HandleObject proxy) const
858 : {
859 0 : FORWARD(regexp_toShared, (cx, proxy), nullptr);
860 : }
861 :
862 : RegExpShared*
863 0 : WrapperOwner::regexp_toShared(JSContext* cx, HandleObject proxy)
864 : {
865 0 : ObjectId objId = idOf(proxy);
866 :
867 0 : ReturnStatus status;
868 0 : nsString source;
869 0 : unsigned flags = 0;
870 0 : if (!SendRegExpToShared(objId, &status, &source, &flags)) {
871 0 : MOZ_ALWAYS_FALSE(ipcfail(cx));
872 0 : return nullptr;
873 : }
874 0 : LOG_STACK();
875 :
876 0 : if (!ok(cx, status))
877 : return nullptr;
878 :
879 0 : RootedObject regexp(cx);
880 0 : regexp = JS_NewUCRegExpObject(cx, source.get(), source.Length(), flags);
881 0 : if (!regexp)
882 : return nullptr;
883 :
884 0 : return js::RegExpToSharedNonInline(cx, regexp);
885 : }
886 :
887 : void
888 0 : CPOWProxyHandler::finalize(JSFreeOp* fop, JSObject* proxy) const
889 : {
890 0 : AuxCPOWData* aux = AuxCPOWDataOf(proxy);
891 :
892 0 : OwnerOf(proxy)->drop(proxy);
893 :
894 0 : if (aux)
895 0 : delete aux;
896 0 : }
897 :
898 : size_t
899 0 : CPOWProxyHandler::objectMoved(JSObject* proxy, JSObject* old) const
900 : {
901 0 : OwnerOf(proxy)->updatePointer(proxy, old);
902 0 : return 0;
903 : }
904 :
905 : bool
906 0 : CPOWProxyHandler::isCallable(JSObject* proxy) const
907 : {
908 0 : AuxCPOWData* aux = AuxCPOWDataOf(proxy);
909 0 : return aux->isCallable;
910 : }
911 :
912 : bool
913 0 : CPOWProxyHandler::isConstructor(JSObject* proxy) const
914 : {
915 0 : AuxCPOWData* aux = AuxCPOWDataOf(proxy);
916 0 : return aux->isConstructor;
917 : }
918 :
919 : void
920 0 : WrapperOwner::drop(JSObject* obj)
921 : {
922 : // The association may have already been swept from the table but if it's
923 : // there then remove it.
924 0 : ObjectId objId = idOfUnchecked(obj);
925 0 : if (cpows_.findPreserveColor(objId) == obj)
926 0 : cpows_.remove(objId);
927 :
928 0 : if (active())
929 0 : Unused << SendDropObject(objId);
930 0 : decref();
931 0 : }
932 :
933 : void
934 0 : WrapperOwner::updatePointer(JSObject* obj, const JSObject* old)
935 : {
936 0 : ObjectId objId = idOfUnchecked(obj);
937 0 : MOZ_ASSERT(hasCPOW(objId, old));
938 0 : cpows_.add(objId, obj);
939 0 : }
940 :
941 : bool
942 0 : WrapperOwner::init()
943 : {
944 0 : if (!JavaScriptShared::init())
945 : return false;
946 :
947 0 : return true;
948 : }
949 :
950 : bool
951 0 : WrapperOwner::getPropertyKeys(JSContext* cx, HandleObject proxy, uint32_t flags, AutoIdVector& props)
952 : {
953 0 : ObjectId objId = idOf(proxy);
954 :
955 0 : ReturnStatus status;
956 0 : InfallibleTArray<JSIDVariant> ids;
957 0 : if (!SendGetPropertyKeys(objId, flags, &status, &ids))
958 0 : return ipcfail(cx);
959 :
960 0 : LOG_STACK();
961 :
962 0 : if (!ok(cx, status))
963 : return false;
964 :
965 0 : for (size_t i = 0; i < ids.Length(); i++) {
966 0 : RootedId id(cx);
967 0 : if (!fromJSIDVariant(cx, ids[i], &id))
968 0 : return false;
969 0 : if (!props.append(id))
970 : return false;
971 : }
972 :
973 : return true;
974 : }
975 :
976 : namespace mozilla {
977 : namespace jsipc {
978 :
979 : bool
980 0 : IsCPOW(JSObject* obj)
981 : {
982 0 : return IsProxy(obj) && GetProxyHandler(obj) == &CPOWProxyHandler::singleton;
983 : }
984 :
985 : bool
986 0 : IsWrappedCPOW(JSObject* obj)
987 : {
988 0 : JSObject* unwrapped = js::UncheckedUnwrap(obj, true);
989 0 : if (!unwrapped)
990 : return false;
991 0 : return IsCPOW(unwrapped);
992 : }
993 :
994 : void
995 0 : GetWrappedCPOWTag(JSObject* obj, nsACString& out)
996 : {
997 0 : JSObject* unwrapped = js::UncheckedUnwrap(obj, true);
998 0 : MOZ_ASSERT(IsCPOW(unwrapped));
999 :
1000 0 : AuxCPOWData* aux = AuxCPOWDataOf(unwrapped);
1001 0 : if (aux)
1002 0 : out = aux->objectTag;
1003 0 : }
1004 :
1005 : nsresult
1006 0 : InstanceOf(JSObject* proxy, const nsID* id, bool* bp)
1007 : {
1008 0 : WrapperOwner* parent = OwnerOf(proxy);
1009 0 : if (!parent->active())
1010 : return NS_ERROR_UNEXPECTED;
1011 0 : return parent->instanceOf(proxy, id, bp);
1012 : }
1013 :
1014 : bool
1015 0 : DOMInstanceOf(JSContext* cx, JSObject* proxyArg, int prototypeID, int depth, bool* bp)
1016 : {
1017 0 : RootedObject proxy(cx, proxyArg);
1018 0 : FORWARD(domInstanceOf, (cx, proxy, prototypeID, depth, bp), false);
1019 : }
1020 :
1021 : } /* namespace jsipc */
1022 : } /* namespace mozilla */
1023 :
1024 : nsresult
1025 0 : WrapperOwner::instanceOf(JSObject* obj, const nsID* id, bool* bp)
1026 : {
1027 0 : ObjectId objId = idOf(obj);
1028 :
1029 0 : JSIID iid;
1030 0 : ConvertID(*id, &iid);
1031 :
1032 0 : ReturnStatus status;
1033 0 : if (!SendInstanceOf(objId, iid, &status, bp))
1034 : return NS_ERROR_UNEXPECTED;
1035 :
1036 0 : if (status.type() != ReturnStatus::TReturnSuccess)
1037 : return NS_ERROR_UNEXPECTED;
1038 :
1039 0 : return NS_OK;
1040 : }
1041 :
1042 : bool
1043 0 : WrapperOwner::domInstanceOf(JSContext* cx, JSObject* obj, int prototypeID, int depth, bool* bp)
1044 : {
1045 0 : ObjectId objId = idOf(obj);
1046 :
1047 0 : ReturnStatus status;
1048 0 : if (!SendDOMInstanceOf(objId, prototypeID, depth, &status, bp))
1049 0 : return ipcfail(cx);
1050 :
1051 0 : LOG_STACK();
1052 :
1053 0 : return ok(cx, status);
1054 : }
1055 :
1056 : void
1057 0 : WrapperOwner::ActorDestroy(ActorDestroyReason why)
1058 : {
1059 0 : inactive_ = true;
1060 :
1061 0 : objects_.clear();
1062 0 : unwaivedObjectIds_.clear();
1063 0 : waivedObjectIds_.clear();
1064 0 : }
1065 :
1066 : bool
1067 0 : WrapperOwner::ipcfail(JSContext* cx)
1068 : {
1069 0 : JS_ReportErrorASCII(cx, "cross-process JS call failed");
1070 0 : return false;
1071 : }
1072 :
1073 : bool
1074 0 : WrapperOwner::ok(JSContext* cx, const ReturnStatus& status)
1075 : {
1076 0 : if (status.type() == ReturnStatus::TReturnSuccess)
1077 : return true;
1078 :
1079 0 : if (status.type() == ReturnStatus::TReturnDeadCPOW) {
1080 0 : JS_ReportErrorASCII(cx, "operation not possible on dead CPOW");
1081 0 : return false;
1082 : }
1083 :
1084 0 : RootedValue exn(cx);
1085 0 : if (!fromVariant(cx, status.get_ReturnException().exn(), &exn))
1086 : return false;
1087 :
1088 0 : JS_SetPendingException(cx, exn);
1089 0 : return false;
1090 : }
1091 :
1092 : bool
1093 0 : WrapperOwner::ok(JSContext* cx, const ReturnStatus& status, ObjectOpResult& result)
1094 : {
1095 0 : if (status.type() == ReturnStatus::TReturnObjectOpResult)
1096 0 : return result.fail(status.get_ReturnObjectOpResult().code());
1097 0 : if (!ok(cx, status))
1098 : return false;
1099 0 : return result.succeed();
1100 : }
1101 :
1102 : // CPOWs can have a tag string attached to them, originating in the local
1103 : // process from this function. It's sent with the CPOW to the remote process,
1104 : // where it can be fetched with Components.utils.getCrossProcessWrapperTag.
1105 : static nsCString
1106 0 : GetRemoteObjectTag(JS::Handle<JSObject*> obj)
1107 : {
1108 0 : if (nsCOMPtr<nsISupports> supports = xpc::UnwrapReflectorToISupports(obj)) {
1109 0 : nsCOMPtr<nsIDocShellTreeItem> treeItem(do_QueryInterface(supports));
1110 0 : if (treeItem)
1111 0 : return NS_LITERAL_CSTRING("ContentDocShellTreeItem");
1112 :
1113 0 : nsCOMPtr<nsIDocument> doc(do_QueryInterface(supports));
1114 0 : if (doc)
1115 0 : return NS_LITERAL_CSTRING("ContentDocument");
1116 : }
1117 :
1118 0 : return NS_LITERAL_CSTRING("generic");
1119 : }
1120 :
1121 : static RemoteObject
1122 0 : MakeRemoteObject(JSContext* cx, ObjectId id, HandleObject obj)
1123 : {
1124 0 : return RemoteObject(id.serialize(),
1125 0 : JS::IsCallable(obj),
1126 0 : JS::IsConstructor(obj),
1127 0 : dom::IsDOMObject(obj),
1128 0 : GetRemoteObjectTag(obj));
1129 : }
1130 :
1131 : bool
1132 0 : WrapperOwner::toObjectVariant(JSContext* cx, JSObject* objArg, ObjectVariant* objVarp)
1133 : {
1134 0 : RootedObject obj(cx, objArg);
1135 0 : MOZ_ASSERT(obj);
1136 :
1137 : // We always save objects unwrapped in the CPOW table. If we stored
1138 : // wrappers, then the wrapper might be GCed while the target remained alive.
1139 : // Whenever operating on an object that comes from the table, we wrap it
1140 : // in findObjectById.
1141 0 : unsigned wrapperFlags = 0;
1142 0 : obj = js::UncheckedUnwrap(obj, true, &wrapperFlags);
1143 0 : if (obj && IsCPOW(obj) && OwnerOf(obj) == this) {
1144 0 : *objVarp = LocalObject(idOf(obj).serialize());
1145 0 : return true;
1146 : }
1147 0 : bool waiveXray = wrapperFlags & xpc::WrapperFactory::WAIVE_XRAY_WRAPPER_FLAG;
1148 :
1149 0 : ObjectId id = objectIdMap(waiveXray).find(obj);
1150 0 : if (!id.isNull()) {
1151 0 : MOZ_ASSERT(id.hasXrayWaiver() == waiveXray);
1152 0 : *objVarp = MakeRemoteObject(cx, id, obj);
1153 0 : return true;
1154 : }
1155 :
1156 : // Need to call PreserveWrapper on |obj| in case it's a reflector.
1157 : // FIXME: What if it's an XPCWrappedNative?
1158 0 : if (mozilla::dom::IsDOMObject(obj))
1159 0 : mozilla::dom::TryPreserveWrapper(obj);
1160 :
1161 0 : id = ObjectId(nextSerialNumber_++, waiveXray);
1162 0 : if (!objects_.add(id, obj))
1163 : return false;
1164 0 : if (!objectIdMap(waiveXray).add(cx, obj, id))
1165 : return false;
1166 :
1167 0 : *objVarp = MakeRemoteObject(cx, id, obj);
1168 0 : return true;
1169 : }
1170 :
1171 : JSObject*
1172 0 : WrapperOwner::fromObjectVariant(JSContext* cx, const ObjectVariant& objVar)
1173 : {
1174 0 : if (objVar.type() == ObjectVariant::TRemoteObject) {
1175 0 : return fromRemoteObjectVariant(cx, objVar.get_RemoteObject());
1176 : } else {
1177 0 : return fromLocalObjectVariant(cx, objVar.get_LocalObject());
1178 : }
1179 : }
1180 :
1181 : JSObject*
1182 0 : WrapperOwner::fromRemoteObjectVariant(JSContext* cx, const RemoteObject& objVar)
1183 : {
1184 0 : Maybe<ObjectId> maybeObjId(ObjectId::deserialize(objVar.serializedId()));
1185 0 : MOZ_RELEASE_ASSERT(maybeObjId.isSome());
1186 0 : ObjectId objId = maybeObjId.value();
1187 48 : RootedObject obj(cx, findCPOWById(objId));
1188 24 : if (!obj) {
1189 :
1190 : // All CPOWs live in the privileged junk scope.
1191 0 : RootedObject junkScope(cx, xpc::PrivilegedJunkScope());
1192 18 : JSAutoRealm ar(cx, junkScope);
1193 12 : RootedValue v(cx, UndefinedValue());
1194 : // We need to setLazyProto for the getPrototype/getPrototypeIfOrdinary
1195 : // hooks.
1196 0 : ProxyOptions options;
1197 6 : options.setLazyProto(true);
1198 12 : obj = NewProxyObject(cx,
1199 : &CPOWProxyHandler::singleton,
1200 : v,
1201 : nullptr,
1202 1 : options);
1203 6 : if (!obj)
1204 0 : return nullptr;
1205 :
1206 12 : if (!cpows_.add(objId, obj))
1207 : return nullptr;
1208 :
1209 12 : nextCPOWNumber_ = objId.serialNumber() + 1;
1210 :
1211 : // Incref once we know the decref will be called.
1212 12 : incref();
1213 :
1214 : AuxCPOWData* aux = new AuxCPOWData(objId,
1215 0 : objVar.isCallable(),
1216 0 : objVar.isConstructor(),
1217 6 : objVar.isDOMObject(),
1218 0 : objVar.objectTag());
1219 :
1220 12 : SetProxyReservedSlot(obj, 0, PrivateValue(this));
1221 12 : SetProxyReservedSlot(obj, 1, PrivateValue(aux));
1222 : }
1223 :
1224 0 : if (!JS_WrapObject(cx, &obj))
1225 : return nullptr;
1226 24 : return obj;
1227 : }
1228 :
1229 : JSObject*
1230 0 : WrapperOwner::fromLocalObjectVariant(JSContext* cx, const LocalObject& objVar)
1231 : {
1232 0 : Maybe<ObjectId> id(ObjectId::deserialize(objVar.serializedId()));
1233 0 : MOZ_RELEASE_ASSERT(id.isSome());
1234 0 : Rooted<JSObject*> obj(cx, findObjectById(cx, id.value()));
1235 0 : if (!obj)
1236 : return nullptr;
1237 : if (!JS_WrapObject(cx, &obj))
1238 : return nullptr;
1239 : return obj;
1240 : }
|