Skip to content

Commit 9bf1549

Browse files
Quincunx271paceholder
authored andcommitted
Simplify registry code (paceholder#171)
1 parent 60261d7 commit 9bf1549

File tree

8 files changed

+176
-38
lines changed

8 files changed

+176
-38
lines changed

.travis.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,12 @@ matrix:
2323
dist: xenial
2424
sudo: false
2525
compiler: gcc
26-
env: CXX=g++-7 CC=gcc-7 QT=512
26+
env:
27+
- CXX=g++-7 CC=gcc-7 QT=512
28+
- CXXFLAGS="-fsanitize=address -fno-omit-frame-pointer"
29+
- LDFLAGS=-fsanitize=address
30+
# Too many false positive leaks:
31+
- ASAN_OPTIONS=detect_leaks=0
2732
addons:
2833
apt:
2934
sources:

include/nodes/internal/DataModelRegistry.hpp

Lines changed: 54 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
#pragma once
22

3-
#include <set>
4-
#include <memory>
53
#include <functional>
4+
#include <memory>
5+
#include <set>
6+
#include <type_traits>
67
#include <unordered_map>
8+
#include <utility>
79
#include <vector>
810

911
#include <QtCore/QString>
@@ -55,21 +57,40 @@ class NODE_EDITOR_PUBLIC DataModelRegistry
5557
void registerModel(RegistryItemCreator creator,
5658
QString const &category = "Nodes")
5759
{
58-
registerModelImpl<ModelType>(std::move(creator), category);
60+
const QString name = computeName<ModelType>(HasStaticMethodName<ModelType>{}, creator);
61+
if (!_registeredItemCreators.count(name))
62+
{
63+
_registeredItemCreators[name] = std::move(creator);
64+
_categories.insert(category);
65+
_registeredModelsCategory[name] = category;
66+
}
5967
}
6068

6169
template<typename ModelType>
6270
void registerModel(QString const &category = "Nodes")
6371
{
6472
RegistryItemCreator creator = [](){ return std::make_unique<ModelType>(); };
65-
registerModelImpl<ModelType>(std::move(creator), category);
73+
registerModel<ModelType>(std::move(creator), category);
6674
}
6775

6876
template<typename ModelType>
6977
void registerModel(QString const &category,
7078
RegistryItemCreator creator)
7179
{
72-
registerModelImpl<ModelType>(std::move(creator), category);
80+
registerModel<ModelType>(std::move(creator), category);
81+
}
82+
83+
template <typename ModelCreator>
84+
void registerModel(ModelCreator&& creator, QString const& category = "Nodes")
85+
{
86+
using ModelType = compute_model_type_t<decltype(creator())>;
87+
registerModel<ModelType>(std::forward<ModelCreator>(creator), category);
88+
}
89+
90+
template <typename ModelCreator>
91+
void registerModel(QString const& category, ModelCreator&& creator)
92+
{
93+
registerModel(std::forward<ModelCreator>(creator), category);
7394
}
7495

7596
void registerTypeConverter(TypeConverterId const & id,
@@ -120,32 +141,40 @@ class NODE_EDITOR_PUBLIC DataModelRegistry
120141
: std::true_type
121142
{};
122143

123-
template<typename ModelType>
124-
typename std::enable_if< HasStaticMethodName<ModelType>::value>::type
125-
registerModelImpl(RegistryItemCreator creator, QString const &category )
144+
template <typename ModelType>
145+
static QString
146+
computeName(std::true_type, RegistryItemCreator const&)
126147
{
127-
const QString name = ModelType::Name();
128-
if (_registeredItemCreators.count(name) == 0)
129-
{
130-
_registeredItemCreators[name] = std::move(creator);
131-
_categories.insert(category);
132-
_registeredModelsCategory[name] = category;
133-
}
148+
return ModelType::Name();
134149
}
135150

136-
template<typename ModelType>
137-
typename std::enable_if< !HasStaticMethodName<ModelType>::value>::type
138-
registerModelImpl(RegistryItemCreator creator, QString const &category )
151+
template <typename ModelType>
152+
static QString
153+
computeName(std::false_type, RegistryItemCreator const& creator)
139154
{
140-
const QString name = creator()->name();
141-
if (_registeredItemCreators.count(name) == 0)
142-
{
143-
_registeredItemCreators[name] = std::move(creator);
144-
_categories.insert(category);
145-
_registeredModelsCategory[name] = category;
146-
}
155+
return creator()->name();
147156
}
148157

158+
template <typename T>
159+
struct UnwrapUniquePtr
160+
{
161+
// Assert always fires, but the compiler doesn't know this:
162+
static_assert(!std::is_same<T, T>::value,
163+
"The ModelCreator must return a std::unique_ptr<T>, where T "
164+
"inherits from NodeDataModel");
165+
};
166+
167+
template <typename T>
168+
struct UnwrapUniquePtr<std::unique_ptr<T>>
169+
{
170+
static_assert(std::is_base_of<NodeDataModel, T>::value,
171+
"The ModelCreator must return a std::unique_ptr<T>, where T "
172+
"inherits from NodeDataModel");
173+
using type = T;
174+
};
175+
176+
template <typename CreatorResult>
177+
using compute_model_type_t = typename UnwrapUniquePtr<CreatorResult>::type;
149178
};
150179

151180

include/nodes/internal/FlowScene.hpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,14 @@ class NODE_EDITOR_PUBLIC FlowScene
139139
using SharedConnection = std::shared_ptr<Connection>;
140140
using UniqueNode = std::unique_ptr<Node>;
141141

142+
// DO NOT reorder this member to go after the others.
143+
// This should outlive all the connections and nodes of
144+
// the graph, so that nodes can potentially have pointers into it,
145+
// which is why it comes first in the class.
146+
std::shared_ptr<DataModelRegistry> _registry;
147+
142148
std::unordered_map<QUuid, SharedConnection> _connections;
143149
std::unordered_map<QUuid, UniqueNode> _nodes;
144-
std::shared_ptr<DataModelRegistry> _registry;
145150

146151
private Q_SLOTS:
147152

src/Connection.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,11 @@ Connection(Node& nodeIn,
6666
Connection::
6767
~Connection()
6868
{
69-
if (complete()) connectionMadeIncomplete(*this);
69+
if (complete())
70+
{
71+
connectionMadeIncomplete(*this);
72+
}
73+
7074
propagateEmptyData();
7175

7276
if (_inNode)
@@ -356,7 +360,8 @@ void
356360
Connection::
357361
clearNode(PortType portType)
358362
{
359-
if (complete()) {
363+
if (complete())
364+
{
360365
connectionMadeIncomplete(*this);
361366
}
362367

src/FlowScene.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,8 @@ FlowScene::
185185
deleteConnection(Connection& connection)
186186
{
187187
auto it = _connections.find(connection.id());
188-
if (it != _connections.end()) {
188+
if (it != _connections.end())
189+
{
189190
connection.removeFromNodes();
190191
_connections.erase(it);
191192
}

test/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ add_executable(test_nodes
55
test_main.cpp
66
src/TestDragging.cpp
77
src/TestDataModelRegistry.cpp
8-
src/TestNodeGraphicsObject.cpp
98
src/TestFlowScene.cpp
9+
src/TestNodeGraphicsObject.cpp
1010
)
1111

1212
target_include_directories(test_nodes

test/src/TestDataModelRegistry.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,31 @@ TEST_CASE("DataModelRegistry::registerModel", "[interface]")
4242

4343
CHECK(model->name() == "name");
4444
}
45+
SECTION("From model creator function")
46+
{
47+
SECTION("non-static name()")
48+
{
49+
registry.registerModel([] {
50+
return std::make_unique<StubNodeDataModel>();
51+
});
52+
53+
auto model = registry.create("name");
54+
55+
REQUIRE(model != nullptr);
56+
CHECK(model->name() == "name");
57+
CHECK(dynamic_cast<StubNodeDataModel*>(model.get()));
58+
}
59+
SECTION("static Name()")
60+
{
61+
registry.registerModel([] {
62+
return std::make_unique<StubModelStaticName>();
63+
});
64+
65+
auto model = registry.create("Name");
66+
67+
REQUIRE(model != nullptr);
68+
CHECK(model->name() == "name");
69+
CHECK(dynamic_cast<StubModelStaticName*>(model.get()));
70+
}
71+
}
4572
}

test/src/TestFlowScene.cpp

Lines changed: 73 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -125,15 +125,19 @@ TEST_CASE("FlowScene triggers connections created or deleted", "[gui]")
125125
struct Deletion
126126
{
127127
std::string name;
128-
std::function<void(std::shared_ptr<Connection>)> deleteConnection;
128+
std::function<void(Connection & connection)> deleteConnection;
129129
};
130130

131131
Deletion sceneDeletion{"scene.deleteConnection",
132-
[&](auto c) { scene.deleteConnection(*c); }};
132+
[&](Connection & c) { scene.deleteConnection(c); }};
133133

134134
Deletion partialDragDeletion{"scene-deleteConnectionByDraggingOff",
135-
[&](auto connection) {
136-
connection->clearNode(PortType::In);
135+
[&](Connection & c)
136+
{
137+
PortIndex portIndex = c.getPortIndex(PortType::In);
138+
Node * node = c.getNode(PortType::In);
139+
node->nodeState().getEntries(PortType::In)[portIndex].clear();
140+
c.clearNode(PortType::In);
137141
}};
138142

139143
SECTION("creating a connection")
@@ -168,13 +172,14 @@ TEST_CASE("FlowScene triggers connections created or deleted", "[gui]")
168172
{
169173
SECTION("deletion: " + deletion.name)
170174
{
171-
auto connection = sceneCreation.createConnection();
172-
std::weak_ptr<Connection> weakConnection = connection;
175+
Connection & connection = *sceneCreation.createConnection();
173176

174177
from.resetCallCounts();
175178
to.resetCallCounts();
176179

177-
deletion.deleteConnection(std::move(connection));
180+
deletion.deleteConnection(connection);
181+
182+
// Here the Connection reference becomes dangling
178183

179184
CHECK(from.inputDeletedCalledCount == 0);
180185
CHECK(from.outputDeletedCalledCount == 1);
@@ -188,3 +193,64 @@ TEST_CASE("FlowScene triggers connections created or deleted", "[gui]")
188193
}
189194
}
190195
}
196+
197+
198+
TEST_CASE("FlowScene's DataModelRegistry outlives nodes and connections", "[asan][gui]")
199+
{
200+
class MockDataModel : public StubNodeDataModel
201+
{
202+
public:
203+
MockDataModel(int* const& incrementOnDestruction)
204+
: incrementOnDestruction(incrementOnDestruction)
205+
{
206+
}
207+
208+
~MockDataModel()
209+
{
210+
(*incrementOnDestruction)++;
211+
}
212+
213+
// The reference ensures that we point into the memory that would be free'd
214+
// if the DataModelRegistry doesn't outlive this node
215+
int* const& incrementOnDestruction;
216+
};
217+
218+
struct MockDataModelCreator
219+
{
220+
MockDataModelCreator(int* shouldBeAliveWhenAssignedTo)
221+
: shouldBeAliveWhenAssignedTo(shouldBeAliveWhenAssignedTo)
222+
{
223+
}
224+
225+
auto
226+
operator()() const
227+
{
228+
return std::make_unique<MockDataModel>(shouldBeAliveWhenAssignedTo);
229+
}
230+
231+
int* shouldBeAliveWhenAssignedTo;
232+
};
233+
234+
int modelsDestroyed = 0;
235+
236+
// Introduce a new scope, so that modelsDestroyed will be alive even after the
237+
// FlowScene is destroyed.
238+
{
239+
auto setup = applicationSetup();
240+
241+
auto registry = std::make_shared<DataModelRegistry>();
242+
registry->registerModel(MockDataModelCreator(&modelsDestroyed));
243+
244+
modelsDestroyed = 0;
245+
246+
FlowScene scene(std::move(registry));
247+
248+
auto& node = scene.createNode(scene.registry().create("name"));
249+
250+
// On destruction, if this `node` outlives its MockDataModelCreator,
251+
// (if it outlives the DataModelRegistry), then we trigger undefined
252+
// behavior through use-after-free. ASAN will catch that.
253+
}
254+
255+
CHECK(modelsDestroyed == 1);
256+
}

0 commit comments

Comments
 (0)