Bug 4251 – Hole in the const system: immutable(T)[] implicitly casts to ref const(T)[]

Status
RESOLVED
Resolution
FIXED
Severity
blocker
Priority
P2
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2010-05-30T04:11:00Z
Last change time
2014-04-08T14:39:44Z
Keywords
accepts-invalid, patch
Assigned to
nobody
Creator
pelle.mansson
Blocks
2573

Comments

Comment #0 by pelle.mansson — 2010-05-30T04:11:17Z
void messwith(T)(ref const(T)[] ts, const(T) t) { ts ~= t; } class C { int x; this(int i) { x = i; } } void main(string[] args) { C[] cs; immutable C ci = new immutable(C)(6); assert (ci.x == 6); messwith(cs,ci); cs[$-1].x = 14; assert (ci.x == 14); //whoops. } I'm pretty sure you shouldn't be able to do that.
Comment #1 by nfxjfg — 2010-07-13T10:57:33Z
Isn't this a really really really bad bug in the language specification? Obviously you shouldn't be able to overwrite immutable values (unless you use unsafe tricks). What can be done to fix this hole in the const system? Just disallowing appending const(T) to const(T)[] isn't really an option, is it?
Comment #2 by schveiguy — 2010-07-13T11:54:33Z
The issue is casting a ref T[] to a ref const(T)[]. The safety is broken if you implicitly apply const two references deep. I think this is a dup of bug 2095 (which actually is dup of a sub-1000 bug). But I think the const implications are somewhat new. I recall there was either a discussion on the NG or another bug that covers this. Maybe someone else can find it.
Comment #3 by yebblies — 2011-06-12T08:14:13Z
https://github.com/D-Programming-Language/dmd/pull/115 When implicitly converting types with indirections, only allow the following results: > completely mutable > completely non-mutable > exactly one mutable indirection > the same number of mutable indirections as before eg. > T*** => const(T***) allowed, full const > T*** => const(T**)* allowed, tail const > T*** => const(T*)** not allowed > T*** => const(T)*** not allowed > T*** => T*** allowed, same number of mutable indirections > immutable(T*)** => const(T*)** allowed, same number of mutable indirections etc This prevents (as far as I know) using implicit conversions to let a pointer to mutable and a pointer to immutable point to the same place using const conversions. eg. int** a = [new int].ptr; const(int)*** b = &a; *b = [new immutable(int)].ptr; The same applies to other reference types.
Comment #4 by yebblies — 2011-06-15T20:22:49Z
*** Issue 2544 has been marked as a duplicate of this issue. ***
Comment #5 by smjg — 2011-06-16T01:22:22Z
(In reply to comment #3) >> T*** => const(T***) allowed, full const >> T*** => const(T**)* allowed, tail const >> T*** => const(T*)** not allowed >> T*** => const(T)*** not allowed >> T*** => T*** allowed, same number of mutable indirections >> immutable(T*)** => const(T*)** allowed, same number of mutable indirections >etc I agree about the first five of these. But I'm not sure if this last one is safe. I'll think about it when I've more time. In any case, see this set of rules I proposed before: http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=81566
Comment #6 by yebblies — 2011-06-16T01:41:07Z
(In reply to comment #5) > I agree about the first five of these. But I'm not sure if this last one is > safe. I'll think about it when I've more time. In any case, see this set of > rules I proposed before: > I haven't been able to think of a way to break it, but that doesn't mean there isn't one! > http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=81566 I did find this today. As far as I can tell, this fix combined with the fix for issue 2095 fixes it all.
Comment #7 by schveiguy — 2011-06-16T08:06:02Z
I think the cases are all sound. In order for there to be a problem, both mutable and immutable data need to be castable into const. If you cannot cast mutable into const N references deep, then you can't accidentally rebind it to immutable data. This all stems from being able to treat mutable data as const, and also as mutable at the same time. Being able to treat immutable data as const and immutable at the same time does not cause any harm. This is definitely one of those things that makes my brain hurt... It's like 4 dimensional geometry :)
Comment #8 by yebblies — 2011-06-16T08:14:06Z
(In reply to comment #7) > This is definitely one of those things that makes my brain hurt... It's like 4 > dimensional geometry :) I had to draw out tables and diagrams before I could get this right in my mind.
Comment #9 by smjg — 2011-06-16T12:12:23Z
(In reply to comment #5) >>> immutable(T*)** => const(T*)** allowed, same number of mutable indirections As it turns out, this is unsafe, as the following code shows: ---------- import std.stdio; void main() { immutable(int) i = 42; immutable(int)* ip = &i; immutable(int)** ipp = &ip; const(int)** cpp = ipp; int m = 69; // the next statement makes ip point to a mutable value! *cpp = &m; writefln("%d", *ip); m = 105; writefln("%d", *ip); }
Comment #10 by andrei — 2011-06-16T12:23:01Z
Yah, this has constantly puzzled starting C++ programmers - you can convert char* to const(char*) but not char** to const(char*)*. Generally, consider types P (permissive) and N (nonpermissive). Assume both types have the same structure, so there is no matter of converting representation. Generally you can't convert the address of a N to the address of a P even if you can actually convert a N to an P. This is because the address conversion would allow you subsequent P-specific operations directly against an N object.
Comment #11 by smjg — 2011-06-16T13:18:31Z
(In reply to comment #10) > Yah, this has constantly puzzled starting C++ programmers - you can convert > char* to const(char*) but not char** to const(char*)*. Do you mean char** to const(char)** ? > Generally, consider types P (permissive) and N (nonpermissive). Assume both > types have the same structure, so there is no matter of converting > representation. Generally you can't convert the address of a N to the address > of a P even if you can actually convert a N to an P. This is because the > address conversion would allow you subsequent P-specific operations directly > against an N object. Well said. Converting T* (N) to const(T)* (P) is safe. The P-specific operation is rebinding it to an immutable(T). So converting T** to const(T)** is unsafe. Similarly, Converting immutable(T)* (N) to const(T)* (P) is safe. The P-specific operation is rebinding it to a mutable T. So converting immutable(T)** to const(T)** is unsafe. This is the principle that underlies all these proposed rules - whether the indirection is a pointer, dynamic array or other container type, and whether the N->P is a constancy change or a walk up the class hierarchy.
Comment #12 by yebblies — 2011-06-16T20:18:02Z
(In reply to comment #9) > (In reply to comment #5) > >>> immutable(T*)** => const(T*)** allowed, same number of mutable indirections > > As it turns out, this is unsafe, as the following code shows: > Good catch! I'll get on it.
Comment #13 by yebblies — 2011-06-22T00:31:47Z
*** Issue 6190 has been marked as a duplicate of this issue. ***
Comment #14 by braddr — 2011-08-03T20:16:33Z
I've removed the patch keyword from this bug since the pull request was withdrawn. I don't suppose you've had a chance to fix the issues with the first version of the patch, have you yebblies?
Comment #15 by yebblies — 2011-08-03T20:31:41Z
(In reply to comment #14) > I've removed the patch keyword from this bug since the pull request was > withdrawn. I don't suppose you've had a chance to fix the issues with the > first version of the patch, have you yebblies? Unfortunately no. My patch was instantiating templates it shouldn't have, and I haven't had the time lately to fix the template deduction.
Comment #16 by timon.gehr — 2011-11-17T11:59:55Z
Class references have the same problem. class A{} class B:A{} void main(){ B x; A* y=&x; // bad! *y=new A; Object o = x; assert(o is x && o && is(typeof(x)==B) && !cast(B)o); } I have generalized the title accordingly.
Comment #17 by lat7h — 2011-11-18T05:28:26Z
(In reply to comment #16) > B x; > A* y=&x; // bad! Everything you describe is supposed to happen. "A* y = &x" is the point of inheritance. What exactly is the problem you see in the above? All of your assert elements seem fine too… Reverting title. This bug is about the const safety design of the language, not about inheritance. Even if there is a problem with inheritance, it should be a separate bug unless you can argue they have the same cause.
Comment #18 by schveiguy — 2011-11-18T07:10:20Z
(In reply to comment #17) > (In reply to comment #16) > > B x; > > A* y=&x; // bad! > Everything you describe is supposed to happen. "A* y = &x" is the point of > inheritance. What exactly is the problem you see in the above? All of your > assert elements seem fine too… The example isn't illustrative enough. Here's a better example: class A {} class B : A {void foo() {writeln("hello, B");} } void main() { B x; A* y = &x; *y = new A; x.foo(); // boom! Call to invalid vtable entry } As I said in an earlier comment, this bug is really a dup of bug 2095, but it adds a different twist. If we want to consolidate, we should close this as a duplicate of 2095. > Reverting title. This bug is about the const safety design of the language, not > about inheritance. Even if there is a problem with inheritance, it should be a > separate bug unless you can argue they have the same cause. It's somewhat the same thing. const(T) is really a "base class" of T, since T implicitly casts to const(T), but const(T) doesn't implicitly cast to T. I won't revert the bug description again, because I think this case is already covered in 2095.
Comment #19 by schveiguy — 2011-11-18T07:12:53Z
However, I just realized the title is not correct. So I'll change the title to reflect the actual problem (const(T) is perfectly legal to append to const(T)[]).
Comment #20 by andrei — 2011-11-18T07:45:55Z
(In reply to comment #19) > However, I just realized the title is not correct. So I'll change the title to > reflect the actual problem (const(T) is perfectly legal to append to > const(T)[]). I like Timon's example though, it clarifies quite well a significant problem.
Comment #21 by k.hara.pg — 2011-12-12T21:56:04Z
Comment #22 by bugzilla — 2012-01-16T10:38:48Z
Comment #23 by jbinero — 2014-04-08T14:39:44Z
*** Issue 12549 has been marked as a duplicate of this issue. ***