1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-03-03 15:09:39 +00:00

fixes a crash (#3183)

This PR fixes a crash caused by the improperly ensured lifetime of RigGeometry::mSourceGeometry. mSourceGeometry was not adequate to ensure mSourceGeometry would outlive mGeometry because we extend mGeometrys lifetime beyond this lifetime by passing mGeometry to the draw traversal instead of this.
In addition,

We add important comments.
We detect and prevent generally unsafe operations in high level code.
We add a sprinkling of const to help clarify intentions.
This commit is contained in:
Bo Svensson 2021-10-23 08:31:46 +00:00 committed by GitHub
parent f2e43272e4
commit c9c8d02332
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 86 additions and 32 deletions

View file

@ -111,7 +111,7 @@ namespace CSVRender
if (!mesh.empty() && node != mNodeMap.end()) if (!mesh.empty() && node != mNodeMap.end())
{ {
auto instance = sceneMgr->getInstance(mesh); auto instance = sceneMgr->getInstance(mesh);
SceneUtil::attach(instance, mSkeleton, boneName, node->second); SceneUtil::attach(instance, mSkeleton, boneName, node->second, sceneMgr);
} }
} }

View file

@ -1319,10 +1319,10 @@ namespace MWRender
cache.insert(std::make_pair(model, created)); cache.insert(std::make_pair(model, created));
return sceneMgr->createInstance(created); return sceneMgr->getInstance(created);
} }
else else
return sceneMgr->createInstance(found->second); return sceneMgr->getInstance(found->second);
} }
else else
{ {

View file

@ -164,7 +164,7 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot)
NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(bonename)); NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(bonename));
if (found == nodeMap.end()) if (found == nodeMap.end())
throw std::runtime_error("Can't find attachment node " + bonename); throw std::runtime_error("Can't find attachment node " + bonename);
osg::ref_ptr<osg::Node> attached = SceneUtil::attach(node, mObjectRoot, bonename, found->second.get()); osg::ref_ptr<osg::Node> attached = SceneUtil::attach(node, mObjectRoot, bonename, found->second.get(), mResourceSystem->getSceneManager());
scene.reset(new PartHolder(attached)); scene.reset(new PartHolder(attached));

View file

@ -717,7 +717,7 @@ PartHolderPtr NpcAnimation::insertBoundedPart(const std::string& model, const st
if (found == nodeMap.end()) if (found == nodeMap.end())
throw std::runtime_error("Can't find attachment node " + bonename); throw std::runtime_error("Can't find attachment node " + bonename);
osg::ref_ptr<osg::Node> attached = SceneUtil::attach(templateNode, mObjectRoot, bonefilter, found->second); osg::ref_ptr<osg::Node> attached = SceneUtil::attach(templateNode, mObjectRoot, bonefilter, found->second, mResourceSystem->getSceneManager());
if (enchantedGlow) if (enchantedGlow)
mGlowUpdater = SceneUtil::addEnchantedGlow(attached, mResourceSystem, *glowColor); mGlowUpdater = SceneUtil::addEnchantedGlow(attached, mResourceSystem, *glowColor);

View file

@ -617,6 +617,10 @@ namespace MWRender
pat->setAttitude(nodeAttitude); pat->setAttitude(nodeAttitude);
} }
// DO NOT COPY AND PASTE THIS CODE. Cloning osg::Geometry without also cloning its contained Arrays is generally unsafe.
// In this specific case the operation is safe under the following two assumptions:
// - When Arrays are removed or replaced in the cloned geometry, the original Arrays in their place must outlive the cloned geometry regardless. (ensured by TemplateMultiRef)
// - Arrays that we add or replace in the cloned geometry must be explicitely forbidden from reusing BufferObjects of the original geometry. (ensured by needvbo() in optimizer.cpp)
copyop.setCopyFlags(merge ? osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES : osg::CopyOp::DEEP_COPY_NODES); copyop.setCopyFlags(merge ? osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES : osg::CopyOp::DEEP_COPY_NODES);
copyop.mOptimizeBillboards = (size > 1/4.f); copyop.mOptimizeBillboards = (size > 1/4.f);
copyop.mNodePath.push_back(trans); copyop.mNodePath.push_back(trans);
@ -645,7 +649,8 @@ namespace MWRender
} }
if (numinstances > 0) if (numinstances > 0)
{ {
// add a ref to the original template, to hint to the cache that it's still being used and should be kept in cache // add a ref to the original template to help verify the safety of shallow cloning operations
// in addition, we hint to the cache that it's still being used and should be kept in cache
templateRefs->addRef(cnode); templateRefs->addRef(cnode);
if (pair.second.mNeedCompile) if (pair.second.mNeedCompile)

View file

@ -693,19 +693,33 @@ namespace Resource
} }
} }
osg::ref_ptr<osg::Node> SceneManager::createInstance(const std::string& name) osg::ref_ptr<osg::Node> SceneManager::getInstance(const std::string& name)
{ {
osg::ref_ptr<const osg::Node> scene = getTemplate(name); osg::ref_ptr<const osg::Node> scene = getTemplate(name);
return createInstance(scene); return getInstance(scene);
} }
osg::ref_ptr<osg::Node> SceneManager::createInstance(const osg::Node *base) osg::ref_ptr<osg::Node> SceneManager::cloneNode(const osg::Node* base)
{ {
osg::ref_ptr<osg::Node> cloned = static_cast<osg::Node*>(base->clone(SceneUtil::CopyOp())); SceneUtil::CopyOp copyop;
if (const osg::Drawable* drawable = base->asDrawable())
// add a ref to the original template, to hint to the cache that it's still being used and should be kept in cache {
if (drawable->asGeometry())
{
Log(Debug::Warning) << "SceneManager::cloneNode: attempting to clone osg::Geometry. For safety reasons this will be expensive. Consider avoiding this call.";
copyop.setCopyFlags(copyop.getCopyFlags()|osg::CopyOp::DEEP_COPY_ARRAYS|osg::CopyOp::DEEP_COPY_PRIMITIVES);
}
}
osg::ref_ptr<osg::Node> cloned = static_cast<osg::Node*>(base->clone(copyop));
// add a ref to the original template to help verify the safety of shallow cloning operations
// in addition, if this node is managed by a cache, we hint to the cache that it's still being used and should be kept in cache
cloned->getOrCreateUserDataContainer()->addUserObject(new TemplateRef(base)); cloned->getOrCreateUserDataContainer()->addUserObject(new TemplateRef(base));
return cloned;
}
osg::ref_ptr<osg::Node> SceneManager::getInstance(const osg::Node *base)
{
osg::ref_ptr<osg::Node> cloned = cloneNode(base);
// we can skip any scene graphs without update callbacks since we know that particle emitters will have an update callback set // we can skip any scene graphs without update callbacks since we know that particle emitters will have an update callback set
if (cloned->getNumChildrenRequiringUpdateTraversal() > 0) if (cloned->getNumChildrenRequiringUpdateTraversal() > 0)
{ {
@ -716,11 +730,6 @@ namespace Resource
return cloned; return cloned;
} }
osg::ref_ptr<osg::Node> SceneManager::getInstance(const std::string &name)
{
return createInstance(name);
}
osg::ref_ptr<osg::Node> SceneManager::getInstance(const std::string &name, osg::Group* parentNode) osg::ref_ptr<osg::Node> SceneManager::getInstance(const std::string &name, osg::Group* parentNode)
{ {
osg::ref_ptr<osg::Node> cloned = getInstance(name); osg::ref_ptr<osg::Node> cloned = getInstance(name);

View file

@ -132,16 +132,22 @@ namespace Resource
/// @note Thread safe. /// @note Thread safe.
osg::ref_ptr<const osg::Node> getTemplate(const std::string& name, bool compile=true); osg::ref_ptr<const osg::Node> getTemplate(const std::string& name, bool compile=true);
osg::ref_ptr<osg::Node> createInstance(const std::string& name); /// Clone osg::Node safely.
/// @note Thread safe.
static osg::ref_ptr<osg::Node> cloneNode(const osg::Node* base);
osg::ref_ptr<osg::Node> createInstance(const osg::Node* base);
void shareState(osg::ref_ptr<osg::Node> node); void shareState(osg::ref_ptr<osg::Node> node);
/// Get an instance of the given scene template
/// Clone osg::Node and adjust it according to SceneManager's settings.
/// @note Thread safe.
osg::ref_ptr<osg::Node> getInstance(const osg::Node* base);
/// Instance the given scene template.
/// @see getTemplate /// @see getTemplate
/// @note Thread safe. /// @note Thread safe.
osg::ref_ptr<osg::Node> getInstance(const std::string& name); osg::ref_ptr<osg::Node> getInstance(const std::string& name);
/// Get an instance of the given scene template and immediately attach it to a parent node /// Instance the given scene template and immediately attach it to a parent node
/// @see getTemplate /// @see getTemplate
/// @note Not thread safe, unless parentNode is not part of the main scene graph yet. /// @note Not thread safe, unless parentNode is not part of the main scene graph yet.
osg::ref_ptr<osg::Node> getInstance(const std::string& name, osg::Group* parentNode); osg::ref_ptr<osg::Node> getInstance(const std::string& name, osg::Group* parentNode);

View file

@ -13,9 +13,9 @@
#include <components/misc/stringops.hpp> #include <components/misc/stringops.hpp>
#include <components/sceneutil/skeleton.hpp> #include <components/sceneutil/skeleton.hpp>
#include <components/resource/scenemanager.hpp>
#include "visitor.hpp" #include "visitor.hpp"
#include "clone.hpp"
namespace SceneUtil namespace SceneUtil
{ {
@ -49,10 +49,10 @@ namespace SceneUtil
if (!filterMatches(drawable.getName())) if (!filterMatches(drawable.getName()))
return; return;
osg::Node* node = &drawable; const osg::Node* node = &drawable;
for (auto it = getNodePath().rbegin()+1; it != getNodePath().rend(); ++it) for (auto it = getNodePath().rbegin()+1; it != getNodePath().rend(); ++it)
{ {
osg::Node* parent = *it; const osg::Node* parent = *it;
if (!filterMatches(parent->getName())) if (!filterMatches(parent->getName()))
break; break;
node = parent; node = parent;
@ -60,11 +60,11 @@ namespace SceneUtil
mToCopy.emplace(node); mToCopy.emplace(node);
} }
void doCopy() void doCopy(Resource::SceneManager* sceneManager)
{ {
for (const osg::ref_ptr<osg::Node>& node : mToCopy) for (const osg::ref_ptr<const osg::Node>& node : mToCopy)
{ {
mParent->addChild(static_cast<osg::Node*>(node->clone(SceneUtil::CopyOp()))); mParent->addChild(sceneManager->getInstance(node));
} }
mToCopy.clear(); mToCopy.clear();
} }
@ -78,7 +78,7 @@ namespace SceneUtil
|| (lowerName.size() >= mFilter2.size() && lowerName.compare(0, mFilter2.size(), mFilter2) == 0); || (lowerName.size() >= mFilter2.size() && lowerName.compare(0, mFilter2.size(), mFilter2) == 0);
} }
using NodeSet = std::set<osg::ref_ptr<osg::Node>>; using NodeSet = std::set<osg::ref_ptr<const osg::Node>>;
NodeSet mToCopy; NodeSet mToCopy;
osg::ref_ptr<osg::Group> mParent; osg::ref_ptr<osg::Group> mParent;
@ -100,7 +100,7 @@ namespace SceneUtil
} }
} }
osg::ref_ptr<osg::Node> attach(osg::ref_ptr<const osg::Node> toAttach, osg::Node *master, const std::string &filter, osg::Group* attachNode) osg::ref_ptr<osg::Node> attach(osg::ref_ptr<const osg::Node> toAttach, osg::Node *master, const std::string &filter, osg::Group* attachNode, Resource::SceneManager* sceneManager)
{ {
if (dynamic_cast<const SceneUtil::Skeleton*>(toAttach.get())) if (dynamic_cast<const SceneUtil::Skeleton*>(toAttach.get()))
{ {
@ -108,7 +108,9 @@ namespace SceneUtil
CopyRigVisitor copyVisitor(handle, filter); CopyRigVisitor copyVisitor(handle, filter);
const_cast<osg::Node*>(toAttach.get())->accept(copyVisitor); const_cast<osg::Node*>(toAttach.get())->accept(copyVisitor);
copyVisitor.doCopy(); copyVisitor.doCopy(sceneManager);
// add a ref to the original template to hint to the cache that it is still being used and should be kept in cache.
handle->getOrCreateUserDataContainer()->addUserObject(new Resource::TemplateRef(toAttach));
if (handle->getNumChildren() == 1) if (handle->getNumChildren() == 1)
{ {
@ -127,7 +129,7 @@ namespace SceneUtil
} }
else else
{ {
osg::ref_ptr<osg::Node> clonedToAttach = static_cast<osg::Node*>(toAttach->clone(SceneUtil::CopyOp())); osg::ref_ptr<osg::Node> clonedToAttach = sceneManager->getInstance(toAttach);
FindByNameVisitor findBoneOffset("BoneOffset"); FindByNameVisitor findBoneOffset("BoneOffset");
clonedToAttach->accept(findBoneOffset); clonedToAttach->accept(findBoneOffset);

View file

@ -10,6 +10,10 @@ namespace osg
class Node; class Node;
class Group; class Group;
} }
namespace Resource
{
class SceneManager;
}
namespace SceneUtil namespace SceneUtil
{ {
@ -19,7 +23,7 @@ namespace SceneUtil
/// Otherwise, just attach all of the toAttach scenegraph to the attachment node on the master scenegraph, with no filtering. /// Otherwise, just attach all of the toAttach scenegraph to the attachment node on the master scenegraph, with no filtering.
/// @note The master scene graph is expected to include a skeleton. /// @note The master scene graph is expected to include a skeleton.
/// @return A newly created node that is directly attached to the master scene graph /// @return A newly created node that is directly attached to the master scene graph
osg::ref_ptr<osg::Node> attach(osg::ref_ptr<const osg::Node> toAttach, osg::Node* master, const std::string& filter, osg::Group* attachNode); osg::ref_ptr<osg::Node> attach(osg::ref_ptr<const osg::Node> toAttach, osg::Node* master, const std::string& filter, osg::Group* attachNode, Resource::SceneManager *sceneManager);
} }

View file

@ -18,6 +18,7 @@ namespace SceneUtil
/// @par Defines the cloning behaviour we need: /// @par Defines the cloning behaviour we need:
/// * Assigns updated ParticleSystem pointers on cloned emitters and programs. /// * Assigns updated ParticleSystem pointers on cloned emitters and programs.
/// * Deep copies RigGeometry and MorphGeometry so they can animate without affecting clones. /// * Deep copies RigGeometry and MorphGeometry so they can animate without affecting clones.
/// @warning Avoid using this class directly. The safety of cloning operations depends on the copy flags and the objects involved. Consider using SceneManager::cloneNode for additional safety.
/// @warning Do not use an object of this class for more than one copy operation. /// @warning Do not use an object of this class for more than one copy operation.
class CopyOp : public osg::CopyOp class CopyOp : public osg::CopyOp
{ {

View file

@ -1,6 +1,7 @@
#include "morphgeometry.hpp" #include "morphgeometry.hpp"
#include <cassert> #include <cassert>
#include <components/resource/scenemanager.hpp>
#include <osg/Version> #include <osg/Version>
@ -27,11 +28,19 @@ MorphGeometry::MorphGeometry(const MorphGeometry &copy, const osg::CopyOp &copyo
void MorphGeometry::setSourceGeometry(osg::ref_ptr<osg::Geometry> sourceGeom) void MorphGeometry::setSourceGeometry(osg::ref_ptr<osg::Geometry> sourceGeom)
{ {
for (unsigned int i=0; i<2; ++i)
mGeometry[i] = nullptr;
mSourceGeometry = sourceGeom; mSourceGeometry = sourceGeom;
for (unsigned int i=0; i<2; ++i) for (unsigned int i=0; i<2; ++i)
{ {
// DO NOT COPY AND PASTE THIS CODE. Cloning osg::Geometry without also cloning its contained Arrays is generally unsafe.
// In this specific case the operation is safe under the following two assumptions:
// - When Arrays are removed or replaced in the cloned geometry, the original Arrays in their place must outlive the cloned geometry regardless. (ensured by TemplateRef)
// - Arrays that we add or replace in the cloned geometry must be explicitely forbidden from reusing BufferObjects of the original geometry. (ensured by vbo below)
mGeometry[i] = new osg::Geometry(*mSourceGeometry, osg::CopyOp::SHALLOW_COPY); mGeometry[i] = new osg::Geometry(*mSourceGeometry, osg::CopyOp::SHALLOW_COPY);
mGeometry[i]->getOrCreateUserDataContainer()->addUserObject(new Resource::TemplateRef(mSourceGeometry));
const osg::Geometry& from = *mSourceGeometry; const osg::Geometry& from = *mSourceGeometry;
osg::Geometry& to = *mGeometry[i]; osg::Geometry& to = *mGeometry[i];

View file

@ -3,6 +3,7 @@
#include <osg/Version> #include <osg/Version>
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
#include <components/resource/scenemanager.hpp>
#include <osg/MatrixTransform> #include <osg/MatrixTransform>
#include "skeleton.hpp" #include "skeleton.hpp"
@ -60,12 +61,22 @@ RigGeometry::RigGeometry(const RigGeometry &copy, const osg::CopyOp &copyop)
void RigGeometry::setSourceGeometry(osg::ref_ptr<osg::Geometry> sourceGeometry) void RigGeometry::setSourceGeometry(osg::ref_ptr<osg::Geometry> sourceGeometry)
{ {
for (unsigned int i=0; i<2; ++i)
mGeometry[i] = nullptr;
mSourceGeometry = sourceGeometry; mSourceGeometry = sourceGeometry;
for (unsigned int i=0; i<2; ++i) for (unsigned int i=0; i<2; ++i)
{ {
const osg::Geometry& from = *sourceGeometry; const osg::Geometry& from = *sourceGeometry;
// DO NOT COPY AND PASTE THIS CODE. Cloning osg::Geometry without also cloning its contained Arrays is generally unsafe.
// In this specific case the operation is safe under the following two assumptions:
// - When Arrays are removed or replaced in the cloned geometry, the original Arrays in their place must outlive the cloned geometry regardless. (ensured by mSourceGeometry)
// - Arrays that we add or replace in the cloned geometry must be explicitely forbidden from reusing BufferObjects of the original geometry. (ensured by vbo below)
mGeometry[i] = new osg::Geometry(from, osg::CopyOp::SHALLOW_COPY); mGeometry[i] = new osg::Geometry(from, osg::CopyOp::SHALLOW_COPY);
mGeometry[i]->getOrCreateUserDataContainer()->addUserObject(new Resource::TemplateRef(mSourceGeometry));
osg::Geometry& to = *mGeometry[i]; osg::Geometry& to = *mGeometry[i];
to.setSupportsDisplayList(false); to.setSupportsDisplayList(false);
to.setUseVertexBufferObjects(true); to.setUseVertexBufferObjects(true);

View file

@ -9,6 +9,13 @@ namespace SceneUtil
class Skeleton; class Skeleton;
class Bone; class Bone;
// TODO: This class has a lot of issues.
// - We require too many workarounds to ensure safety.
// - mSourceGeometry should be const, but can not be const because of a use case in shadervisitor.cpp.
// - We create useless mGeometry clones in template RigGeometries.
// - We do not support compileGLObjects.
// - We duplicate some code in MorphGeometry.
/// @brief Mesh skinning implementation. /// @brief Mesh skinning implementation.
/// @note A RigGeometry may be attached directly to a Skeleton, or somewhere below a Skeleton. /// @note A RigGeometry may be attached directly to a Skeleton, or somewhere below a Skeleton.
/// Note though that the RigGeometry ignores any transforms below the Skeleton, so the attachment point is not that important. /// Note though that the RigGeometry ignores any transforms below the Skeleton, so the attachment point is not that important.