Files
2025-09-18 17:55:52 -04:00

1184 lines
36 KiB
C++

/* 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, bool> Instance::propArchivable("Archivable", category_Behavior, &Instance::getIsArchivable, &Instance::setIsArchivable, Reflection::PropertyDescriptor::SCRIPTING);
Reflection::PropDescriptor<Instance, bool> propArchivableDeprecated("archivable", category_Behavior, &Instance::getIsArchivable, &Instance::setIsArchivable, Reflection::PropertyDescriptor::LEGACY_SCRIPTING);
static Reflection::BoundFuncDesc<Instance, bool(std::string)> func_isA(&Instance::isA, "IsA", "className", Security::None);
static Reflection::BoundFuncDesc<Instance, bool(std::string)> func_isA_deprecated(&Instance::isA, "isA", "className", Security::None);
static Reflection::BoundFuncDesc<Instance, shared_ptr<Instance>(std::string, bool)> findFirstChild(&Instance::findFirstChildByName2, "FindFirstChild", "name", "recursive", false, Security::None);
static Reflection::BoundFuncDesc<Instance, shared_ptr<Instance>(std::string, bool)> findFirstChildDeprecated(&Instance::findFirstChildByName2, "findFirstChild", "name", "recursive", false, Security::None);
static Reflection::BoundFuncDesc<Instance, shared_ptr<Instance>()> func_clone(&Instance::luaClone, "Clone", Security::None);
static Reflection::BoundFuncDesc<Instance, shared_ptr<Instance>()> func_cloneDeprecated(&Instance::luaClone, "clone", Security::None);
static Reflection::BoundFuncDesc<Instance, void()> func_Remove(&Instance::remove, "Remove", Security::None);
static Reflection::BoundFuncDesc<Instance, void()> func_RemoveDeprecated(&Instance::remove, "remove", Security::None);
static Reflection::BoundFuncDesc<Instance, void()> func_RemoveAllChildren(&Instance::destroyAllChildrenLua, "ClearAllChildren", Security::None);
static Reflection::BoundFuncDesc<Instance, void()> func_Destroy(&Instance::destroy, "Destroy", Security::None);
static Reflection::BoundFuncDesc<Instance, void()> dep_Destroy(&Instance::destroy, "destroy", Security::None, Reflection::PropertyDescriptor::Attributes::deprecated(func_Destroy));
static Reflection::BoundFuncDesc<Instance, shared_ptr<const Instances>()> func_childrenOld(&Instance::getChildren2, "children", Security::None);
static Reflection::BoundFuncDesc<Instance, shared_ptr<const Instances>()> func_childrenOld2(&Instance::getChildren2, "getChildren", Security::None);
static Reflection::BoundFuncDesc<Instance, shared_ptr<const Instances>()> func_children(&Instance::getChildren2, "GetChildren", Security::None);
static Reflection::BoundFuncDesc<Instance, std::string()> func_GetFullName(&Instance::getFullNameForReflection, "GetFullName", Security::None);
static Reflection::BoundFuncDesc<Instance, bool(shared_ptr<Instance>)> func_isDescendantOf(&Instance::isDescendantOf2, "IsDescendantOf", "ancestor", Security::None);
static Reflection::BoundFuncDesc<Instance, bool(shared_ptr<Instance>)> dep_isDescendantOf(&Instance::isDescendantOf2, "isDescendantOf", "ancestor", Security::None, Reflection::PropertyDescriptor::Attributes::deprecated(func_isDescendantOf));
static Reflection::BoundFuncDesc<Instance, bool(shared_ptr<Instance>)> func_isAncestorOf(&Instance::isAncestorOf2, "IsAncestorOf", "descendant", Security::None);
static Reflection::BoundFuncDesc<Instance, std::string(int)> func_GetReadableId(&Instance::getReadableDebugId, "GetDebugId", "scopeLength", 4, Security::Plugin);
static Reflection::PropDescriptor<Instance, std::string> prop_className("ClassName", category_Data, &Instance::getClassNameStr, NULL, Reflection::PropertyDescriptor::UI);
static Reflection::PropDescriptor<Instance, std::string> prop_classNameDeprecated("className", category_Data, &Instance::getClassNameStr, NULL, Reflection::PropertyDescriptor::Attributes::deprecated(prop_className));
static Reflection::PropDescriptor<Instance, int> prop_dataCost("DataCost", category_Data, &Instance::getPersistentDataCost, NULL, Reflection::PropertyDescriptor::UI, Security::RobloxPlace);
static Reflection::BoundYieldFuncDesc<Instance, shared_ptr<Instance>(std::string)> func_WaitForChild(&Instance::waitForChild, "WaitForChild", "childName", Security::None);
const Reflection::PropDescriptor<Instance, std::string> Instance::desc_Name("Name", category_Data, &Instance::getName, &Instance::setName);
const Reflection::RefPropDescriptor<Instance, Instance> Instance::propParent("Parent", category_Data, &Instance::getParentDangerous, &Instance::setParent, Reflection::PropertyDescriptor::UI);
const Reflection::PropDescriptor<Instance, bool> Instance::propRobloxLocked("RobloxLocked", category_Data, &Instance::getRobloxLocked, &Instance::setRobloxLocked, Reflection::PropertyDescriptor::SCRIPTING, Security::Plugin);
Reflection::EventDesc<
Instance,
void(shared_ptr<Instance>),
rbx::signal<void(shared_ptr<Instance>)>,
rbx::signal<void(shared_ptr<Instance>)>* (Instance::*)(bool)>
event_childAdded(&Instance::getOrCreateChildAddedSignal, "ChildAdded", "child");
Reflection::EventDesc<
Instance,
void(shared_ptr<Instance>),
rbx::signal<void(shared_ptr<Instance>)>,
rbx::signal<void(shared_ptr<Instance>)>* (Instance::*)(bool)>
dep_childAdded(&Instance::getOrCreateChildAddedSignal, "childAdded", "child", Reflection::Descriptor::Attributes::deprecated(event_childAdded));
Reflection::EventDesc<
Instance,
void(shared_ptr<Instance>),
rbx::signal<void(shared_ptr<Instance>)>,
rbx::signal<void(shared_ptr<Instance>)>* (Instance::*)(bool)>
event_childRemoved(&Instance::getOrCreateChildRemovedSignal, "ChildRemoved", "child");
Reflection::EventDesc<
Instance,
void(shared_ptr<Instance>),
rbx::signal<void(shared_ptr<Instance>)>,
rbx::signal<void(shared_ptr<Instance>)>* (Instance::*)(bool)>
event_descendantAdded(&Instance::getOrCreateDescendantAddedSignal, "DescendantAdded", "descendant");
Reflection::EventDesc<
Instance,
void(shared_ptr<Instance>),
rbx::signal<void(shared_ptr<Instance>)>,
rbx::signal<void(shared_ptr<Instance>)>* (Instance::*)(bool)>
event_descendantRemoving(&Instance::getOrCreateDescendantRemovingSignal, "DescendantRemoving", "descendant");
Reflection::EventDesc<Instance, void(shared_ptr<Instance>, shared_ptr<Instance>)> event_ancestryChanged(&Instance::ancestryChangedSignal, "AncestryChanged", "child", "parent");
Reflection::EventDesc<Instance, void(const Reflection::PropertyDescriptor*)> event_propertyChanged(&Instance::propertyChangedSignal, "Changed", "property");
REFLECTION_END();
shared_ptr<Instance> Instance::createChild(const RBX::Name& className, RBX::CreatorRole creatorRole)
{
return Creatable<Instance>::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<Instance> 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<bool(Instance*)>& 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<bool(Instance*)>& 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> 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<void(shared_ptr<Instance>)> resumeFunction, boost::function<void(std::string)> 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<OnDemandInstance::ThreadWaitingForChild>& waitingThreads = parent->onDemandWrite()->threadsWaitingForChildren;
std::vector<OnDemandInstance::ThreadWaitingForChild>::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> Instance::findFirstAncestorOf(const Instance* descendant) const
{
if (!children)
return shared_ptr<Instance>();
const Instances& c(*children);
for (size_t i = 0; i < c.size(); ++i) {
if (c[i]->isAncestorOf(descendant)) {
return c[i];
}
}
return shared_ptr<Instance>();
}
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<Instance> oldParent = shared_from(parent);
// Keep a lock on ourself until we exit the function!
shared_ptr<Instance> 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<Instances>& 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<kCallCheckCallArg, callCheckSetBasicFlag<HATE_RETURN_CHECK> >(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<const Instances> 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<const Instances> 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, 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<const Instances> 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>& 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<const Instance*, shared_ptr<Instance> > Map;
Map map;
const CreatorRole creatorRole;
shared_ptr<Instance> clone;
public:
// The constructor does the cloning
Cloner(const Instance* instance, CreatorRole creatorRole):creatorRole(creatorRole)
{
shared_ptr<const Instance> 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<Instance> getClone() const
{
return clone;
}
private:
// This function simply duplicates all the instances, but doesn't create the tree nor copy properties
shared_ptr<Instance> createClones(shared_ptr<const Instance> instance)
{
RBXASSERT(instance);
if (!instance->getDescriptor().isSerializable())
return shared_ptr<Instance>();
if (!instance->getIsArchivable())
return shared_ptr<Instance>();
shared_ptr<Instance> copy = Creatable<Instance>::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<const Reflection::RefPropertyDescriptor*>(&prop.getDescriptor());
RBXASSERT(refDesc);
Instance* value = static_cast<Instance*>(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<const Instance> 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<Instance>& 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<RBX::Instance*>(source)->onDemandWrite()->instanceClonedSignal(value.second);
}
};
shared_ptr<Instance> Instance::clone(CreatorRole creatorRole)
{
return Cloner(this, creatorRole).getClone();
}
shared_ptr<Instance> 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<const Instances> 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<const Instances> c(children.read());
boost::shared_ptr<Instance> 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<Reflection::EventDescriptor>(), getDescriptor().end<Reflection::EventDescriptor>(),
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<kCallCheckCodeOnly, callCheckSetApiFlag<kRbxLockedApiOffset> >(
reinterpret_cast<void*>((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<Instance> child(children->back());
child->remove();
}
}
void Instance::destroyAllChildrenLua()
{
// security that results in a kick
void (Instance::* thisFunction)() = &Instance::destroyAllChildrenLua;
checkRbxCaller<kCallCheckRegCall, callCheckSetBasicFlag<HATE_DESTROY_ALL> >(
reinterpret_cast<void*>((void*&)(thisFunction)));
while (children)
{
shared_ptr<Instance> child(children->back());
// this prevents these commands from actually being processed if incorrectly called.
if ( !checkRbxCaller<kCallCheckCodeOnly, callCheckNop >(reinterpret_cast<void*>((void*&)(thisFunction))) )
{
child->destroy();
}
}
}
void Instance::predelete(Instance* instance)
{
instance->predelete();
}
void Instance::predelete()
{
RBXASSERT(parent==NULL);
while (children)
{
shared_ptr<Instance> child;
{
// get the last element and pop it off (more efficient that way)
shared_ptr<Instances> 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<ServiceProvider>(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;
}
}