1 /* -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*- */
2 /* vi: set ts=4 sw=4 expandtab: (add to ~/.vimrc: set modeline modelines=5) */
3 /* ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
16 * The Original Code is [Open Source Virtual Machine.].
18 * The Initial Developer of the Original Code is
19 * Adobe System Incorporated.
20 * Portions created by the Initial Developer are Copyright (C) 2004-2006
21 * the Initial Developer. All Rights Reserved.
26 * Alternatively, the contents of this file may be used under the terms of
27 * either the GNU General Public License Version 2 or later (the "GPL"), or
28 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
38 * ***** END LICENSE BLOCK ***** */
42 //#include "../vprof/vprof.h"
46 ScriptObject::ScriptObject(VTable
* _vtable
, ScriptObject
* _delegate
) :
48 AvmPlusScriptableObject(sotObject(_vtable
)),
51 // note that it's substantially more efficient to initialize this in the ctor
52 // list vs. a later explicit call to setDelegate, as we don't have to check for marking
53 // nor decrement an existing value...
56 AvmAssert(vtable
->traits
->isResolved());
58 // Ensure that our object is large enough to hold its extra traits data.
59 AvmAssert(MMgc::GC::Size(this) >= vtable
->traits
->getTotalSize());
62 ScriptObject::ScriptObject(VTable
* _vtable
, ScriptObject
* _delegate
, int capacity
) :
64 AvmPlusScriptableObject(sotObject(_vtable
)),
67 // note that it's substantially more efficient to initialize this in the ctor
68 // list vs. a later explicit call to setDelegate, as we don't have to check for marking
69 // nor decrement an existing value...
72 AvmAssert(vtable
->traits
->isResolved());
74 // Ensure that our object is large enough to hold its extra traits data.
75 AvmAssert(MMgc::GC::Size(this) >= vtable
->traits
->getTotalSize());
77 //if capacity not specified then initialize the hashtable lazily
78 if (vtable
->traits
->needsHashtable() && capacity
)
80 initHashtable(capacity
);
84 ScriptObject::~ScriptObject()
86 //setDelegate(NULL); -- no longer necessary
87 vtable
->traits
->destroyInstance(this);
90 /* The storage for a ScriptObject or a subclass SO of ScriptObject is
91 * laid out as follows.
93 * - first the bits of the C++ class; if there are pointers here
94 * then they must be traced explicitly by the appropriate class's
96 * - then the bits for the ActionScript slots
97 * - optionally an InlineHashtable for dynamic properties
99 * The in-line slots are native and their representation is described
100 * by the Traits object (vtable->traits). They are not named, but named
101 * lookup is possible by going to the Traits object, which contains
104 * The InlineHashtable always stores Atoms.
106 bool ScriptObject::gcTrace(MMgc::GC
* gc
, size_t cursor
)
109 gc
->TraceLocation(&vtable
);
110 gc
->TraceLocation(&delegate
);
111 traits()->traceSlots(gc
, this);
112 if (traits()->needsHashtable())
114 // Avoid initializing the hash table here
115 InlineHashtable
* iht
= getTableNoInit();
122 void ScriptObject::initHashtable(int capacity
/*=InlineHashtable::kDefaultCapacity*/)
124 AvmAssert(vtable
->traits
->isDictionary() == 0); //should not be called DictionaryObject uses HeapHashtable
129 InlineHashtable
* iht
;
131 p
= (uint8_t*)this + vtable
->traits
->getHashtableOffset();
132 iht
->initializeWithDontEnumSupport(this->gc(), capacity
);
135 InlineHashtable
* ScriptObject::getTable() const
137 AvmAssert(vtable
->traits
->getHashtableOffset() != 0);
140 InlineHashtable
* iht
;
143 p
= (uint8_t*)this + vtable
->traits
->getHashtableOffset();
144 if(!vtable
->traits
->isDictionary())
146 if (iht
->needsInitialize())
147 const_cast<ScriptObject
*>(this)->initHashtable();
152 //DictionaryObjects store pointer to HeapHashtable at
153 //the hashtable offset
154 return (*hht
)->get_ht();
159 * traverse the delegate chain looking for a value.
160 * [ed] it's okay to look only at the HT's in the delegate chain because
161 * delegate values may only be instances of Object. They cannot be objects
162 * with slots. We don't need to look at traits at each step.
163 * todo - enforce this rule
167 Atom
ScriptObject::getAtomProperty(Atom name
) const
169 if (!traits()->needsHashtable())
171 return getAtomPropertyFromProtoChain(name
, delegate
, traits());
175 Stringp s
= core()->atomToString(name
);
176 AvmAssert(s
->isInterned());
177 Atom ival
= s
->getIntAtom();
183 // dynamic lookup on this object
184 const ScriptObject
*o
= this;
187 // ensure prototype is dynamic
188 if (!o
->vtable
->traits
->getHashtableOffset())
190 Atom
const value
= o
->getTable()->getNonEmpty(name
);
191 if (!InlineHashtable::isEmpty(value
))
194 while ((o
= o
->delegate
) != NULL
);
195 return undefinedAtom
;
199 Atom
ScriptObject::getAtomPropertyFromProtoChain(Atom name
, ScriptObject
* o
, Traits
*origObjTraits
) const
201 // todo will delegate always be non-null here?
204 Atom searchname
= name
;
205 Stringp s
= core()->atomToString(name
);
206 AvmAssert(s
->isInterned());
207 Atom ival
= s
->getIntAtom();
214 // ensure prototype is dynamic
215 if (!o
->vtable
->traits
->getHashtableOffset())
217 Atom
const value
= o
->getTable()->getNonEmpty(searchname
);
218 if (!InlineHashtable::isEmpty(value
))
221 while ((o
= o
->delegate
) != NULL
);
223 // NOTE use default public since name is not used
224 Multiname
multiname(core()->getAnyPublicNamespace(), AvmCore::atomToString(name
));
225 toplevel()->throwReferenceError(kReadSealedError
, &multiname
, origObjTraits
);
227 return undefinedAtom
;
230 bool ScriptObject::hasMultinameProperty(const Multiname
* multiname
) const
232 if (traits()->needsHashtable() && multiname
->isValidDynamicName())
234 return hasAtomProperty(multiname
->getName()->atom());
238 // ISSUE should this walk the proto chain?
243 bool ScriptObject::hasAtomProperty(Atom name
) const
245 if (traits()->needsHashtable())
247 Stringp s
= core()->atomToString(name
);
248 AvmAssert(s
->isInterned());
249 Atom ival
= s
->getIntAtom();
255 return getTable()->contains(name
);
259 // ISSUE should this walk the proto chain?
264 void ScriptObject::throwWriteSealedError(const Multiname
& name
)
266 toplevel()->throwReferenceError(kWriteSealedError
, name
, traits());
269 void ScriptObject::throwWriteSealedError(Atom name
)
271 AvmCore
* core
= this->core();
272 throwWriteSealedError(Multiname(core
->getAnyPublicNamespace(), core
->intern(name
)));
275 void ScriptObject::throwCantInstantiateError()
277 Multiname
qname(traits()->ns(), traits()->name());
278 toplevel()->argumentErrorClass()->throwError(kCantInstantiateError
, core()->toErrorString(&qname
));
281 void ScriptObject::setAtomProperty(Atom name
, Atom value
)
283 if (traits()->needsHashtable())
285 Stringp s
= core()->atomToString(name
);
286 AvmAssert(s
->isInterned());
287 Atom ival
= s
->getIntAtom();
294 getTable()->add (name
, value
);
299 throwWriteSealedError(name
);
303 void ScriptObject::setMultinameProperty(const Multiname
* name
, Atom value
)
305 if (traits()->needsHashtable() && name
->isValidDynamicName())
307 setStringProperty(name
->getName(), value
);
311 throwWriteSealedError(*name
);
315 bool ScriptObject::getAtomPropertyIsEnumerable(Atom name
) const
317 if (traits()->needsHashtable())
319 Stringp s
= core()->atomToString(name
);
320 AvmAssert(s
->isInterned());
321 Atom ival
= s
->getIntAtom();
327 return getTable()->getAtomPropertyIsEnumerable(name
);
331 // ISSUE should this walk the proto chain?
336 void ScriptObject::setAtomPropertyIsEnumerable(Atom name
, bool enumerable
)
338 if (traits()->needsHashtable())
340 Stringp s
= core()->atomToString(name
);
341 AvmAssert(s
->isInterned());
342 Atom ival
= s
->getIntAtom();
348 getTable()->setAtomPropertyIsEnumerable(name
, enumerable
);
352 throwWriteSealedError(name
);
356 bool ScriptObject::deleteAtomProperty(Atom name
)
358 if (traits()->needsHashtable())
360 Stringp s
= core()->atomToString(name
);
361 AvmAssert(s
->isInterned());
362 Atom ival
= s
->getIntAtom();
368 getTable()->remove(name
);
377 bool ScriptObject::deleteMultinameProperty(const Multiname
* name
)
379 if (traits()->needsHashtable() && name
->isValidDynamicName())
381 return deleteStringProperty(name
->getName());
389 Atom
ScriptObject::getUintProperty(uint32_t i
) const
391 // N.B.: a key present in ScriptObject must be interned string;
392 // thus uninterned implies absent (cf. bugzilla 556023).
394 AvmCore
* core
= this->core();
396 if (!(i
&MAX_INTEGER_MASK
))
398 if (!traits()->needsHashtable())
401 bool present
= core
->isInternedUint(i
, &interned
);
404 Atom name
= interned
->atom();
405 return getAtomPropertyFromProtoChain(name
, delegate
,
410 return undefinedAtom
;
415 // dynamic lookup on this object
416 Atom name
= core
->uintToAtom (i
);
417 const ScriptObject
*o
= this;
420 // ensure prototype is dynamic
421 if (!o
->vtable
->traits
->getHashtableOffset())
423 Atom
const value
= o
->getTable()->getNonEmpty(name
);
424 if (!InlineHashtable::isEmpty(value
))
427 while ((o
= o
->delegate
) != NULL
);
428 return undefinedAtom
;
435 present
= core
->isInternedUint(i
, &interned
);
438 return getAtomProperty(interned
->atom());
442 return undefinedAtom
;
447 void ScriptObject::setUintProperty(uint32_t i
, Atom value
)
449 AvmCore
* core
= this->core();
450 if (!(i
&MAX_INTEGER_MASK
))
452 Atom name
= core
->uintToAtom (i
);
453 if (traits()->needsHashtable())
456 getTable()->add(name
, value
);
461 throwWriteSealedError(core
->internUint32(i
)->atom());
466 setAtomProperty(core
->internUint32(i
)->atom(), value
);
470 bool ScriptObject::delUintProperty(uint32_t i
)
472 AvmCore
* core
= this->core();
473 if (!(i
&MAX_INTEGER_MASK
))
475 Atom name
= core
->uintToAtom (i
);
476 if (traits()->needsHashtable())
478 getTable()->remove(name
);
488 return deleteAtomProperty(core
->internUint32(i
)->atom());
492 bool ScriptObject::hasUintProperty(uint32_t i
) const
494 AvmCore
* core
= this->core();
495 if (!(i
&MAX_INTEGER_MASK
))
497 Atom name
= core
->uintToAtom (i
);
498 if (traits()->needsHashtable())
500 return getTable()->contains(name
);
504 // ISSUE should this walk the proto chain?
510 return hasAtomProperty(core
->internUint32(i
)->atom());
514 Atom
ScriptObject::getMultinameProperty(const Multiname
* multiname
) const
516 if (multiname
->isValidDynamicName())
518 return getStringProperty(multiname
->getName());
522 Toplevel
* toplevel
= this->toplevel();
524 if (multiname
->isNsset())
525 toplevel
->throwReferenceError(kReadSealedErrorNs
, multiname
, traits());
527 toplevel
->throwReferenceError(kReadSealedError
, multiname
, traits());
528 return undefinedAtom
;
532 // this = argv[0] (ignored)
535 Atom
ScriptObject::callProperty(const Multiname
* multiname
, int argc
, Atom
* argv
)
537 Toplevel
* toplevel
= this->toplevel();
538 Atom method
= getMultinameProperty(multiname
);
539 if (!AvmCore::isObject(method
))
540 toplevel
->throwTypeError(kCallOfNonFunctionError
, core()->toErrorString(multiname
));
541 argv
[0] = atom(); // replace receiver
542 return toplevel
->op_call(method
, argc
, argv
);
545 Atom
ScriptObject::constructProperty(const Multiname
* multiname
, int argc
, Atom
* argv
)
547 Atom ctor
= getMultinameProperty(multiname
);
548 argv
[0] = atom(); // replace receiver
549 return toplevel()->op_construct(ctor
, argc
, argv
);
552 Atom
ScriptObject::getDescendants(const Multiname
* /*name*/) const
554 toplevel()->throwTypeError(kDescendentsError
, core()->toErrorString(traits()));
555 return undefinedAtom
;// not reached
558 bool ScriptObject::isGlobalObject() const
560 return traits()->posType() == TRAITSTYPE_SCRIPT
;
563 #ifdef AVMPLUS_VERBOSE
564 PrintWriter
& ScriptObject::print(PrintWriter
& prw
) const
566 (traits()->name() != NULL
) ? prw
<< traits()
568 return prw
<< "@" << asAtomHex(atom());
572 Atom
ScriptObject::defaultValue()
574 AvmCore
*core
= this->core();
575 Toplevel
* toplevel
= this->toplevel();
579 // call this.valueOf()
580 // NOTE use callers versioned public to get correct valueOf
581 Multiname
tempname(core
->findPublicNamespace(), core
->kvalueOf
);
582 atomv_out
[0] = atom();
583 Atom result
= toplevel
->callproperty(atom(), &tempname
, 0, atomv_out
, vtable
);
585 // if result is primitive, return it
586 if (atomKind(result
) != kObjectType
)
589 // otherwise call this.toString()
590 tempname
.setName(core
->ktoString
);
591 atomv_out
[0] = atom();
592 result
= toplevel
->callproperty(atom(), &tempname
, 0, atomv_out
, vtable
);
594 // if result is primitive, return it
595 if (atomKind(result
) != kObjectType
)
598 // could not convert to primitive.
599 toplevel
->throwTypeError(kConvertToPrimitiveError
, core
->toErrorString(traits()));
600 return undefinedAtom
;
603 // Execute the ToString algorithm as described in ECMA-262 Section 9.8.
604 // This is ToString(ToPrimitive(input argument, hint String))
605 // ToPrimitive(input argument, hint String) calls [[DefaultValue]]
606 // described in ECMA-262 8.6.2.6. The [[DefaultValue]] algorithm
607 // with hint String is inlined here.
608 Stringp
ScriptObject::toString()
610 AvmCore
*core
= this->core();
611 Toplevel
* toplevel
= this->toplevel();
615 // call this.toString()
616 // NOTE use callers versioned public to get correct toString
617 Multiname
tempname(core
->findPublicNamespace(), core
->ktoString
);
618 atomv_out
[0] = atom();
619 Atom result
= toplevel
->callproperty(atom(), &tempname
, 0, atomv_out
, vtable
);
621 // if result is primitive, return its ToString
622 if (atomKind(result
) != kObjectType
)
623 return core
->string(result
);
625 // otherwise call this.valueOf()
626 tempname
.setName(core
->kvalueOf
);
627 atomv_out
[0] = atom();
628 result
= toplevel
->callproperty(atom(), &tempname
, 0, atomv_out
, vtable
);
630 // if result is primitive, return it
631 if (atomKind(result
) != kObjectType
)
632 return core
->string(result
);
634 // could not convert to primitive.
635 toplevel
->throwTypeError(kConvertToPrimitiveError
, core
->toErrorString(traits()));
636 return NULL
; // unreachable
639 // this = argv[0] (ignored)
642 Atom
ScriptObject::call(int /*argc*/, Atom
* /*argv*/)
644 // TypeError in ECMA to execute a non-function
645 // NOTE use default public since name is not used
646 Multiname
name(core()->getAnyPublicNamespace(), core()->kvalue
);
647 toplevel()->throwTypeError(kCallOfNonFunctionError
, core()->toErrorString(&name
));
648 return undefinedAtom
;
651 // this = argv[0] (ignored)
654 Atom
ScriptObject::construct(int /*argc*/, Atom
* /*argv*/)
656 // TypeError in ECMA to execute a non-function
657 toplevel()->throwTypeError(kConstructOfNonFunctionError
);
658 return undefinedAtom
;
661 Atom
ScriptObject::applyTypeArgs(int /*argc*/, Atom
* /*argv*/)
663 toplevel()->throwTypeError(kTypeAppOfNonParamType
);
664 return undefinedAtom
;
667 Atom
ScriptObject::getSlotAtom(uint32_t slot
)
669 Traits
* traits
= this->traits();
670 const TraitsBindingsp td
= traits
->getTraitsBindings();
671 // repeated if-else is actually more performant than a switch statement in this case.
672 // SST_atom is most common case, put it first
674 const SlotStorageType sst
= td
->calcSlotAddrAndSST(slot
, (void*)this, p
);
677 return *((const Atom
*)p
);
679 else if (sst
== SST_double
)
681 return traits
->core
->doubleToAtom(*((const double*)p
));
683 else if (sst
== SST_int32
)
685 return traits
->core
->intToAtom(*((const int32_t*)p
));
687 else if (sst
== SST_uint32
)
689 return traits
->core
->uintToAtom(*((const int32_t*)p
));
691 else if (sst
== SST_bool32
)
693 return (*((const int32_t*)p
)<<3)|kBooleanType
;
695 else if (sst
== SST_string
)
697 return (*((const Stringp
*)p
))->atom(); // may be null|kStringType, that's ok
699 else if (sst
== SST_namespace
)
701 return (*((const Namespacep
*)p
))->atom(); // may be null|kNamespaceType, no problemo
703 else // if (sst == SST_scriptobject)
705 AvmAssert(sst
== SST_scriptobject
);
706 return (*((const ScriptObject
**)p
))->atom(); // may be null|kObjectType, copacetic
710 ScriptObject
* ScriptObject::getSlotObject(uint32_t slot
)
712 Traits
* traits
= this->traits();
713 const TraitsBindingsp td
= traits
->getTraitsBindings();
715 const SlotStorageType sst
= td
->calcSlotAddrAndSST(slot
, (void*)this, p
);
717 // based on profiling of Flex apps, it's *much* more common for the slot in this case
718 // to have a type (vs "atom"), so check for that first...
719 if (sst
== SST_scriptobject
)
721 return *((ScriptObject
**)p
);
723 else if (sst
== SST_atom
)
725 Atom
const a
= *((const Atom
*)p
);
727 // don't call AvmCore::isObject(); it checks for null, which we don't care about here
728 if (atomKind(a
) == kObjectType
)
729 return (ScriptObject
*)atomPtr(a
);
731 // else fall thru and return null
737 // note: coerceAndSetSlotAtom now includes a simplified and streamlined version
738 // of Toplevel::coerce. If you modify that code, you might need to modify this code.
739 void ScriptObject::coerceAndSetSlotAtom(uint32_t slot
, Atom value
)
741 Traits
* traits
= this->traits();
742 const TraitsBindingsp td
= traits
->getTraitsBindings();
744 const SlotStorageType sst
= td
->calcSlotAddrAndSST(slot
, (void*)this, p
);
745 // repeated if-else is actually more performant than a switch statement in this case.
746 // SST_atom is most common case, put it first
749 // no call to coerce() needed, since anything will fit here... with one exception:
750 // BUILTIN_object needs to convert undefined->null (though BUILTIN_any does not).
751 // it's cheaper to do that here than call out to coerce().
752 AvmAssert(td
->getSlotTraits(slot
) == NULL
|| td
->getSlotTraits(slot
)->builtinType
== BUILTIN_object
);
753 if (value
== undefinedAtom
&& td
->getSlotTraits(slot
) != NULL
)
754 value
= nullObjectAtom
;
755 WBATOM(traits
->core
->GetGC(), this, (Atom
*)p
, value
);
757 else if (sst
== SST_double
)
759 *((double*)p
) = AvmCore::number(value
);
761 else if (sst
== SST_int32
)
763 *((int32_t*)p
) = AvmCore::integer(value
);
765 else if (sst
== SST_uint32
)
767 *((uint32_t*)p
) = AvmCore::toUInt32(value
);
769 else if (sst
== SST_bool32
)
771 *((int32_t*)p
) = AvmCore::boolean(value
);
775 // null/undefined -> NULL for all of these
776 if (AvmCore::isNullOrUndefined(value
))
778 value
= (Atom
)0; // don't bother setting tag bits
780 else if (sst
== SST_string
)
782 value
= (Atom
)traits
->core
->string(value
); // don't bother setting tag bits
784 else if (sst
== SST_namespace
)
786 // Namespace is final, so we don't have to do the hard work
787 if (atomKind(value
) != kNamespaceType
)
790 else // if (sst == SST_scriptobject)
792 AvmAssert(sst
== SST_scriptobject
);
793 if (atomKind(value
) != kObjectType
|| !AvmCore::atomToScriptObject(value
)->traits()->subtypeof(td
->getSlotTraits(slot
)))
796 WBRC(traits
->core
->GetGC(), this, p
, atomPtr(value
));
801 toplevel()->throwTypeError(kCheckTypeFailedError
, traits
->core
->atomToErrorString(value
), traits
->core
->toErrorString(td
->getSlotTraits(slot
)));
805 Atom
ScriptObject::nextName(int index
)
807 AvmAssert(traits()->needsHashtable());
808 AvmAssert(index
> 0);
810 InlineHashtable
* ht
= getTable();
811 Atom m
= ht
->keyAt(index
);
812 return AvmCore::isNullOrUndefined(m
) ? nullStringAtom
: m
;
815 Atom
ScriptObject::nextValue(int index
)
817 AvmAssert(traits()->needsHashtable());
818 AvmAssert(index
> 0);
820 InlineHashtable
* ht
= getTable();
821 Atom m
= ht
->keyAt(index
);
822 if (AvmCore::isNullOrUndefined(m
))
823 return nullStringAtom
;
824 return ht
->valueAt(index
);
827 int ScriptObject::nextNameIndex(int index
)
829 AvmAssert(index
>= 0);
831 if (!traits()->needsHashtable())
834 return getTable()->next(index
);
838 uint64_t ScriptObject::bytesUsed() const
840 uint64_t bytesUsed
= traits()->getTotalSize();
841 if(traits()->needsHashtable())
843 if (traits()->isDictionary())
849 p
= (uint8_t*)this + traits()->getHashtableOffset();
850 bytesUsed
+= (*hht
)->bytesUsed();
854 bytesUsed
+= getTable()->bytesUsed();
861 Stringp
ScriptObject::implToString() const
863 AvmCore
* core
= this->core();
864 Traits
* t
= this->traits();
865 Stringp s
= core
->concatStrings(core
->newConstantStringLatin1("[object "), t
->name());
866 return core
->concatStrings(s
, core
->newConstantStringLatin1("]"));
869 uint32_t ScriptObject::getLengthProperty()
871 Toplevel
* toplevel
= this->toplevel();
872 AvmCore
* core
= toplevel
->core();
873 Multiname
mname(core
->getAnyPublicNamespace(), core
->klength
);
874 Atom lenAtm
= toplevel
->getproperty(this->atom(), &mname
, this->vtable
);
875 return AvmCore::toUInt32(lenAtm
);
878 void ScriptObject::setLengthProperty(uint32_t newLen
)
880 Toplevel
* toplevel
= this->toplevel();
881 AvmCore
* core
= toplevel
->core();
882 Multiname
mname(core
->getAnyPublicNamespace(), core
->klength
);
883 Atom lenAtm
= core
->uintToAtom(newLen
);
884 toplevel
->setproperty(this->atom(), &mname
, lenAtm
, this->vtable
);