1 // as_object.cpp: ActionScript Object class and its properties, for Gnash.
2 //
3 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
4 // Free Software Foundation, Inc
5 //
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 3 of the License, or
9 // (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19
20 #include "as_object.h"
21
22 #include <set>
23 #include <string>
24 #include <boost/algorithm/string/case_conv.hpp>
25 #include <utility> // for std::pair
26
27 #include "RunResources.h"
28 #include "log.h"
29 #include "as_function.h"
30 #include "as_environment.h"
31 #include "movie_root.h"
32 #include "event_id.h"
33 #include "Property.h"
34 #include "VM.h"
35 #include "GnashException.h"
36 #include "fn_call.h"
37 #include "Array_as.h"
38 #include "as_function.h"
39 #include "Global_as.h"
40 #include "GnashAlgorithm.h"
41 #include "DisplayObject.h"
42 #include "namedStrings.h"
43
44 namespace gnash {
45 template<typename T>
46 class
47 as_object::PrototypeRecursor
48 {
49 public:
PrototypeRecursor(as_object * top,const ObjectURI & uri,T cmp=T ())50 PrototypeRecursor(as_object* top, const ObjectURI& uri, T cmp = T())
51 :
52 _object(top),
53 _uri(uri),
54 _iterations(0),
55 _condition(std::move(cmp))
56 {
57 _visited.insert(top);
58 }
59
60 /// Iterate to the next object in the inheritance chain.
61 //
62 /// This function throws an ActionLimitException when the maximum
63 /// number of recursions is reached.
64 //
65 /// @return false if there is no next object. In this case calling
66 /// the other functions will abort.
operator ()()67 bool operator()()
68 {
69 ++_iterations;
70
71 // See swfdec/prototype-recursion-get-?.swf
72 if (_iterations > 256) {
73 throw ActionLimitException("Lookup depth exceeded.");
74 }
75
76 _object = _object->get_prototype();
77
78 // TODO: there is recursion prevention anyway; is this extra
79 // check for circularity really necessary?
80 if (!_visited.insert(_object).second) return 0;
81 return _object && !_object->displayObject();
82 }
83
84 /// Return the wanted property if it exists and satisfies the predicate.
85 //
86 /// This will abort if there is no current object.
getProperty(as_object ** owner=nullptr) const87 Property* getProperty(as_object** owner = nullptr) const {
88
89 assert(_object);
90 Property* prop = _object->_members.getProperty(_uri);
91
92 if (prop && _condition(*prop)) {
93 if (owner) *owner = _object;
94 return prop;
95 }
96 return nullptr;
97 }
98
99 private:
100 as_object* _object;
101 const ObjectURI& _uri;
102 std::set<const as_object*> _visited;
103 size_t _iterations;
104 T _condition;
105 };
106
107 // Anonymous namespace used for module-static defs
108 namespace {
109
110 as_function*
getConstructor(as_object & o)111 getConstructor(as_object& o)
112 {
113 as_value ctorVal;
114 if (!o.get_member(NSV::PROP_uuCONSTRUCTORuu, &ctorVal)) {
115 return nullptr;
116 }
117 return ctorVal.to_function();
118 }
119
120 /// 'super' is a special kind of object
121 //
122 /// See http://wiki.gnashdev.org/wiki/index.php/ActionScriptSuper
123 ///
124 /// We make it derive from as_function instead of as_object
125 /// to avoid touching too many files (ie: an as_object is not considered
126 /// something that can be called by current Gnash code). We may want
127 /// to change this in the future to implement what ECMA-262 refers to
128 /// as the [[Call]] property of objects.
129 ///
130 class as_super : public as_object
131 {
132 public:
133
as_super(Global_as & gl,as_object * super)134 as_super(Global_as& gl, as_object* super)
135 :
136 as_object(gl),
137 _super(super)
138 {
139 set_prototype(prototype());
140 }
141
isSuper() const142 virtual bool isSuper() const { return true; }
143
144 virtual as_object* get_super(const ObjectURI& fname);
145
146 // Fetching members from 'super' yelds a lookup on the associated prototype
get_member(const ObjectURI & uri,as_value * val)147 virtual bool get_member(const ObjectURI& uri, as_value* val)
148 {
149 as_object* proto = prototype();
150 if (proto) return proto->get_member(uri, val);
151 log_debug("Super has no associated prototype");
152 return false;
153 }
154
155 /// Dispatch.
call(const fn_call & fn)156 virtual as_value call(const fn_call& fn)
157 {
158
159 // TODO: this is a hack to make sure objects are constructed, not
160 // converted (fn.isInstantiation() must be true).
161 fn_call::Args::container_type argsIn(fn.getArgs());
162 fn_call::Args args;
163 args.swap(argsIn);
164
165 fn_call fn2(fn.this_ptr, fn.env(), args, fn.super, true);
166 assert(fn2.isInstantiation());
167 as_function* ctor = constructor();
168 if (ctor) return ctor->call(fn2);
169 log_debug("Super has no associated constructor");
170 return as_value();
171 }
172
173 protected:
174
markReachableResources() const175 virtual void markReachableResources() const
176 {
177 if (_super) _super->setReachable();
178 as_object::markReachableResources();
179 }
180
181 private:
182
prototype()183 as_object* prototype() {
184 return _super ? _super->get_prototype() : nullptr;
185 }
186
constructor()187 as_function* constructor() {
188 return _super ? getConstructor(*_super) : nullptr;
189 }
190
191 as_object* _super;
192 };
193
194 as_object*
get_super(const ObjectURI & fname)195 as_super::get_super(const ObjectURI& fname)
196 {
197 // Super references the super class of our class prototype.
198 // Our class prototype is __proto__.
199 // Our class superclass prototype is __proto__.__proto__
200
201 // Our class prototype is __proto__.
202 as_object* proto = get_prototype();
203 if (!proto) return new as_super(getGlobal(*this), nullptr);
204
205 if (fname.empty() || getSWFVersion(*this) <= 6) {
206 return new as_super(getGlobal(*this), proto);
207 }
208
209 as_object* owner = nullptr;
210 proto->findProperty(fname, &owner);
211 if (!owner) return nullptr;
212
213 if (owner == proto) return new as_super(getGlobal(*this), proto);
214
215 as_object* tmp = proto;
216 while (tmp && tmp->get_prototype() != owner) {
217 tmp = tmp->get_prototype();
218 }
219 // ok, now 'tmp' should be the object whose __proto__ member
220 // contains the actual named method.
221 //
222 // in the C:B:A:F case this would be B when calling
223 // super.myName() from C.prototype.myName()
224
225 // well, since we found the property, it must be somewhere!
226 assert(tmp);
227
228 if (tmp != proto) { return new as_super(getGlobal(*this), tmp); }
229 return new as_super(getGlobal(*this), owner);
230
231 }
232
233 /// A PropertyList visitor copying properties to an object
234 class PropsCopier : public PropertyVisitor
235 {
236
237 public:
238
239 /// \brief
240 /// Initialize a PropsCopier instance associating it
241 /// with a target object (an object whose members has to be set)
242 ///
PropsCopier(as_object & tgt)243 PropsCopier(as_object& tgt)
244 :
245 _tgt(tgt)
246 {
247 }
248
249 /// Set *inherited* properties of the given target object
accept(const ObjectURI & uri,const as_value & val)250 bool accept(const ObjectURI& uri, const as_value& val) {
251 if (getName(uri) == NSV::PROP_uuPROTOuu) return true;
252 _tgt.set_member(uri, val);
253 return true;
254 }
255 private:
256 as_object& _tgt;
257 };
258
259 class PropertyEnumerator : public PropertyVisitor
260 {
261 public:
PropertyEnumerator(SortedPropertyList & to)262 PropertyEnumerator(SortedPropertyList& to)
263 :
264 _to(to)
265 {}
266
accept(const ObjectURI & uri,const as_value & val)267 bool accept(const ObjectURI& uri, const as_value& val) {
268 _to.push_back(std::make_pair(uri, val));
269 return true;
270 }
271 private:
272 SortedPropertyList& _to;
273 };
274
275 } // anonymous namespace
276
277
278 const int as_object::DefaultFlags;
279
as_object(const Global_as & gl)280 as_object::as_object(const Global_as& gl)
281 :
282 GcResource(getRoot(gl).gc()),
283 _displayObject(nullptr),
284 _array(false),
285 _vm(getVM(gl)),
286 _members(*this)
287 {
288 }
289
as_object(VM & vm)290 as_object::as_object(VM& vm)
291 :
292 GcResource(vm.getRoot().gc()),
293 _displayObject(nullptr),
294 _array(false),
295 _vm(vm),
296 _members(*this)
297 {
298 }
299
300 as_value
call(const fn_call &)301 as_object::call(const fn_call& /*fn*/)
302 {
303 throw ActionTypeError();
304 }
305
306 std::string
stringValue() const307 as_object::stringValue() const
308 {
309 return "[object Object]";
310 }
311
312 std::pair<bool,bool>
delProperty(const ObjectURI & uri)313 as_object::delProperty(const ObjectURI& uri)
314 {
315 return _members.delProperty(uri);
316 }
317
318
319 void
add_property(const std::string & name,as_function & getter,as_function * setter)320 as_object::add_property(const std::string& name, as_function& getter,
321 as_function* setter)
322 {
323 const ObjectURI& uri = getURI(vm(), name);
324
325 Property* prop = _members.getProperty(uri);
326
327 if (prop) {
328 const as_value& cacheVal = prop->getCache();
329 // Used to return the return value of addGetterSetter, but this
330 // is always true.
331 _members.addGetterSetter(uri, getter, setter, cacheVal);
332 return;
333 // NOTE: watch triggers not called when adding a new
334 // getter-setter property
335 }
336 else {
337
338 _members.addGetterSetter(uri, getter, setter, as_value());
339
340 // Nothing more to do if there are no triggers.
341 if (!_trigs.get()) return;
342
343 // check if we have a trigger, if so, invoke it
344 // and set val to its return
345 TriggerContainer::iterator trigIter = _trigs->find(uri);
346
347 if (trigIter != _trigs->end()) {
348
349 Trigger& trig = trigIter->second;
350
351 log_debug("add_property: property %s is being watched", name);
352 as_value v = trig.call(as_value(), as_value(), *this);
353
354 // The trigger call could have deleted the property,
355 // so we check for its existence again, and do NOT put
356 // it back in if it was deleted
357 prop = _members.getProperty(uri);
358 if (!prop) {
359 log_debug("Property %s deleted by trigger on create (getter-setter)", name);
360 return;
361 }
362 prop->setCache(v);
363 }
364 return;
365 }
366 }
367
368
369 /// Order of property lookup:
370 //
371 /// 1. Visible own properties.
372 /// 2. If DisplayObject, magic properties
373 /// 3. Visible own properties of all __proto__ objects (a DisplayObject
374 /// ends the chain).
375 /// 4. __resolve property of this object and all __proto__ objects (a Display
376 /// Object ends the chain). This should ignore visibility but doesn't.
377 bool
get_member(const ObjectURI & uri,as_value * val)378 as_object::get_member(const ObjectURI& uri, as_value* val)
379 {
380 assert(val);
381
382 const int version = getSWFVersion(*this);
383
384 PrototypeRecursor<IsVisible> pr(this, uri, IsVisible(version));
385
386 Property* prop = pr.getProperty();
387 if (!prop) {
388 if (displayObject()) {
389 DisplayObject* d = displayObject();
390 if (getDisplayObjectProperty(*d, uri, *val)) return true;
391 }
392 while (pr()) {
393 if ((prop = pr.getProperty())) break;
394 }
395 }
396
397 // If the property isn't found or doesn't apply to any objects in the
398 // inheritance chain, try the __resolve property.
399 if (!prop) {
400
401 PrototypeRecursor<Exists> pr(this, NSV::PROP_uuRESOLVE);
402
403 as_value resolve;
404
405 for (;;) {
406 Property* res = pr.getProperty();
407 if (res) {
408 resolve = res->isGetterSetter() ? res->getCache() :
409 res->getValue(*this);
410 if (version < 7) break;
411 if (resolve.is_object()) break;
412 }
413 // Finished searching.
414 if (!pr()) return false;
415 }
416
417 // If __resolve exists, call it with the name of the undefined
418 // property.
419 string_table& st = getStringTable(*this);
420 const std::string& undefinedName = st.value(getName(uri));
421
422 fn_call::Args args;
423 args += undefinedName;
424
425 // Invoke the __resolve property.
426 *val = invoke(resolve, as_environment(getVM(*this)), this, args);
427
428 return true;
429 }
430
431 try {
432 *val = prop->getValue(*this);
433 return true;
434 }
435 catch (const ActionTypeError& exc) {
436 IF_VERBOSE_ASCODING_ERRORS(
437 log_aserror(_("Caught exception: %s"), exc.what());
438 );
439 return false;
440 }
441 }
442
443
444 as_object*
get_super(const ObjectURI & fname)445 as_object::get_super(const ObjectURI& fname)
446 {
447 // Super references the super class of our class prototype.
448 // Our class prototype is __proto__.
449 // Our class superclass prototype is __proto__.__proto__
450
451 // Our class prototype is __proto__.
452 as_object* proto = get_prototype();
453
454 if ( ! fname.empty() && getSWFVersion(*this) > 6) {
455 as_object* owner = nullptr;
456 findProperty(fname, &owner);
457 // should be 0 if findProperty returned 0
458 if (owner != this) proto = owner;
459 }
460
461 as_object* super = new as_super(getGlobal(*this), proto);
462
463 return super;
464 }
465
466 as_object*
get_super()467 as_object::get_super()
468 {
469 // Our class prototype is __proto__.
470 as_object* proto = get_prototype();
471 as_object* super = new as_super(getGlobal(*this), proto);
472
473 return super;
474 }
475
476 Property*
findProperty(const ObjectURI & uri,as_object ** owner)477 as_object::findProperty(const ObjectURI& uri, as_object** owner)
478 {
479
480 const int version = getSWFVersion(*this);
481
482 PrototypeRecursor<IsVisible> pr(this, uri, IsVisible(version));
483
484 do {
485 Property* prop = pr.getProperty(owner);
486 if (prop) return prop;
487 } while (pr());
488
489 // No Property found
490 return nullptr;
491 }
492
493 Property*
findUpdatableProperty(const ObjectURI & uri)494 as_object::findUpdatableProperty(const ObjectURI& uri)
495 {
496
497 PrototypeRecursor<Exists> pr(this, uri);
498
499 Property* prop = pr.getProperty();
500
501 // We won't scan the inheritance chain if we find a member,
502 // even if invisible.
503 if (prop) return prop;
504
505 const int swfVersion = getSWFVersion(*this);
506
507 while (pr()) {
508 if ((prop = pr.getProperty())) {
509 if (prop->isGetterSetter() && visible(*prop, swfVersion)) {
510 return prop;
511 }
512 }
513 }
514 return nullptr;
515 }
516
517 void
set_prototype(const as_value & proto)518 as_object::set_prototype(const as_value& proto)
519 {
520 // TODO: check what happens if __proto__ is set as a user-defined
521 // getter/setter
522 // TODO: check triggers !!
523 _members.setValue(NSV::PROP_uuPROTOuu, proto, as_object::DefaultFlags);
524 }
525
526 void
executeTriggers(Property * prop,const ObjectURI & uri,const as_value & val)527 as_object::executeTriggers(Property* prop, const ObjectURI& uri,
528 const as_value& val)
529 {
530
531 // check if we have a trigger, if so, invoke it
532 // and set val to its return
533 TriggerContainer::iterator trigIter;
534
535 // If there are no triggers or the trigger is not found, just set
536 // the property.
537 if (!_trigs.get() || (trigIter = _trigs->find(uri)) == _trigs->end()) {
538 if (prop) {
539 prop->setValue(*this, val);
540 prop->clearVisible(getSWFVersion(*this));
541 }
542 return;
543 }
544
545 Trigger& trig = trigIter->second;
546
547 if (trig.dead()) {
548 _trigs->erase(trigIter);
549 return;
550 }
551
552 // WARNING: getValue might itself invoke a trigger
553 // (getter-setter)... ouch ?
554 // TODO: in this case, return the underlying value !
555 const as_value& curVal = prop ? prop->getCache() : as_value();
556 const as_value& newVal = trig.call(curVal, val, *this);
557
558 // This is a particularly clear and concise way of removing dead triggers.
559 EraseIf(*_trigs, std::bind(std::mem_fn(&Trigger::dead),
560 std::bind(&TriggerContainer::value_type::second,
561 std::placeholders::_1)));
562
563 // The trigger call could have deleted the property,
564 // so we check for its existence again, and do NOT put
565 // it back in if it was deleted
566 prop = findUpdatableProperty(uri);
567 if (!prop) return;
568
569 prop->setValue(*this, newVal);
570 prop->clearVisible(getSWFVersion(*this));
571
572 }
573
574 /// Order of property lookup:
575 //
576 /// 0. MovieClip textfield variables. TODO: this is a hack and should be
577 /// eradicated.
578 /// 1. Own properties even if invisible or not getter-setters.
579 /// 2. If DisplayObject, magic properties
580 /// 3. Visible own getter-setter properties of all __proto__ objects
581 /// (a DisplayObject ends the chain).
582 bool
set_member(const ObjectURI & uri,const as_value & val,bool ifFound)583 as_object::set_member(const ObjectURI& uri, const as_value& val, bool ifFound)
584 {
585
586 bool tfVarFound = false;
587 if (displayObject()) {
588 MovieClip* mc = dynamic_cast<MovieClip*>(displayObject());
589 if (mc) tfVarFound = mc->setTextFieldVariables(uri, val);
590 // We still need to set the member.
591 }
592
593 // Handle the length property for arrays. NB: checkArrayLength() will
594 // call this function again if the key is a valid index.
595 if (array()) checkArrayLength(*this, uri, val);
596
597 PrototypeRecursor<Exists> pr(this, uri);
598
599 Property* prop = pr.getProperty();
600
601 // We won't scan the inheritance chain if we find a member,
602 // even if invisible.
603 if (!prop) {
604
605 if (displayObject()) {
606 DisplayObject* d = displayObject();
607 if (setDisplayObjectProperty(*d, uri, val)) return true;
608 // TODO: should we execute triggers?
609 }
610
611 const int version = getSWFVersion(*this);
612 while (pr()) {
613 if ((prop = pr.getProperty())) {
614 if ((prop->isGetterSetter()) && visible(*prop, version)) {
615 break;
616 }
617 else prop = nullptr;
618 }
619 }
620 }
621
622 if (prop) {
623 if (readOnly(*prop)) {
624 IF_VERBOSE_ASCODING_ERRORS(
625 ObjectURI::Logger l(getStringTable(*this));
626 log_aserror(_("Attempt to set read-only property '%s'"),
627 l(uri));
628 );
629 return true;
630 }
631
632 try {
633 executeTriggers(prop, uri, val);
634 }
635 catch (const ActionTypeError& exc) {
636 IF_VERBOSE_ASCODING_ERRORS(
637 log_aserror(
638 _("%s: %s"), getStringTable(*this).value(getName(uri)), exc.what());
639 );
640 }
641
642 return true;
643 }
644
645 // Else, add new property...
646 if (ifFound) return false;
647
648 // Property does not exist, so it won't be read-only. Set it.
649 if (!_members.setValue(uri, val)) {
650
651 IF_VERBOSE_ASCODING_ERRORS(
652 ObjectURI::Logger l(getStringTable(*this));
653 log_aserror(_("Unknown failure in setting property '%s' on "
654 "object '%p'"), l(uri), (void*) this);
655 );
656 return false;
657 }
658
659 executeTriggers(prop, uri, val);
660
661 // Return true if we found a textfield variable.
662 if (tfVarFound) return true;
663
664 return false;
665 }
666
667
668 void
init_member(const std::string & key1,const as_value & val,int flags)669 as_object::init_member(const std::string& key1, const as_value& val, int flags)
670 {
671 const ObjectURI& uri(getURI(vm(), key1));
672 init_member(uri, val, flags);
673 }
674
675 void
init_member(const ObjectURI & uri,const as_value & val,int flags)676 as_object::init_member(const ObjectURI& uri, const as_value& val, int flags)
677 {
678
679 // Set (or create) a SimpleProperty
680 if (!_members.setValue(uri, val, flags)) {
681 ObjectURI::Logger l(getStringTable(*this));
682 log_error(_("Attempt to initialize read-only property '%s'"
683 " on object '%p' twice"), l(uri), (void*)this);
684 // We shouldn't attempt to initialize a member twice, should we ?
685 abort();
686 }
687 }
688
689 void
init_property(const std::string & name,as_function & getter,as_function & setter,int flags)690 as_object::init_property(const std::string& name, as_function& getter,
691 as_function& setter, int flags)
692 {
693 const ObjectURI& uri = getURI(vm(), name);
694 init_property(uri, getter, setter, flags);
695 }
696
697 void
init_property(const ObjectURI & uri,as_function & getter,as_function & setter,int flags)698 as_object::init_property(const ObjectURI& uri, as_function& getter,
699 as_function& setter, int flags)
700 {
701 _members.addGetterSetter(uri, getter, &setter, as_value(), flags);
702 }
703
704 void
init_property(const std::string & name,as_c_function_ptr getter,as_c_function_ptr setter,int flags)705 as_object::init_property(const std::string& name, as_c_function_ptr getter,
706 as_c_function_ptr setter, int flags)
707 {
708 const ObjectURI& uri = getURI(vm(), name);
709 init_property(uri, getter, setter, flags);
710 }
711
712 void
init_property(const ObjectURI & uri,as_c_function_ptr getter,as_c_function_ptr setter,int flags)713 as_object::init_property(const ObjectURI& uri, as_c_function_ptr getter,
714 as_c_function_ptr setter, int flags)
715 {
716 _members.addGetterSetter(uri, getter, setter, flags);
717 }
718
719 bool
init_destructive_property(const ObjectURI & uri,as_function & getter,int flags)720 as_object::init_destructive_property(const ObjectURI& uri, as_function& getter,
721 int flags)
722 {
723 return _members.addDestructiveGetter(uri, getter, flags);
724 }
725
726 bool
init_destructive_property(const ObjectURI & uri,as_c_function_ptr getter,int flags)727 as_object::init_destructive_property(const ObjectURI& uri,
728 as_c_function_ptr getter, int flags)
729 {
730 return _members.addDestructiveGetter(uri, getter, flags);
731 }
732
733 void
init_readonly_property(const std::string & name,as_function & getter,int initflags)734 as_object::init_readonly_property(const std::string& name, as_function& getter,
735 int initflags)
736 {
737 const ObjectURI& uri = getURI(vm(), name);
738
739 init_property(uri, getter, getter, initflags | PropFlags::readOnly);
740 assert(_members.getProperty(uri));
741 }
742
743 void
init_readonly_property(const std::string & name,as_c_function_ptr getter,int initflags)744 as_object::init_readonly_property(const std::string& name,
745 as_c_function_ptr getter, int initflags)
746 {
747 const ObjectURI& uri = getURI(vm(), name);
748 init_property(uri, getter, getter, initflags | PropFlags::readOnly);
749 assert(_members.getProperty(uri));
750 }
751
752 void
set_member_flags(const ObjectURI & uri,int setTrue,int setFalse)753 as_object::set_member_flags(const ObjectURI& uri, int setTrue, int setFalse)
754 {
755 _members.setFlags(uri, setTrue, setFalse);
756 }
757
758 void
addInterface(as_object * obj)759 as_object::addInterface(as_object* obj)
760 {
761 assert(obj);
762 if (std::find(_interfaces.begin(), _interfaces.end(), obj) ==
763 _interfaces.end()) {
764 _interfaces.push_back(obj);
765 }
766 }
767
768 bool
instanceOf(as_object * ctor)769 as_object::instanceOf(as_object* ctor)
770 {
771
772 /// An object is never an instance of a null prototype.
773 if (!ctor) return false;
774
775 as_value protoVal;
776 if (!ctor->get_member(NSV::PROP_PROTOTYPE, &protoVal)) {
777 #ifdef GNASH_DEBUG_INSTANCE_OF
778 log_debug("Object %p can't be an instance of an object (%p) with no 'prototype'",
779 (void*)this, (void*)ctor);
780 #endif
781 return false;
782 }
783
784 as_object* ctorProto = toObject(protoVal, getVM(*this));
785 if (!ctorProto) {
786 #ifdef GNASH_DEBUG_INSTANCE_OF
787 log_debug("Object %p can't be an instance of an object (%p) with non-object 'prototype' (%s)",
788 (void*)this, (void*)ctor, protoVal);
789 #endif
790 return false;
791 }
792
793 // TODO: cleanup the iteration, make it more readable ...
794 std::set<as_object*> visited;
795
796 as_object* obj = this;
797 while (obj && visited.insert(obj).second) {
798 as_object* thisProto = obj->get_prototype();
799 if (!thisProto) {
800 break;
801 }
802
803 // Check our proto
804 if (thisProto == ctorProto) {
805 #ifdef GNASH_DEBUG_INSTANCE_OF
806 log_debug("Object %p is an instance of constructor %p as the constructor exposes our __proto__ %p",
807 (void*)obj, (void*)ctor, (void*)thisProto);
808 #endif
809 return true;
810 }
811
812 // Check our proto interfaces
813 if (std::find(thisProto->_interfaces.begin(),
814 thisProto->_interfaces.end(), ctorProto)
815 != thisProto->_interfaces.end()) {
816
817 #ifdef GNASH_DEBUG_INSTANCE_OF
818 log_debug("Object %p __proto__ %p had one interface matching with the constructor prototype %p",
819 (void*)obj, (void*)thisProto, (void*)ctorProto);
820 #endif
821 return true;
822 }
823
824 obj = thisProto;
825 }
826
827 return false;
828 }
829
830 bool
prototypeOf(as_object & instance)831 as_object::prototypeOf(as_object& instance)
832 {
833 as_object* obj = &instance;
834
835 std::set<as_object*> visited;
836
837 while (obj && visited.insert(obj).second ) {
838 if (obj->get_prototype() == this) return true;
839 obj = obj->get_prototype();
840 }
841
842 // See actionscript.all/Inheritance.as for a way to trigger this
843 IF_VERBOSE_ASCODING_ERRORS(
844 if (obj) log_aserror(_("Circular inheritance chain detected "
845 "during isPrototypeOf call"));
846 );
847
848 return false;
849 }
850
851 void
dump_members()852 as_object::dump_members()
853 {
854 log_debug("%d members of object %p follow", _members.size(),
855 static_cast<const void*>(this));
856 _members.dump();
857 }
858
859 void
setPropFlags(const as_value & props_val,int set_false,int set_true)860 as_object::setPropFlags(const as_value& props_val, int set_false, int set_true)
861 {
862
863 if (props_val.is_null()) {
864 // Take all the members of the object
865 _members.setFlagsAll(set_true, set_false);
866 return;
867 }
868
869 std::string propstr = props_val.to_string();
870
871 for (;;) {
872
873 std::string prop;
874 size_t next_comma=propstr.find(",");
875 if (next_comma == std::string::npos) {
876 prop = propstr;
877 }
878 else {
879 prop = propstr.substr(0,next_comma);
880 propstr = propstr.substr(next_comma+1);
881 }
882
883 // set_member_flags will take care of case conversion
884 set_member_flags(getURI(vm(), prop), set_true, set_false);
885
886 if (next_comma == std::string::npos) {
887 break;
888 }
889 }
890 return;
891 }
892
893
894 void
copyProperties(const as_object & o)895 as_object::copyProperties(const as_object& o)
896 {
897 PropsCopier copier(*this);
898
899 // TODO: check if non-visible properties should be also copied !
900 o.visitProperties<Exists>(copier);
901 }
902
903 void
visitKeys(KeyVisitor & visitor) const904 as_object::visitKeys(KeyVisitor& visitor) const
905 {
906 // Hack to handle MovieClips.
907 if (displayObject()) {
908 displayObject()->visitNonProperties(visitor);
909 }
910
911 // this set will keep track of visited objects,
912 // to avoid infinite loops
913 std::set<const as_object*> visited;
914
915 PropertyList::PropertyTracker doneList;
916
917 const as_object* current(this);
918 while (current && visited.insert(current).second) {
919 current->_members.visitKeys(visitor, doneList);
920 current = current->get_prototype();
921 }
922 }
923
924
925 Property*
getOwnProperty(const ObjectURI & uri)926 as_object::getOwnProperty(const ObjectURI& uri)
927 {
928 return _members.getProperty(uri);
929 }
930
931 as_object*
get_prototype() const932 as_object::get_prototype() const
933 {
934 int swfVersion = getSWFVersion(*this);
935
936 Property* prop = _members.getProperty(NSV::PROP_uuPROTOuu);
937 if (!prop) return nullptr;
938 if (!visible(*prop, swfVersion)) return nullptr;
939
940 const as_value& proto = prop->getValue(*this);
941
942 return toObject(proto, getVM(*this));
943 }
944
945 std::string
getURLEncodedVars(as_object & o)946 getURLEncodedVars(as_object& o)
947 {
948 SortedPropertyList props = enumerateProperties(o);
949
950 std::string data;
951 string_table& st = getStringTable(o);
952
953 for (SortedPropertyList::const_reverse_iterator i = props.rbegin(),
954 e = props.rend(); i != e; ++i) {
955
956 const std::string& name = i->first.toString(st);
957 const std::string& value = i->second.to_string();
958
959 // see bug #22006
960 if (!name.empty() && name[0] == '$') continue;
961
962 URL::encode(value);
963 if (i != props.rbegin()) data += '&';
964
965 data += name + "=" + value;
966
967 }
968 return data;
969 }
970
971 bool
watch(const ObjectURI & uri,as_function & trig,const as_value & cust)972 as_object::watch(const ObjectURI& uri, as_function& trig,
973 const as_value& cust)
974 {
975
976 std::string propname = getStringTable(*this).value(getName(uri));
977
978 if (!_trigs.get()) _trigs.reset(new TriggerContainer);
979
980 TriggerContainer::iterator it = _trigs->find(uri);
981 if (it == _trigs->end()) {
982 return _trigs->insert(
983 std::make_pair(uri, Trigger(propname, trig, cust))).second;
984 }
985 it->second = Trigger(propname, trig, cust);
986 return true;
987 }
988
989 bool
unwatch(const ObjectURI & uri)990 as_object::unwatch(const ObjectURI& uri)
991 {
992 if (!_trigs.get()) return false;
993
994 TriggerContainer::iterator trigIter = _trigs->find(uri);
995 if (trigIter == _trigs->end()) {
996 log_debug("No watch for property %s",
997 getStringTable(*this).value(getName(uri)));
998 return false;
999 }
1000 Property* prop = _members.getProperty(uri);
1001 if (prop && prop->isGetterSetter()) {
1002 log_debug("Watch on %s not removed (is a getter-setter)",
1003 getStringTable(*this).value(getName(uri)));
1004 return false;
1005 }
1006 trigIter->second.kill();
1007 return true;
1008 }
1009
1010 void
markReachableResources() const1011 as_object::markReachableResources() const
1012 {
1013 _members.setReachable();
1014
1015 if (_trigs.get()) {
1016 for (TriggerContainer::const_iterator it = _trigs->begin();
1017 it != _trigs->end(); ++it) {
1018 it->second.setReachable();
1019 }
1020 }
1021
1022 // Mark interfaces reachable.
1023 std::for_each(_interfaces.begin(), _interfaces.end(),
1024 std::mem_fun(&as_object::setReachable));
1025
1026 // Proxy objects can contain references to other as_objects.
1027 if (_relay) _relay->setReachable();
1028 if (_displayObject) _displayObject->setReachable();
1029 }
1030
1031 void
setReachable() const1032 Trigger::setReachable() const
1033 {
1034 _func->setReachable();
1035 _customArg.setReachable();
1036 }
1037
1038 as_value
call(const as_value & oldval,const as_value & newval,as_object & this_obj)1039 Trigger::call(const as_value& oldval, const as_value& newval,
1040 as_object& this_obj)
1041 {
1042 assert(!_dead);
1043
1044 if (_executing) return newval;
1045
1046 _executing = true;
1047
1048 try {
1049
1050 const as_environment env(getVM(this_obj));
1051
1052 fn_call::Args args;
1053 args += _propname, oldval, newval, _customArg;
1054
1055 fn_call fn(&this_obj, env, args);
1056 as_value ret = _func->call(fn);
1057 _executing = false;
1058
1059 return ret;
1060
1061 }
1062 catch (const GnashException&) {
1063 _executing = false;
1064 throw;
1065 }
1066 }
1067
1068 SortedPropertyList
enumerateProperties(as_object & obj)1069 enumerateProperties(as_object& obj)
1070 {
1071
1072 // this set will keep track of visited objects,
1073 // to avoid infinite loops
1074 std::set<as_object*> visited;
1075
1076 SortedPropertyList to;
1077 PropertyEnumerator e(to);
1078 as_object* current(&obj);
1079
1080 while (current && visited.insert(current).second) {
1081 current->visitProperties<IsEnumerable>(e);
1082 current = current->get_prototype();
1083 }
1084 return to;
1085
1086 }
1087
1088 as_object*
getPathElement(as_object & o,const ObjectURI & uri)1089 getPathElement(as_object& o, const ObjectURI& uri)
1090 {
1091 as_value tmp;
1092 if (!o.get_member(uri, &tmp)) return nullptr;
1093 if (!tmp.is_object()) return nullptr;
1094 return toObject(tmp, getVM(o));
1095 }
1096
1097
1098 void
sendEvent(as_object & o,const as_environment & env,const ObjectURI & name)1099 sendEvent(as_object& o, const as_environment& env, const ObjectURI& name)
1100 {
1101 Property* prop = o.findProperty(name);
1102 if (prop) {
1103 fn_call::Args args;
1104 invoke(prop->getValue(o), env, &o, args);
1105 }
1106 }
1107
1108 as_object*
getObjectWithPrototype(Global_as & gl,const ObjectURI & c)1109 getObjectWithPrototype(Global_as& gl, const ObjectURI& c)
1110 {
1111 as_object* ctor = toObject(getMember(gl, c), getVM(gl));
1112 as_object* proto = ctor ?
1113 toObject(getMember(*ctor, NSV::PROP_PROTOTYPE), getVM(gl)) : nullptr;
1114
1115 as_object* o = createObject(gl);
1116 o->set_prototype(proto ? proto : as_value());
1117 return o;
1118 }
1119
1120 /// Get the VM from an as_object
1121 VM&
getVM(const as_object & o)1122 getVM(const as_object& o)
1123 {
1124 return o.vm();
1125 }
1126
1127 /// Get the movie_root from an as_object
1128 movie_root&
getRoot(const as_object & o)1129 getRoot(const as_object& o)
1130 {
1131 return o.vm().getRoot();
1132 }
1133
1134 /// Get the string_table from an as_object
1135 string_table&
getStringTable(const as_object & o)1136 getStringTable(const as_object& o)
1137 {
1138 return o.vm().getStringTable();
1139 }
1140
1141 const RunResources&
getRunResources(const as_object & o)1142 getRunResources(const as_object& o)
1143 {
1144 return o.vm().getRoot().runResources();
1145 }
1146
1147 int
getSWFVersion(const as_object & o)1148 getSWFVersion(const as_object& o)
1149 {
1150 return o.vm().getSWFVersion();
1151 }
1152
1153 Global_as&
getGlobal(const as_object & o)1154 getGlobal(const as_object& o)
1155 {
1156 return *o.vm().getGlobal();
1157 }
1158
1159 } // end of gnash namespace
1160
1161 // local Variables:
1162 // mode: C++
1163 // indent-tabs-mode: nil
1164 // End:
1165