/* Copyright 2003-2005 ROBLOX Corporation, All Rights Reserved */ #include "stdafx.h" #include "V8Tree/Instance.h" #include "RbxAssert.h" #include "V8Xml/Serializer.h" #include "V8Tree/Service.h" #include "util/standardout.h" #include "V8DataModel/ScriptService.h" #include "V8DataModel/DataModel.h" #include "security/ApiSecurity.h" #include "v8datamodel/HackDefines.h" #include "VMProtectSDK.h" LOGVARIABLE(InstanceTreeManipulation, 0) DYNAMIC_FASTFLAGVARIABLE(LockViolationInstanceCrash, false) namespace RBX { /////////////////////////////////// const char* const sInstance = "Instance"; REFLECTION_BEGIN(); Reflection::PropDescriptor Instance::propArchivable("Archivable", category_Behavior, &Instance::getIsArchivable, &Instance::setIsArchivable, Reflection::PropertyDescriptor::SCRIPTING); Reflection::PropDescriptor propArchivableDeprecated("archivable", category_Behavior, &Instance::getIsArchivable, &Instance::setIsArchivable, Reflection::PropertyDescriptor::LEGACY_SCRIPTING); static Reflection::BoundFuncDesc func_isA(&Instance::isA, "IsA", "className", Security::None); static Reflection::BoundFuncDesc func_isA_deprecated(&Instance::isA, "isA", "className", Security::None); static Reflection::BoundFuncDesc(std::string, bool)> findFirstChild(&Instance::findFirstChildByName2, "FindFirstChild", "name", "recursive", false, Security::None); static Reflection::BoundFuncDesc(std::string, bool)> findFirstChildDeprecated(&Instance::findFirstChildByName2, "findFirstChild", "name", "recursive", false, Security::None); static Reflection::BoundFuncDesc()> func_clone(&Instance::luaClone, "Clone", Security::None); static Reflection::BoundFuncDesc()> func_cloneDeprecated(&Instance::luaClone, "clone", Security::None); static Reflection::BoundFuncDesc func_Remove(&Instance::remove, "Remove", Security::None); static Reflection::BoundFuncDesc func_RemoveDeprecated(&Instance::remove, "remove", Security::None); static Reflection::BoundFuncDesc func_RemoveAllChildren(&Instance::destroyAllChildrenLua, "ClearAllChildren", Security::None); static Reflection::BoundFuncDesc func_Destroy(&Instance::destroy, "Destroy", Security::None); static Reflection::BoundFuncDesc dep_Destroy(&Instance::destroy, "destroy", Security::None, Reflection::PropertyDescriptor::Attributes::deprecated(func_Destroy)); static Reflection::BoundFuncDesc()> func_childrenOld(&Instance::getChildren2, "children", Security::None); static Reflection::BoundFuncDesc()> func_childrenOld2(&Instance::getChildren2, "getChildren", Security::None); static Reflection::BoundFuncDesc()> func_children(&Instance::getChildren2, "GetChildren", Security::None); static Reflection::BoundFuncDesc func_GetFullName(&Instance::getFullNameForReflection, "GetFullName", Security::None); static Reflection::BoundFuncDesc)> func_isDescendantOf(&Instance::isDescendantOf2, "IsDescendantOf", "ancestor", Security::None); static Reflection::BoundFuncDesc)> dep_isDescendantOf(&Instance::isDescendantOf2, "isDescendantOf", "ancestor", Security::None, Reflection::PropertyDescriptor::Attributes::deprecated(func_isDescendantOf)); static Reflection::BoundFuncDesc)> func_isAncestorOf(&Instance::isAncestorOf2, "IsAncestorOf", "descendant", Security::None); static Reflection::BoundFuncDesc func_GetReadableId(&Instance::getReadableDebugId, "GetDebugId", "scopeLength", 4, Security::Plugin); static Reflection::PropDescriptor prop_className("ClassName", category_Data, &Instance::getClassNameStr, NULL, Reflection::PropertyDescriptor::UI); static Reflection::PropDescriptor prop_classNameDeprecated("className", category_Data, &Instance::getClassNameStr, NULL, Reflection::PropertyDescriptor::Attributes::deprecated(prop_className)); static Reflection::PropDescriptor prop_dataCost("DataCost", category_Data, &Instance::getPersistentDataCost, NULL, Reflection::PropertyDescriptor::UI, Security::RobloxPlace); static Reflection::BoundYieldFuncDesc(std::string)> func_WaitForChild(&Instance::waitForChild, "WaitForChild", "childName", Security::None); const Reflection::PropDescriptor Instance::desc_Name("Name", category_Data, &Instance::getName, &Instance::setName); const Reflection::RefPropDescriptor Instance::propParent("Parent", category_Data, &Instance::getParentDangerous, &Instance::setParent, Reflection::PropertyDescriptor::UI); const Reflection::PropDescriptor Instance::propRobloxLocked("RobloxLocked", category_Data, &Instance::getRobloxLocked, &Instance::setRobloxLocked, Reflection::PropertyDescriptor::SCRIPTING, Security::Plugin); Reflection::EventDesc< Instance, void(shared_ptr), rbx::signal)>, rbx::signal)>* (Instance::*)(bool)> event_childAdded(&Instance::getOrCreateChildAddedSignal, "ChildAdded", "child"); Reflection::EventDesc< Instance, void(shared_ptr), rbx::signal)>, rbx::signal)>* (Instance::*)(bool)> dep_childAdded(&Instance::getOrCreateChildAddedSignal, "childAdded", "child", Reflection::Descriptor::Attributes::deprecated(event_childAdded)); Reflection::EventDesc< Instance, void(shared_ptr), rbx::signal)>, rbx::signal)>* (Instance::*)(bool)> event_childRemoved(&Instance::getOrCreateChildRemovedSignal, "ChildRemoved", "child"); Reflection::EventDesc< Instance, void(shared_ptr), rbx::signal)>, rbx::signal)>* (Instance::*)(bool)> event_descendantAdded(&Instance::getOrCreateDescendantAddedSignal, "DescendantAdded", "descendant"); Reflection::EventDesc< Instance, void(shared_ptr), rbx::signal)>, rbx::signal)>* (Instance::*)(bool)> event_descendantRemoving(&Instance::getOrCreateDescendantRemovingSignal, "DescendantRemoving", "descendant"); Reflection::EventDesc, shared_ptr)> event_ancestryChanged(&Instance::ancestryChangedSignal, "AncestryChanged", "child", "parent"); Reflection::EventDesc event_propertyChanged(&Instance::propertyChangedSignal, "Changed", "property"); REFLECTION_END(); shared_ptr Instance::createChild(const RBX::Name& className, RBX::CreatorRole creatorRole) { return Creatable::createByName(className, creatorRole); } void Instance::readChild(const XmlElement* childElement, IReferenceBinder& binder, CreatorRole creatorRole) { const RBX::Name* className = NULL; if (!childElement->findAttributeValue(tag_class, className)) return; shared_ptr childInstance = createChild(*className, creatorRole); if (childInstance!=NULL) { // First read the data before setting parent. This is more efficient and makes programming easier, // because an Instance's data is set *before* being added childInstance->read(childElement, binder, creatorRole); if (childInstance!=NULL) childInstance->setParent(this); } else { #ifdef _DEBUG std::string message; message = "Instance::readChild: Ignoring unrecognized class \""; std::string text; if (className) message += className->c_str(); message += "\""; RBX::StandardOut::singleton()->print(RBX::MESSAGE_WARNING, message.c_str()); #endif // Read the referent attribute, if it exists // We do this here because instance is NULL and therefore can't read it! const XmlAttribute* referentAttribute = childElement->findAttribute(name_referent); if (referentAttribute!=NULL) binder.announceID(referentAttribute, NULL); } } void Instance::readChildren(const XmlElement* element, IReferenceBinder& binder, RBX::CreatorRole creatorRole) { if (!element) return; const XmlElement* childElement = element->findFirstChildByTag(tag_Item); while (childElement) { readChild(childElement, binder, creatorRole); childElement = element->findNextChildWithSameTag(childElement); } } void Instance::readProperty(const XmlElement* propertyElement, IReferenceBinder& binder) { std::string name; if (propertyElement->findAttributeValue(name_name, name)) { if (Reflection::PropertyDescriptor* desc = this->findPropertyDescriptor(name.c_str())) if (!desc->isReadOnly()) Reflection::Property(*desc, this).read(propertyElement, binder); } } void Instance::readProperties(const XmlElement* container, IReferenceBinder& binder) { const XmlElement* propertyElement = container->firstChild(); while (propertyElement) { this->readProperty(propertyElement, binder); propertyElement = container->nextChild(propertyElement); } } void Instance::read(const XmlElement* element, IReferenceBinder& binder, RBX::CreatorRole creatorRole) { // Read the referent attribute, if it exists const XmlAttribute* referentAttribute = element->findAttribute(name_referent); if (referentAttribute!=NULL) { binder.announceID(referentAttribute, this); } if (element->getTag()==tag_Item) { // Read the Properties and Attributes const XmlElement* propertiesElement = element->findFirstChildByTag(tag_Properties); if (propertiesElement!=NULL) readProperties(propertiesElement, binder); // Read the child Instances readChildren(element, binder, creatorRole); } else { // Assume that we are reading a Property of this Instance this->readProperty(element, binder); } } static bool itIsInScope(Instance*) { return true; } void Instance::writeChildren(XmlElement* container, RBX::CreatorRole creatorRole, const SaveFilter saveFilter) { writeChildren(container, itIsInScope, creatorRole, saveFilter); } void Instance::writeChildren(XmlElement* container, const boost::function& isInScope, RBX::CreatorRole creatorRole, const SaveFilter saveFilter) { if (children) { const Instances& c(*children); for (Instances::const_iterator iter = c.begin(); iter!=c.end(); ++iter) { if( Serializer::canWriteChild((*iter),saveFilter) ) if (XmlElement* child = (*iter)->writeXml(isInScope, creatorRole)) container->addChild(child); } } } void Instance::writeProperties(XmlElement* container) const { // Write each Property, provided it wants to be streamed RBX::Reflection::ConstPropertyIterator iter = properties_begin(); while (iter!=properties_end()) { const Reflection::PropertyDescriptor& descriptor = (*iter).getDescriptor(); XmlElement* element = descriptor.write(this); if (element!=NULL) container->addChild(element); ++iter; } } XmlElement* Instance::writeXml(const boost::function& isInScope, RBX::CreatorRole creatorRole) { // TODO: archivable==false messes up functions like clone() // find a better way to do this if (!getIsArchivable() || (getClassName()==RBX::Name::getNullName())) return NULL; switch(creatorRole) { case ReplicationCreator: RBXASSERT(0); break; case SerializationCreator: if(!getDescriptor().isSerializable()) return NULL; break; case ScriptingCreator: if(!getDescriptor().isScriptCreatable()) return NULL; break; case EngineCreator: break; } XmlElement* element = new XmlElement(tag_Item); element->addAttribute(tag_class, &getClassName()); // Write the referent GUID if it exists element->addAttribute(name_referent, InstanceHandle(this)); // Write properties and attributes writeProperties(element->addChild(tag_Properties)); // Write child items writeChildren(element, isInScope, creatorRole); return element; } static void computeChildCost(shared_ptr instance, int* result) { (*result) += instance->getPersistentDataCost(); } int Instance::getPersistentDataCost() const { int total = 4; visitChildren(boost::bind(&computeChildCost, _1, &total)); return total; } void Instance::onChildChanged(Instance* instance, const PropertyChanged& event) { if (getParent()!=NULL) getParent()->onChildChanged(instance, event); } int Instance::findChildIndex(const Instance* instance) const { RBXASSERT(children); Instances::const_iterator it; const Instances& c(*children); it = std::find( c.begin(), c.end(), instance->shared_from_this() ); debugAssertM((it != c.end()), "Trying to find child not in the Instance child list"); return (it - c.begin()); } void Instance::waitForChild(std::string childName, boost::function)> resumeFunction, boost::function errorFunction) { if (childName == "") { errorFunction("WaitForChild called with an empty child name."); return; } if (findFirstChildByName(childName) != NULL) { resumeFunction(shared_from(findFirstChildByName(childName))); return; } OnDemandInstance* onDemand = onDemandWrite(); OnDemandInstance::ThreadWaitingForChild thread; thread.childName = childName; thread.resumeFunction = resumeFunction; onDemand->threadsWaitingForChildren.push_back(thread); } void Instance::checkParentWaitingForChildren() { if (!parent || !parent->onDemandRead()) return; const std::string name = getName(); std::vector& waitingThreads = parent->onDemandWrite()->threadsWaitingForChildren; std::vector::iterator iter = waitingThreads.begin(); for (;iter != waitingThreads.end();) { if (iter->childName == name) { iter->resumeFunction(shared_from(this)); iter = waitingThreads.erase(iter); } else { ++iter; } } } Instance* Instance::findFirstChildByNameRecursive(const std::string& findName) { // breadth-first search Instance* child = findFirstChildByName(findName); if (child!=NULL) return child; if (!children) return NULL; const Instances& c(*children); for (size_t i = 0; i < c.size(); ++i) { child = c[i]->findFirstChildByNameRecursive(findName); if (child!=NULL) return child; } return NULL; } const Instance* Instance::findConstFirstChildByName(const std::string& findName) const { if (!children) return NULL; const Instances& c(*children); for (size_t i = 0; i < c.size(); ++i) { if (c[i]->getName() == findName) { return c[i].get(); } } return NULL; } shared_ptr Instance::findFirstAncestorOf(const Instance* descendant) const { if (!children) return shared_ptr(); const Instances& c(*children); for (size_t i = 0; i < c.size(); ++i) { if (c[i]->isAncestorOf(descendant)) { return c[i]; } } return shared_ptr(); } ChildAdded::ChildAdded(Instance* child):child(shared_from(child)) { } ChildAdded::ChildAdded(const ChildAdded& event):child(event.child) { } ChildRemoved::ChildRemoved(Instance* child):child(shared_from(child)) { } ChildRemoved::ChildRemoved(const ChildRemoved& event):child(event.child) { } void Instance::securityCheck() const { securityCheck(RBX::Security::Context::current()); } void Instance::securityCheck(RBX::Security::Context& context) const { context.requirePermission(getDescriptor().security, "Class security check"); // Parents affect child security. The notion is that anything inside a secure object should also be secure if (const Instance* parent = getParent()) parent->securityCheck(context); } void Instance::verifySetAncestor(const Instance* const newParent, const Instance* const instanceGettingNewParent) const { visitChildren(boost::bind(&Instance::verifySetAncestor, _1, newParent, instanceGettingNewParent)); } void Instance::verifyAddDescendant(const Instance* newParent, const Instance* instanceGettingNewParent) const { // recur with this = this.parent const Instance* parent = getParent(); const Instance* oldParent = instanceGettingNewParent->getParent(); if (parent!=NULL && (parent!=oldParent) && !parent->isAncestorOf(oldParent)) { parent->verifyAddDescendant(parent, instanceGettingNewParent); } } class SetParentSentry { public: SetParentSentry(Instance* instance) : mInstance(instance) { mInstance->isSettingParent = true; } ~SetParentSentry() { mInstance->isSettingParent = false; } private: SetParentSentry() {} private: Instance* mInstance; }; // An anti-exploit feature in this function assumes "newParent" is the first argument to the function. bool Instance::setParentInternal(Instance* newParent, bool ignoreLock) { if (newParent==parent) { return true; } //Things that have locked parents (not complete) // All Services // LuaDraggers (to prevent insertion) // DataModel (to prevent parenting) // Workspace (Service) // GuiRoot // GuiHooks // ClientReplicators // Player objects (when removed) // Server Replicators if (!ignoreLock && getIsParentLocked()) { std::string message = RBX::format("The Parent property of %s is locked, current parent: %s, new parent %s", getFullName().c_str(), parent ? parent->getName().c_str() : "NULL", newParent ? newParent->getName().c_str() : "NULL"); throw std::runtime_error(message); } if (this==newParent) { std::string message = RBX::format("Attempt to set %s as its own parent", getFullName().c_str()); throw std::runtime_error(message); } if (this->isAncestorOf(newParent)) { RBXASSERT(newParent); std::string message = RBX::format("Attempt to set parent of %s to %s would result in circular reference", getFullName().c_str(), newParent->getFullName().c_str()); throw std::runtime_error(message); } FASTLOG3(FLog::InstanceTreeManipulation, "Instance %p: Setting parent to: %p. Old parent: %p", this, newParent, parent); if (isSettingParent) { if (getParent() == newParent) { return true; } std::string parentName = parent ? parent->getName() : "NULL"; std::string newParentName = newParent ? newParent->getName() : "NULL"; std::string message = RBX::format("Something unexpectedly tried to set the parent of %s to %s while trying to set the parent of %s. Current parent is %s.", getName().c_str(), newParentName.c_str(), getName().c_str(), parentName.c_str()); RBX::StandardOut::singleton()->print(RBX::MESSAGE_WARNING, message.c_str()); return false; } shared_ptr oldParent = shared_from(parent); // Keep a lock on ourself until we exit the function! shared_ptr self = shared_from(this); SetParentSentry sentry(this); //May throw if the child doesn't like this this->verifySetParent(newParent); // may throw if any descendant doesn't like this this->verifySetAncestor(newParent, this); if (newParent) { // may throw if an ancestor doesn't like this newParent->verifyAddChild(this); newParent->verifyAddDescendant(newParent, this); } if (oldParent) { if (!oldParent->contains(newParent)) signalDescendantRemoving(self, oldParent.get(), newParent); oldParent->onChildRemoving(this); { boost::shared_ptr& c = oldParent->children.write(); if (c->size() == 1) { c->clear(); oldParent->children.reset(); // We never keep an empty list around } else { // TODO: It might be faster to search from back-to-front (Since Instance::removeAllChildren starts at back) Instances::iterator iter = std::find(c->begin(), c->end(), self); if(iter != c->end()) { if (c->size() > 20) { // Fast-remove. This can make a huge speed improvement over regular remove *iter = c->back(); c->pop_back(); } else { // TODO: We keep this around because hopper bins don't do well with re-ordering c->erase(iter); } } } } this->parent = NULL; } if (newParent!=NULL) { newParent->children.write()->push_back(self); } this->parent = newParent; // signals for the child being removed if (oldParent != NULL) { ChildRemovedSignalData data(self); oldParent->combinedSignal(CHILD_REMOVED, &data); oldParent->childRemovedSignal(self); oldParent->onChildRemoved(this); // added by DB 4/3/07 } // This is here to aid in preventing ALX style exploits const void* thisFunction = getSetParentAddr(); checkRbxCaller >(thisFunction); // signals for the child being added #if !defined(RBX_RCC_SECURITY) && !defined(RBX_STUDIO_BUILD) && !defined(_NOOPT) && !defined(_DEBUG) && defined(_WIN32) && !defined(RBX_PLATFORM_DURANGO) bool detectedExploit = false; #endif if (newParent != NULL) { newParent->onChildAdded(this); if (!newParent->contains(oldParent.get())) signalDescendantAdded(this, newParent, oldParent.get()); const ChildAddedSignalData data(self); newParent->combinedSignal(CHILD_ADDED, &data); newParent->childAddedSignal(self); checkParentWaitingForChildren(); } #if !defined(RBX_RCC_SECURITY) && !defined(RBX_STUDIO_BUILD) && !defined(_NOOPT) && !defined(_DEBUG) && defined(_WIN32) && !defined(RBX_PLATFORM_DURANGO) else { detectedExploit = (detectDllByExceptionChainStack<4>(&newParent, RBX::Security::kCheckDefault) != 0); } #endif // TODO: Refactor this code - onAncestorChanged should happen BEFORE signalDescendantAdded this->onAncestorChanged(AncestorChanged(this, oldParent.get(), newParent)); raiseChanged(propParent); #if !defined(RBX_RCC_SECURITY) && !defined(RBX_STUDIO_BUILD) && !defined(_NOOPT) && !defined(_DEBUG) && defined(_WIN32) && !defined(RBX_PLATFORM_DURANGO) if (detectedExploit) { VMProtectBeginVirtualization(NULL); RBX::Security::setHackFlagVs<0>(RBX::Security::hackFlag3, HATE_SEH_CHECK); VMProtectEnd(); } #endif return true; } void Instance::onAncestorChanged(const AncestorChanged& event) { // Notify all children if (children) { shared_ptr c(children.read()); for (Instances::const_iterator iter = c->begin(); iter!=c->end(); ++iter) (*iter)->onAncestorChanged(event); } if (!combinedSignal.empty()){ AncestryChangedSignalData data(shared_from(event.child), shared_from(event.newParent)); combinedSignal(ANCESTRY_CHANGED, &data); } if (!ancestryChangedSignal.empty()) ancestryChangedSignal(shared_from(event.child), shared_from(event.newParent)); } void Instance::signalDescendantAdded(Instance* instance, Instance* beginParent, Instance* oldParent) { //Notify parents Instance* parent = beginParent; while (parent!=NULL && (parent!=oldParent) && !parent->isAncestorOf(oldParent)) { parent->onDescendantAdded(instance); parent = parent->getParent(); } // recurse through children shared_ptr c(instance->getChildren().read()); if (c) for (Instances::const_iterator iter = c->begin(); iter!=c->end(); ++iter) signalDescendantAdded(iter->get(), beginParent, oldParent); } void Instance::onDescendantAdded(Instance* instance) { if(onDemandRead()) onDemandWrite()->descendantAddedSignal(shared_from(instance)); } void Instance::signalDescendantRemoving(const shared_ptr& instance, Instance* beginParent, Instance* newParent) { //Notify parents Instance* parent = beginParent; while (parent!=NULL && (parent!=newParent) && !parent->isAncestorOf(newParent)) { parent->onDescendantRemoving(instance); parent = parent->getParent(); } // recurse through children shared_ptr c(instance->getChildren().read()); if (c) for (Instances::const_iterator iter = c->begin(); iter!=c->end(); ++iter) signalDescendantRemoving(*iter, beginParent, newParent); } void Instance::onDescendantRemoving(const shared_ptr& instance) { descendantRemovingSignal(instance); } static bool isInScope(Instance* parent, Instance* child) { if (parent == child) return true; return parent->isAncestorOf(child); } // DB 12/5/05 - added here to centralize the spawner behavior - to, from XML XmlElement* Instance::toNewXmlRoot(Instance* instance, RBX::CreatorRole creatorRole) { XmlElement* answer = Serializer::newRootElement(); if(XmlElement* child = instance->writeXml(boost::bind(isInScope, instance, _1), creatorRole)) answer->addChild(child); return answer; } // Clones an instance and all its children using Reflection. Ref properties // are also managed to reference items within the cloned tree. class Cloner : boost::noncopyable { // Map of Source -> Destination typedef boost::unordered_map > Map; Map map; const CreatorRole creatorRole; shared_ptr clone; public: // The constructor does the cloning Cloner(const Instance* instance, CreatorRole creatorRole):creatorRole(creatorRole) { shared_ptr source = shared_from(instance); // First just duplicate all the objects clone = createClones(source); // Copy all the property values (except for Parent) std::for_each(map.begin(), map.end(), boost::bind(&Cloner::copyProperties, this, _1)); // Now that all properties are copied, re-create the Instance tree setParent(source); // Notify cloning std::for_each(map.begin(), map.end(), boost::bind(&Cloner::notifyCloning, this, _1)); } shared_ptr getClone() const { return clone; } private: // This function simply duplicates all the instances, but doesn't create the tree nor copy properties shared_ptr createClones(shared_ptr instance) { RBXASSERT(instance); if (!instance->getDescriptor().isSerializable()) return shared_ptr(); if (!instance->getIsArchivable()) return shared_ptr(); shared_ptr copy = Creatable::createByName(instance->getDescriptor().name, creatorRole); if (!copy) throw std::runtime_error(RBX::format("%s cannot be cloned", instance->getFullName().c_str())); RBXASSERT(map.find(instance.get()) == map.end()); map[instance.get()] = copy; instance->visitChildren(boost::bind(&Cloner::createClones, this, _1)); return copy; } // Copy all non-Parent serializable properties of an Instance void copyProperties(const Map::value_type& value) { const Instance* source = value.first; std::for_each( source->properties_begin(), source->properties_end(), boost::bind(&Cloner::copyProperty, this, _1, source, value.second.get()) ); } // Copy non-Parent serializable property void copyProperty(const RBX::Reflection::ConstProperty& prop, const Instance* source, Instance* destination) { if (!prop.getDescriptor().alwaysClone()) { if (!prop.getDescriptor().canXmlRead()) return; if (!prop.getDescriptor().canXmlWrite()) return; } RBXASSERT (prop.getDescriptor() != Instance::propParent); RBXASSERT(source == prop.getInstance()); if (Reflection::RefPropertyDescriptor::isRefPropertyDescriptor(prop.getDescriptor())) { const Reflection::RefPropertyDescriptor* refDesc = static_cast(&prop.getDescriptor()); RBXASSERT(refDesc); Instance* value = static_cast(refDesc->getRefValue(source)); Map::const_iterator iter = map.find(value); if (iter != map.end()) value = iter->second.get(); // redirect to the cloned copy refDesc->setRefValue(destination, value); } else prop.getDescriptor().copyValue(source, destination); } // Recursively re-create the Instance tree on the copied items (bottom-up) void setParent(shared_ptr source) { Map::const_iterator iter = map.find(source.get()); if (iter == map.end()) return; // This item didn't get cloned, probably because it wasn't serializable const shared_ptr& destination = iter->second; // First iterate down to the children, since we want to set the parents bottom-up source->visitChildren(boost::bind(&Cloner::setParent, this, _1)); // Now set this node's parent if (destination == clone) return; // don't set the root's parent RBXASSERT(source->getParent()); RBXASSERT(map.find(source->getParent()) != map.end()); Instance* parent = map[source->getParent()].get(); RBXASSERT(parent); destination->setParent(parent); } void notifyCloning(const Map::value_type& value) { const Instance* source = value.first; if(source->onDemandRead()) const_cast(source)->onDemandWrite()->instanceClonedSignal(value.second); } }; shared_ptr Instance::clone(CreatorRole creatorRole) { return Cloner(this, creatorRole).getClone(); } shared_ptr Instance::luaClone() { return clone(RBX::ScriptingCreator); } // Declare some dictionary entries static const RBX::Name& nameName = RBX::Name::declare("Name"); static const RBX::Name& nameParent = RBX::Name::declare("Parent"); Instance::Instance() :parent(NULL) ,name("Instance") ,archivable(true) ,isParentLocked(false) ,robloxLocked(false) ,isSettingParent(false) { } Instance::Instance(const char* name) :parent(NULL) ,name(name) ,archivable(true) ,isParentLocked(false) ,robloxLocked(false) ,isSettingParent(false) { } Instance::~Instance() { RBXASSERT(parent==NULL); } void Instance::remove() { boost::shared_ptr c(children.read()); setParent(NULL); if (c) // To expedite garbage collection, we recursively call remove on the children std::for_each(c->begin(), c->end(), boost::bind(&Instance::remove, _1)); } void Instance::destroy() { if (getIsParentLocked() && getParent()) throw RBX::runtime_error("The Parent property of %s is locked", getFullName().c_str()); // This method will remove this object from the DataModel, possibly // removing the last shared_ptr reference to this. Make a local // self shared_ptr to make sure that 'this' remains valid inside this method. boost::shared_ptr c(children.read()); boost::shared_ptr self = shared_from(this); // Set the parent setAndLockParent(NULL); if (c) // To expedite garbage collection, we recursively call remove on the children std::for_each(c->begin(), c->end(), boost::bind(&Instance::destroy, _1)); // Clear all events to help prevent memory leaks. // Without this code, the following Lua will leak: // while true do // local i = Instance.new("Part") // local c = i.Changed:connect(function () // print(i) -- this closure prevents i from being collected // end) // wait(0.1) // i:Remove() // end std::for_each( getDescriptor().begin(), getDescriptor().end(), boost::bind(&Reflection::EventDescriptor::disconnectAll, _1, this) ); } void Instance::setName(const std::string& value) { if (name.get() != value) { if (value.size() > 100) name = value.substr(0, 100); else name = value; this->raisePropertyChanged(desc_Name); checkParentWaitingForChildren(); } } void Instance::setRobloxLocked(bool value) { if(robloxLocked != value){ robloxLocked = value; raisePropertyChanged(propRobloxLocked); } void (Instance::* thisFunction)(bool) = &Instance::setRobloxLocked; checkRbxCaller >( reinterpret_cast((void*&)(thisFunction))); } void Instance::setIsArchivable(bool value) { if(archivable != value) { archivable = value; raisePropertyChanged(propArchivable); } } void Instance::removeAllChildren() { while (children) { // Note: this is using "->" which implies a read, but the reference // returned from back() is used to perform updates (removal). shared_ptr child(children->back()); child->remove(); } } void Instance::destroyAllChildrenLua() { // security that results in a kick void (Instance::* thisFunction)() = &Instance::destroyAllChildrenLua; checkRbxCaller >( reinterpret_cast((void*&)(thisFunction))); while (children) { shared_ptr child(children->back()); // this prevents these commands from actually being processed if incorrectly called. if ( !checkRbxCaller(reinterpret_cast((void*&)(thisFunction))) ) { child->destroy(); } } } void Instance::predelete(Instance* instance) { instance->predelete(); } void Instance::predelete() { RBXASSERT(parent==NULL); while (children) { shared_ptr child; { // get the last element and pop it off (more efficient that way) shared_ptr c(children.write()); child = c->back(); // TODO: Can we nuke this: child->signalDescendantRemoving(child, this, NULL); // TODO: Can we nuke this: onChildRemoving(child.get()); child->parent = NULL; c->pop_back(); if (c->size()==0) children.reset(); } { // TODO: Can we nuke this: ChildRemovedSignalData data(child); combinedSignal(CHILD_REMOVED, &data); } // TODO: **** Can we nuke this: childRemovedSignal(child); // TODO: Can we nuke this: child->onAncestorChanged(AncestorChanged(child.get(), this, NULL)); // TODO: Can we nuke this: child->raiseChanged(propParent); } } bool Instance::contains(const Instance* child) const { while (child!=NULL) { if (child==this) return true; child = child->getParent(); } return false; } bool Instance::askSetParent(const Instance* instance) const { return false; } bool Instance::askForbidParent(const Instance* instance) const { return false; } bool Instance::askAddChild(const Instance* instance) const { return false; } bool Instance::askForbidChild(const Instance* instance) const { return false; } // Moves all children to the same level as this void Instance::promoteChildren() { while (numChildren()!=0) this->getChild(0)->setParent(getParent()); } void Instance::setAndLockParent(Instance* instance) { bool wasLocked = getIsParentLocked(); lockParent(); bool failedToSetParent = false; try { failedToSetParent = !setLockedParent(instance); } catch (RBX::base_exception&) { if (!wasLocked) { unlockParent(); } throw; } if (failedToSetParent && !wasLocked) { unlockParent(); } } // Render up to (but not including) the root node std::string Instance::getFullName() const { if (getParent() && !Instance::fastDynamicCast(getParent())) return getParent()->getFullName() + "." + getName(); else return getName(); } const OnDemandInstance* Instance::onDemandRead() const { return onDemandPtr.get(); } OnDemandInstance* Instance::onDemandWrite() { OnDemandInstance* onDemand = onDemandPtr.get(); if(onDemand == NULL) { onDemandPtr.reset(initOnDemand()); onDemand = onDemandPtr.get(); RBXASSERT(onDemand); } return onDemand; } OnDemandInstance* Instance::initOnDemand() { return new OnDemandInstance(); } static FORCEINLINE void validateThreadAccess(Instance* inst) { // this is actually useful for combating exploits. if (!DataModel::currentThreadHasWriteLock(inst)) RBXCRASH(); } void Instance::raisePropertyChanged(const RBX::Reflection::PropertyDescriptor& descriptor) { PropertyChanged event(RBX::Reflection::Property(descriptor, this)); this->onPropertyChanged(descriptor); // security (This ensures the caller has a data model lock) if (this->parent && DFFlag::LockViolationInstanceCrash) // fast-path for replication and serialization { validateThreadAccess(this); } const PropertyChangedSignalData data(&descriptor); combinedSignal(PROPERTY_CHANGED, &data); propertyChangedSignal(&descriptor); if (getParent()!=NULL) getParent()->onChildChanged(this, event); } void Instance::raiseEventInvocation(const RBX::Reflection::EventDescriptor& descriptor, const RBX::Reflection::EventArguments& args, const SystemAddress* target) { if (this->parent && DFFlag::LockViolationInstanceCrash) // fast-path for replication and serialization { validateThreadAccess(this); } const EventInvocationSignalData data(&descriptor, &args, target); combinedSignal(EVENT_INVOCATION, &data); } void Instance::humanoidChanged() { Instance::HumanoidChangedSignalData data; combinedSignal(Instance::HUMANOID_CHANGED, &data); } void Instance::onGuidChanged() { } const void* Instance::getSetParentAddr() { #ifdef _WIN32 // VS2012 allows this code here, but not in member functions. // This code becomes invalid if setParentInternal becomes virtual. bool (RBX::Instance::*thisPmfn)(RBX::Instance*, bool) = &setParentInternal; const void* thisCodeStruct = (const void*&)(thisPmfn); return thisCodeStruct; #else return NULL; #endif } } // namespace RBX namespace RBX{ namespace Security { // the .rdata section volatile const uintptr_t rbxTextEndNeg = 0x00400000; volatile const size_t rbxTextSizeNeg = 1024; } }