Skip to content

Commit f73b0ea

Browse files
committed
🛡️ Expose is_deep_immutable for lib devs checks.
1 parent 04e0b50 commit f73b0ea

File tree

3 files changed

+29
-1
lines changed

3 files changed

+29
-1
lines changed

src/Networks/Networks.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,12 @@ to the closures used in the API, as it would collapse its logic.
7979
Julia unfortunately has no straightforward mechanism
8080
to automatically enforce that it cannot happen.
8181
82+
In particular,
83+
using views to access individual pieces of data *can* leak references.
84+
Only allow it if the data is "deeply" immutable in the sense
85+
of the exposed `is_deep_immutable(::Type)` method.
86+
Don't produce views into mutable data that could otherwise break the COW-pattern.
87+
8288
[COW]: https://en.wikipedia.org/wiki/Copy-on-write
8389
[RC]: https://en.wikipedia.org/wiki/Reference_counting
8490
[Boxing]: https://en.wikipedia.org/wiki/Boxing_(computer_programming)

src/Networks/views.jl

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,28 @@ Base.setproperty!(::View, ::Symbol, _) = throw("View fields are private.")
4242
Base.deepcopy(::View) = throw("Deepcopying the view would break its logic.")
4343
Base.copy(v::View) = v # There is no use in a copy.
4444

45+
"""
46+
True if a reference leaked from a view cannot be used to break the COW-pattern.
47+
This is not checked on construction of every view,
48+
and Networks users are responsible to only emit views for these types.
49+
"""
50+
function is_deep_immutable(T::Type)
51+
# Special-case these: julia marks them as 'mutable'
52+
# but user can't do anything to mutate them, right?
53+
T isa String && return true
54+
T isa Symbol && return true
55+
if T isa Union
56+
(; a, b) = T
57+
is_deep_immutable(a) && is_deep_immutable(b)
58+
elseif T isa DataType
59+
ismutabletype(T) && return false
60+
all(I.map(is_deep_immutable, fieldtypes(T)))
61+
else
62+
throw("Deep mutability analysis unimplemented for $T.")
63+
end
64+
end
65+
export is_deep_immutable
66+
4567
# Forward to underlying entry.
4668
n_networks(v::View) = n_networks(entry(v))
4769

src/views.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ struct NodesView{T}
3939
end
4040
S = NodesView # "Self"
4141
Base.length(v::S) = length(view(v))
42-
Base.getindex(v::S, i) = getindex(view(v), i) # WARN: leak if entry is mutable?
42+
Base.getindex(v::S, i) = getindex(view(v), i)
4343
Base.setindex!(v::S, x, i) = setindex!(view(v), x, i)
4444
nodes_view(m::Model, class::Symbol, data::Symbol) =
4545
NodesView(m, N.nodes_view(m._value, class, data), data)

0 commit comments

Comments
 (0)