Skip to content

Commit a49ebd3

Browse files
committed
Protect vertex write operation without unlocking
1 parent bf45aad commit a49ebd3

File tree

4 files changed

+92
-15
lines changed

4 files changed

+92
-15
lines changed

lib/graph/graph.dart

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,40 +3,45 @@ import 'vertex.dart';
33
/// A Graph Type
44
class Graph<T> {
55
/// Vertices of this graph
6-
Set<Vertex<T>> vertices;
6+
Set<Vertex<T>> _vertices;
7+
List<Vertex<T>> get vertices => List<Vertex<T>>.unmodifiable(_vertices);
78

89
/// Settings for this graph
910
/// Is this a Digraph?
10-
bool isDigraph;
11+
final bool isDigraph;
1112

1213
/// Does this graph allow loops?
13-
bool allowLoops;
14+
final bool allowLoops;
1415

1516
/// Create a new graph
1617
Graph({this.isDigraph = true, this.allowLoops = false}) {
17-
vertices = <Vertex<T>>{};
18+
_vertices = <Vertex<T>>{};
1819
}
1920

2021
/// Total number of vertices for this graph
21-
int get numberOfVertices => vertices.length;
22+
int get numberOfVertices => _vertices.length;
2223

2324
/// Total number of edges
2425
int get numberOfEdges =>
25-
vertices.map((v) => v.outDegree).fold(0, (a, b) => a + b);
26+
_vertices.map((v) => v.outDegree).fold(0, (a, b) => a + b);
2627

2728
/// Adds an edge
2829
void addEdge(Vertex src, Vertex dst, [num weight = 1]) {
30+
src.unlock();
31+
dst.unlock();
2932
if (src.key == dst.key && !allowLoops) throw Error();
3033

3134
src = _getOrAddVertex(src);
3235
dst = _getOrAddVertex(dst);
3336
src.addConnection(dst, weight);
3437

3538
if (!isDigraph) dst.addConnection(src, weight);
39+
src.lock();
40+
dst.lock();
3641
}
3742

3843
/// Checks if vertex is included
39-
bool containsVertex(Vertex vertex) => vertices.contains(vertex);
44+
bool containsVertex(Vertex vertex) => _vertices.contains(vertex);
4045

4146
/// Checks if this is a null graph
4247
bool get isNull => numberOfVertices == 0;
@@ -48,14 +53,14 @@ class Graph<T> {
4853
bool get isEmpty => numberOfEdges == 0;
4954

5055
/// Adds a new vertex
51-
bool addVertex(Vertex vertex) => vertices.add(vertex);
56+
bool addVertex(Vertex vertex) => _vertices.add(vertex);
5257

5358
Vertex<T> _getOrAddVertex(Vertex vertex) =>
54-
vertices.add(vertex) ? vertex : vertex;
59+
_vertices.add(vertex) ? vertex : vertex;
5560

5661
/// Gets an edge list for [this].
5762
List<List> get edges => [
58-
for (var vertex in vertices) ...[
63+
for (var vertex in _vertices) ...[
5964
for (var connection in vertex.outgoingConnections.entries)
6065
[vertex, connection.key, connection.value]
6166
]

lib/graph/vertex.dart

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'dart:collection';
55
/// the vertex. By default, the `key` and `value` are the same.
66
class Vertex<T> {
77
final String _key;
8+
bool _isLocked;
89

910
/// Uniquely identifiable key to this [Vertex]
1011
String get key => _key;
@@ -26,13 +27,19 @@ class Vertex<T> {
2627

2728
/// Constructor
2829
Vertex(this._key, [T value])
29-
: _incomingVertices = <Vertex>{} as LinkedHashSet,
30+
: _isLocked = true,
31+
_incomingVertices = <Vertex>{} as LinkedHashSet,
3032
_outgoingConnections = <Vertex, num>{} as LinkedHashMap {
3133
this.value = value ?? key;
3234
}
3335

36+
void lock() => _isLocked = true;
37+
void unlock() => _isLocked = false;
38+
3439
/// Adds a connection with [Vertex] `dst` and with `weight`
3540
bool addConnection(Vertex dst, [num weight = 1]) {
41+
if (_isLocked || dst._isLocked)
42+
throw UnsupportedError('Cannot add to a locked vertex');
3643
if (_outgoingConnections.containsKey(dst)) {
3744
return false;
3845
}
@@ -44,6 +51,8 @@ class Vertex<T> {
4451
/// Removes a connection with `other` with `weight`. `false` for non-existent
4552
/// connection.
4653
bool removeConnection(Vertex other, [num weight = 1]) {
54+
if (_isLocked || other._isLocked)
55+
throw UnsupportedError('Cannot remove from a locked vertex');
4756
var outgoingRemoved = _outgoingConnections.remove(other) != null;
4857
var incomingRemoved = other._incomingVertices.remove(this);
4958

test/graph/graph_test.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,4 +134,12 @@ void main() {
134134
};
135135
expect(simpleGraph.edges.toSet(), equals(expectedEdges));
136136
});
137+
138+
test('addConnection on a cherry picked vertex throws error', () {
139+
var vertices = simpleGraph.vertices;
140+
var leak = Vertex('LEAK');
141+
leak.unlock();
142+
expect(() => vertices[0].addConnection(leak),
143+
throwsA(isA<UnsupportedError>()));
144+
});
137145
}

test/graph/vertex_test.dart

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,25 @@ void main() {
77
Vertex rootWithValue;
88
Vertex connectedVertex;
99
Vertex toBeAdded;
10+
Vertex anotherVertex;
1011

1112
setUp(() {
1213
root = Vertex('A');
1314
rootWithValue = Vertex('a', 'Wake up');
1415

1516
connectedVertex = Vertex('0');
1617
toBeAdded = Vertex('1');
18+
anotherVertex = Vertex('2');
19+
20+
// For test purposes, unlock all vertices
21+
root.unlock();
22+
rootWithValue.unlock();
23+
connectedVertex.unlock();
24+
toBeAdded.unlock();
25+
anotherVertex.unlock();
26+
1727
connectedVertex.addConnection(toBeAdded);
18-
connectedVertex.addConnection(Vertex('2'));
28+
connectedVertex.addConnection(anotherVertex);
1929
});
2030

2131
test('Initialization of a node', () {
@@ -28,6 +38,8 @@ void main() {
2838
test('Successfully add a vertex', () {
2939
var b = Vertex('B');
3040
var c = Vertex('C');
41+
b.unlock();
42+
c.unlock();
3143
expect(root.addConnection(b), isTrue);
3244
expect(root.addConnection(c), isTrue);
3345
expect(root.outgoingConnections.containsKey(b), isTrue);
@@ -38,6 +50,7 @@ void main() {
3850

3951
test('Unsuccessfully add a vertex', () {
4052
var b = Vertex('B');
53+
b.unlock();
4154
root.addConnection(b);
4255
expect(root.addConnection(b), isFalse);
4356
});
@@ -50,19 +63,59 @@ void main() {
5063

5164
test('Unsuccessfully remove a vertex', () {
5265
var aVertex = Vertex('-1');
66+
aVertex.unlock();
5367
expect(connectedVertex.removeConnection(aVertex), isFalse);
5468
});
5569

70+
test('Trying to add to a locked vertex throws error', () {
71+
var locked = Vertex('PROTECTED');
72+
expect(() => locked.addConnection(root), throwsA(isA<Error>()));
73+
root.lock();
74+
expect(() => root.addConnection(root), throwsA(isA<Error>()));
75+
});
76+
77+
test('Trying to add a locked vertex throws error', () {
78+
var locked = Vertex('PROTECTED');
79+
expect(() => root.addConnection(locked), throwsA(isA<Error>()));
80+
locked.unlock();
81+
root.lock();
82+
expect(() => locked.addConnection(root), throwsA(isA<Error>()));
83+
});
84+
85+
test('Trying to remove from a locked vertex throws error', () {
86+
toBeAdded.lock();
87+
expect(() => connectedVertex.removeConnection(toBeAdded),
88+
throwsA(isA<Error>()));
89+
});
90+
91+
test('Trying to remove a locked vertex throws error', () {
92+
connectedVertex.lock();
93+
expect(() => connectedVertex.removeConnection(toBeAdded),
94+
throwsA(isA<Error>()));
95+
});
96+
97+
test('Trying to remove a locked vertex throws error', () {
98+
var locked = Vertex('PROTECTED');
99+
expect(() => root.addConnection(locked), throwsA(isA<Error>()));
100+
locked.unlock();
101+
root.lock();
102+
expect(() => locked.addConnection(root), throwsA(isA<Error>()));
103+
});
104+
56105
test('Check for vertex containment', () {
106+
var newVertex = Vertex('DISCONNECTED');
107+
newVertex.unlock();
57108
expect(connectedVertex.containsConnectionTo(toBeAdded), isTrue);
58-
expect(connectedVertex.containsConnectionTo(Vertex('I am not connected')),
59-
isFalse);
109+
expect(connectedVertex.containsConnectionTo(newVertex), isFalse);
60110
});
61111

62112
test('Get a list of vertices', () {
63113
var a = Vertex('A');
64114
var b = Vertex('B');
65115
var c = Vertex('C');
116+
a.unlock();
117+
b.unlock();
118+
c.unlock();
66119
root.addConnection(a);
67120
root.addConnection(b);
68121
root.addConnection(c);
@@ -78,8 +131,10 @@ void main() {
78131
});
79132

80133
test('Get out degree for vertex', () {
134+
Vertex c = Vertex('c');
135+
c.unlock();
81136
expect(root.outDegree, equals(0));
82-
connectedVertex.addConnection(Vertex('C'));
137+
connectedVertex.addConnection(c);
83138
expect(connectedVertex.outDegree, equals(3));
84139
});
85140

0 commit comments

Comments
 (0)