Bug 16056 – immutable delegate can mutate through context pointer
Status
RESOLVED
Resolution
DUPLICATE
Severity
normal
Priority
P3
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2016-05-22T08:49:00Z
Last change time
2016-05-24T10:02:36Z
Keywords
accepts-invalid
Assigned to
nobody
Creator
eyal.lotem
Comments
Comment #0 by eyal.lotem — 2016-05-22T08:49:55Z
The "immutable" qualifier is not transitive, as it claims to be.
import std.stdio;
struct Foo {
int y;
void delegate() pure f;
}
pure void pure_func(immutable Foo foo)
{
foo.f();
}
void main() {
int y;
immutable x = Foo(1, { y++; });
writeln("Before: ", y);
pure_func(x);
writeln("After: ", y);
}
Prints out:
Before: 0
After: 1
A pure function with only immutable parameters should have no possible side-effects. However, due to 'immutable' not transitively applying to delegates' context:
A) We can have a mutable-context delegate inside an immutable struct
B) A pure function taking exclusively immutable parameters is not really pure
The fix would be to transitively apply the "immutable" qualifier to the context type of the Foo.f delegate.
Comment #1 by ag0aep6g — 2016-05-22T09:24:12Z
The struct doesn't really matter here, as far as I see. Simplified code:
----
import std.stdio;
pure void pure_func(immutable void delegate() pure f)
{
f();
}
void main() {
int y;
writeln("Before: ", y);
pure_func({ y++; });
writeln("After: ", y);
}
----
Possibly a duplicate of issue 11043 or issue 1983.
Comment #2 by eyal.lotem — 2016-05-22T14:29:20Z
(In reply to ag0aep6g from comment #1)
> The struct doesn't really matter here, as far as I see. Simplified code:
>
> ----
> import std.stdio;
>
> pure void pure_func(immutable void delegate() pure f)
> {
> f();
> }
>
> void main() {
> int y;
> writeln("Before: ", y);
> pure_func({ y++; });
> writeln("After: ", y);
> }
> ----
>
> Possibly a duplicate of issue 11043 or issue 1983.
This is simpler -- but this simpler example isn't a bug: a pure func that takes a mutable delegate is "weakly pure" because it doesn't take an immutable argument. You could say that the simplified pure func takes an explicitly mutable argument, so it is known to be weakly pure.
When you do take an immutable argument in a pure function -- the immutability is supposed to be transitive -- so it is supposed to be strongly pure.
Comment #3 by ag0aep6g — 2016-05-22T14:33:55Z
(In reply to Eyal Lotem from comment #2)
> This is simpler -- but this simpler example isn't a bug: a pure func that
> takes a mutable delegate is "weakly pure" because it doesn't take an
> immutable argument. You could say that the simplified pure func takes an
> explicitly mutable argument, so it is known to be weakly pure.
pure_func's parameter isn't mutable. It's explicitly marked immutable.
Comment #4 by eyal.lotem — 2016-05-22T15:27:25Z
immutable void delegate() pure(In reply to ag0aep6g from comment #3)
> (In reply to Eyal Lotem from comment #2)
> > This is simpler -- but this simpler example isn't a bug: a pure func that
> > takes a mutable delegate is "weakly pure" because it doesn't take an
> > immutable argument. You could say that the simplified pure func takes an
> > explicitly mutable argument, so it is known to be weakly pure.
>
> pure_func's parameter isn't mutable. It's explicitly marked immutable.
immutable void delegate() pure
vs.
immutable void delegate() immutable pure
Arguably, you can claim that if you've chosen the former form, you knowingly forfeited mutability for the delegate.
However, once the delegate is wrapped in a struct, you would expect that transitivity would take care of full immutability.
Comment #5 by ag0aep6g — 2016-05-22T16:38:21Z
(In reply to Eyal Lotem from comment #4)
> immutable void delegate() pure
>
> vs.
>
> immutable void delegate() immutable pure
When the delegate is part of an immutable struct instance, it has the former type, too:
----
struct S { void delegate() pure f; }
immutable s = S();
pragma(msg, typeof(s.f)); /* immutable(void delegate() pure) */
----
I suppose your point is that it should be the latter type, with two "immutable"s.
Fair enough, but I don't think that distinction is worth having. D doesn't have head const with pointers. Why should we make delegates a special case?
So in my opinion, `immutable void delegate()` should imply an immutable context pointer, making it the same type as `immutable void delegate() immutable`. Just like `immutable(int*)` is the same as `immutable(immutable(int)*)`.
The compiler agrees (to some extent):
----
alias A = immutable int* delegate();
alias B = immutable int* delegate() immutable;
static assert(is(A == B)); /* passes */
----
But then there's this:
----
void main()
{
int x = 1;
const void delegate() dg1 = { ++x; };
const void delegate() const dg2 = { ++x; };
}
----
That compiles, but when you take the dg1 line away, then the dg2 line isn't accepted anymore. And when you swap them around, both lines are rejected. There's obviously something wrong here. I've filed a separate issue: https://issues.dlang.org/show_bug.cgi?id=16058
Comment #6 by eyal.lotem — 2016-05-24T06:44:25Z
(In reply to ag0aep6g from comment #5)
> (In reply to Eyal Lotem from comment #4)
> > immutable void delegate() pure
> >
> > vs.
> >
> > immutable void delegate() immutable pure
>
> When the delegate is part of an immutable struct instance, it has the former
> type, too:
>
> ----
> struct S { void delegate() pure f; }
> immutable s = S();
> pragma(msg, typeof(s.f)); /* immutable(void delegate() pure) */
> ----
>
> I suppose your point is that it should be the latter type, with two
> "immutable"s.
>
> Fair enough, but I don't think that distinction is worth having. D doesn't
> have head const with pointers. Why should we make delegates a special case?
>
> So in my opinion, `immutable void delegate()` should imply an immutable
> context pointer, making it the same type as `immutable void delegate()
> immutable`. Just like `immutable(int*)` is the same as
> `immutable(immutable(int)*)`.
>
> The compiler agrees (to some extent):
>
> ----
> alias A = immutable int* delegate();
> alias B = immutable int* delegate() immutable;
> static assert(is(A == B)); /* passes */
> ----
>
> But then there's this:
>
> ----
> void main()
> {
> int x = 1;
> const void delegate() dg1 = { ++x; };
> const void delegate() const dg2 = { ++x; };
> }
> ----
>
> That compiles, but when you take the dg1 line away, then the dg2 line isn't
> accepted anymore. And when you swap them around, both lines are rejected.
> There's obviously something wrong here. I've filed a separate issue:
> https://issues.dlang.org/show_bug.cgi?id=16058
I agree `immutable delegate ..` should imply the context is immutable too (i.e: no head-const ptrs).
But possibly not the other way around.
It makes sense to have a mutable delegate with an immutable context (like a mutable ptr to immutable data).
Comment #7 by dfj1esp02 — 2016-05-24T10:02:36Z
*** This issue has been marked as a duplicate of issue 1983 ***