Bug 9149 – Disallow calling const delegates with a mutable context

Status
REOPENED
Severity
normal
Priority
P3
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2012-12-13T04:43:47Z
Last change time
2024-12-13T18:03:18Z
Keywords
safe
Assigned to
No Owner
Creator
timon.gehr
Moved to GitHub: dmd#18504 →

Comments

Comment #0 by timon.gehr — 2012-12-13T04:43:47Z
Delegates cannot implicitly convert to const without breaking const guarantees. Reading a mutable delegate out of a const reference therefore must be a compile error. (for the same reason a mutable member function cannot be called on a const receiver.) import std.stdio; class T{ int x; int delegate()pure b; this(){ b = ()pure=>x++; } } void main(){ auto s=new T(); const c = s.b; // ok, should fail const t=s; writeln(c(), c(), c(), c()); writeln(t.b(), t.b(), t.b(), t.b()); // ok, should fail }
Comment #1 by dfj1esp02 — 2015-11-17T07:46:31Z
Nothing mutable is accessible through a const reference. *** This issue has been marked as a duplicate of issue 1983 ***
Comment #2 by timon.gehr — 2015-11-17T12:20:10Z
This is not a duplicate. Issue 1983 is a completely unrelated bug in the type checker (note that it will no longer compile if one removes the const overload).
Comment #3 by dfj1esp02 — 2016-05-24T10:17:08Z
How about this? class A { int i; const void delegate() dg; this() pure { dg=&f; } void f(){ i++; } } unittest { const A a = new A; a.dg(); }
Comment #4 by timon.gehr — 2016-05-24T15:52:06Z
(In reply to Sobirari Muhomori from comment #3) > How about this? > > class A > { > int i; > const void delegate() dg; > this() pure { dg=&f; } > void f(){ i++; } > } > > unittest > { > const A a = new A; > a.dg(); > } The assignment in the constructor shouldn't compile.
Comment #5 by timon.gehr — 2016-05-24T15:53:24Z
(In reply to timon.gehr from comment #4) > (In reply to Sobirari Muhomori from comment #3) > > How about this? > > > > class A > > { > > int i; > > const void delegate() dg; > > this() pure { dg=&f; } > > void f(){ i++; } > > } > > > > unittest > > { > > const A a = new A; > > a.dg(); > > } > > The assignment in the constructor shouldn't compile. And indeed, DMD rejects the assignment.
Comment #6 by dfj1esp02 — 2016-05-25T12:38:19Z
Comment #7 by dfj1esp02 — 2016-05-25T12:46:41Z
(In reply to timon.gehr from comment #0) > Reading a mutable delegate out of a const reference For there to be mutable data accessible through a const reference, the const qualifier should not be transitive?
Comment #8 by timon.gehr — 2016-05-25T12:50:25Z
(In reply to Sobirari Muhomori from comment #6) > really? https://dpaste.dzfl.pl/ba8fdf6711e3 No, you are right, DMD accepts it even though it is invalid. Not sure why I claimed otherwise (maybe I accidentally tested with the wrong compiler). Anyway, void delegate() shouldn't convert to const(void delegate()) implicitly.
Comment #9 by timon.gehr — 2016-05-25T12:56:58Z
(In reply to Sobirari Muhomori from comment #7) > (In reply to timon.gehr from comment #0) > > Reading a mutable delegate out of a const reference > > For there to be mutable data accessible through a const reference, the const > qualifier should not be transitive? i What I meant there is that the type of the field is a mutable delegate type (and hence it could in principle contain a mutable delegate), which is accessed through a const 'this' reference. The issue is that transitivity of const is currently broken for delegate types. The access must be banned in order to ensure mutable data is not accessible through a const reference. Otherwise delegates provide a backdoor for converting const data back to mutable. (I don't think this is much of an issue, but the designed semantics are that const should be transitive.)
Comment #10 by schveiguy — 2016-05-25T15:28:14Z
I think the delegate assignment should be legal. The auto-conversion from pure ctor to immutable should be illegal (note that converting to const is not a good test case, because you can easily assign an A to a const A without purity), because casting the A to immutable does not affect the 'this' pointer of the delegate member, and that could point at the given object that you are casting or any member. I had thought that the 'this' pointer was const-agnostic. That is: class C { void foo() {} void ifoo() immutable {} } void main() { mc = new C; ic = new C; void delegate() x = &mc.foo; x = &mc.ifoo; // why not? } Why does it matter if x's delegate has an immutable 'this'? that detail is insignificant to the caller.
Comment #11 by timon.gehr — 2016-05-25T19:02:12Z
(In reply to Steven Schveighoffer from comment #10) > I think the delegate assignment should be legal. The auto-conversion from > pure ctor to immutable should be illegal (note that converting to const is > not a good test case, because you can easily assign an A to a const A > without purity), because casting the A to immutable does not affect the > 'this' pointer of the delegate member, and that could point at the given > object that you are casting or any member. There might be a misunderstanding about what this report is about. > I had thought that the 'this' > pointer was const-agnostic. That is: > > class C > { > void foo() {} > void ifoo() immutable {} > } > > void main() > { > mc = new C; > ic = new C; > void delegate() x = &mc.foo; > x = &mc.ifoo; // why not? > } > > Why does it matter if x's delegate has an immutable 'this'? that detail is > insignificant to the caller. I assume this is the code you intended to write: void main(){ auto mc=new C,ic=new immutable(C); void delegate() x = &mc.foo; x = &ic.ifoo; // why not? } DMD disallows this code. (Hopefully. I have checked multiple times :-).) It is always okay to remove guarantees on the 'this' pointer, such as pure, const or immutable, because the closure data is untyped (you can access it only as a void*), so this should indeed be fine. You might consider opening an issue about this. This report is about the converse case. The trouble is that the conversion delegate() -> const(delegate()) breaks transitivity of const if we are allowed to call the const(delegate()) afterwards. I.e. if delegate() -> const(delegate()) conversion and calling const(delegate())'s is allowed, and we have a function void foo(const C c)pure{ ... } then this function might modify state reachable by its argument. However, const is designed to guarantee this cannot happen: Code should not be able to modify data through a const reference (no matter whether the type system is able to prove that it was mutable originally). import std.stdio; @safe: class C{ int x=0; this(){ mthis=unconstify(this); } void mutate()const pure{ mthis.x++; } private: const(Unconstify!C) mthis; } void foo(const C c)pure{ c.mutate(); } void main(){ auto c=new C; auto x1=c.x; foo(c); auto x2=c.x; writeln(x1," ",x2); // 0 1 } struct Unconstify(T){ T delegate()pure _get; @property T ptr()const pure{ return _get(); } alias ptr this; } const(Unconstify!T) unconstify(T)(T ptr)pure{ return Unconstify!T(()=>ptr); } The fix I originally proposed in the first post removes the unsoundness in the const system, but it is ugly. It means the line "const x = y;" fails if y is a delegate, but not if y is an instance of a struct wrapping a delegate. A more elegant solution would be to disallow calling const delegates whose context is not typed const or immutable. I.e. const(int delegate()const) can be called, but const(int delegate()) cannot be called. Basically the same problem exists for immutable.
Comment #12 by schveiguy — 2016-05-25T19:33:59Z
Yes I see. The issue is the conversion of the delegate type, when the context pointer is changed to const (but you didn't change the function pointer, so it still assumes it's argument is mutable). I agree with you. So basically, any struct or class instance cannot convert to const (or via pure constructor to immutable) if it contains a non-const delegate. Yuck, but I think we have to do that.
Comment #13 by timon.gehr — 2016-05-25T19:57:40Z
(In reply to Steven Schveighoffer from comment #12) > Yes I see. > > The issue is the conversion of the delegate type, when the context pointer > is changed to const (but you didn't change the function pointer, so it still > assumes it's argument is mutable). > > I agree with you. > > So basically, any struct or class instance cannot convert to const (or via > pure constructor to immutable) Do you have an example where conversion to immutable for a strongly pure constructor leads to problems? > if it contains a non-const delegate. Yuck, > but I think we have to do that. We can't (for class references, you never know whether some subclass might have a mutable delegate field). We need to do something else, but all options I have proposed so far are quite ugly (or become ugly when considering all the details required to make them work), so I'm open to different proposals.
Comment #14 by schveiguy — 2016-05-25T20:11:37Z
(In reply to timon.gehr from comment #13) > Do you have an example where conversion to immutable for a strongly pure > constructor leads to problems? Basically, the example in comment 3, but with the delegate not being const. > We can't (for class references, you never know whether some subclass might > have a mutable delegate field). We need to do something else, but all > options I have proposed so far are quite ugly (or become ugly when > considering all the details required to make them work), so I'm open to > different proposals. Oh crap. Yeah that's a huge problem. The only thing I can think of is to disallow const(A) if A has a non-const delegate. And disallow overriding any const base functions.
Comment #15 by dfj1esp02 — 2016-05-26T12:12:11Z
If we have syntax to declare delegates with function attributes and disambiguate context attributes, why not implement contravariance check? It's likely to be big and complex, but it's pure, doesn't change the compiler state, doesn't even allocate memory, only takes two const types and returns bool - unlikely to cause serious side effects.
Comment #16 by timon.gehr — 2024-01-11T22:30:48Z
(In reply to timon.gehr from comment #11) > > The fix I originally proposed in the first post removes the unsoundness in > the const system, but it is ugly. It means the line "const x = y;" fails if > y is a delegate, but not if y is an instance of a struct wrapping a > delegate. A more elegant solution would be to disallow calling const > delegates whose context is not typed const or immutable. I.e. const(int > delegate()const) can be called, but const(int delegate()) cannot be called. > > Basically the same problem exists for immutable. I updated the title to reflect this more elegant solution.
Comment #17 by robert.schadek — 2024-12-13T18:03:18Z
THIS ISSUE HAS BEEN MOVED TO GITHUB https://github.com/dlang/dmd/issues/18504 DO NOT COMMENT HERE ANYMORE, NOBODY WILL SEE IT, THIS ISSUE HAS BEEN MOVED TO GITHUB