Thread overview
String copying fails when output from assert
Nov 21, 2017
David Zhang
Nov 21, 2017
Adam D. Ruppe
Nov 21, 2017
David Zhang
Nov 21, 2017
Jonathan M Davis
Nov 21, 2017
Jonathan M Davis
Nov 21, 2017
David Zhang
Nov 21, 2017
Jonathan M Davis
Nov 21, 2017
Jonathan M Davis
Nov 21, 2017
H. S. Teoh
November 21, 2017
Hi,

I've been trying to copy a string, then output it from an `assert(false)` statement, but the copy seems to be corrupted somehow.

void main()
{
	string str = "some string";

        //initializing `chars` with any value doesn't do anything
	char[64] chars;
	//char[64] chars = '!';

	//memcpy doesn't work either, though the output's different
	//import core.stdc.string;
	//memcpy(&chars[0], &str[0], str.length);

	chars[0..str.length] = str;
	assert(false, chars[0..str.length]);
}

What am I missing here?

November 21, 2017
On Tuesday, 21 November 2017 at 18:49:40 UTC, David  Zhang wrote:
> 	assert(false, chars[0..str.length]);
> }
>
> What am I missing here?

You're escaping a reference to a local variable there. chars[] is a pointer to the stack, which is promptly smashed when the assert error starts working up the call chain, so by the time it actually prints, you have undefined behavior.
November 21, 2017
On Tuesday, 21 November 2017 at 18:56:03 UTC, Adam D. Ruppe wrote:
> On Tuesday, 21 November 2017 at 18:49:40 UTC, David  Zhang wrote:
>> 	assert(false, chars[0..str.length]);
>> }
>>
>> What am I missing here?
>
> You're escaping a reference to a local variable there. chars[] is a pointer to the stack, which is promptly smashed when the assert error starts working up the call chain, so by the time it actually prints, you have undefined behavior.

So I'd have to allocate the buffer on the heap then...
Is there any way to do this without allocating to the stack?
November 21, 2017
On Tuesday, November 21, 2017 18:49:40 David Zhang via Digitalmars-d-learn wrote:
> Hi,
>
> I've been trying to copy a string, then output it from an `assert(false)` statement, but the copy seems to be corrupted somehow.
>
> void main()
> {
>   string str = "some string";
>
>          //initializing `chars` with any value doesn't do anything
>   char[64] chars;
>   //char[64] chars = '!';
>
>   //memcpy doesn't work either, though the output's different
>   //import core.stdc.string;
>   //memcpy(&chars[0], &str[0], str.length);
>
>   chars[0..str.length] = str;
>   assert(false, chars[0..str.length]);
> }
>
> What am I missing here?

Well, the assertion is going to throw an AssertError, which takes a string for its message. It doesn't copy the contents of the string. It's just taking a slice just like whenever you pass a string to any other function. So, it refers to the same memory as what's passed in. So, if what it's passed is a string that refers to memory on the stack, then when it goes to print the message, it's going to be garbage, because the stack was unwound, and the static array is gone.

Honestly, I'd argue that it's a bug that it allows you provide a message as a char[] instead of immutable(char)[]. It seems that the compiler is implicitly converting char[] to immutable(char)[] in this case, which is very bad. It would matter less if you were giving the assertion a char[] that referred to memory on the heap, but it still shouldn't be allowed.

You really should be giving assertions a value that is an actual string that lives on the heap when providing a message, not a char[], and definitely not a char[] that's a slice of a static array.

- Jonathan M Davis

November 21, 2017
On Tuesday, 21 November 2017 at 19:05:21 UTC, Jonathan M Davis wrote:
> Well, the assertion is going to throw an AssertError, which takes a string for its message. It doesn't copy the contents of the string. It's just taking a slice just like whenever you pass a string to any other function. So, it refers to the same memory as what's passed in. So, if what it's passed is a string that refers to memory on the stack, then when it goes to print the message, it's going to be garbage, because the stack was unwound, and the static array is gone.
>
> Honestly, I'd argue that it's a bug that it allows you provide a message as a char[] instead of immutable(char)[]. It seems that the compiler is implicitly converting char[] to immutable(char)[] in this case, which is very bad. It would matter less if you were giving the assertion a char[] that referred to memory on the heap, but it still shouldn't be allowed.
>
> You really should be giving assertions a value that is an actual string that lives on the heap when providing a message, not a char[], and definitely not a char[] that's a slice of a static array.
>
> - Jonathan M Davis

Right. So a literal would work, because it's in the .data segment, or inlined. Basically, only strings from the heap, or that are compiled into the code are valid for assert messages.

What kind of behavior would an assert from stdc have (id, betterC)?
November 21, 2017
On Tuesday, November 21, 2017 12:05:21 Jonathan M Davis via Digitalmars-d- learn wrote:
> On Tuesday, November 21, 2017 18:49:40 David Zhang via Digitalmars-d-learn wrote:
> > Hi,
> >
> > I've been trying to copy a string, then output it from an `assert(false)` statement, but the copy seems to be corrupted somehow.
> >
> > void main()
> > {
> >
> >   string str = "some string";
> >
> >          //initializing `chars` with any value doesn't do anything
> >
> >   char[64] chars;
> >   //char[64] chars = '!';
> >
> >   //memcpy doesn't work either, though the output's different
> >   //import core.stdc.string;
> >   //memcpy(&chars[0], &str[0], str.length);
> >
> >   chars[0..str.length] = str;
> >   assert(false, chars[0..str.length]);
> >
> > }
> >
> > What am I missing here?
>
> Well, the assertion is going to throw an AssertError, which takes a string for its message. It doesn't copy the contents of the string. It's just taking a slice just like whenever you pass a string to any other function. So, it refers to the same memory as what's passed in. So, if what it's passed is a string that refers to memory on the stack, then when it goes to print the message, it's going to be garbage, because the stack was unwound, and the static array is gone.
>
> Honestly, I'd argue that it's a bug that it allows you provide a message as a char[] instead of immutable(char)[]. It seems that the compiler is implicitly converting char[] to immutable(char)[] in this case, which is very bad. It would matter less if you were giving the assertion a char[] that referred to memory on the heap, but it still shouldn't be allowed.
>
> You really should be giving assertions a value that is an actual string that lives on the heap when providing a message, not a char[], and definitely not a char[] that's a slice of a static array.

https://issues.dlang.org/show_bug.cgi?id=18002

- Jonathan M Davis

November 21, 2017
On Tue, Nov 21, 2017 at 12:05:21PM -0700, Jonathan M Davis via Digitalmars-d-learn wrote:
> On Tuesday, November 21, 2017 18:49:40 David Zhang via Digitalmars-d-learn wrote:
[...]
> >   char[64] chars;
> >   chars[0..str.length] = str;
> >   assert(false, chars[0..str.length]);
[...]
> Well, the assertion is going to throw an AssertError, which takes a string for its message. It doesn't copy the contents of the string. It's just taking a slice just like whenever you pass a string to any other function.  So, it refers to the same memory as what's passed in. So, if what it's passed is a string that refers to memory on the stack, then when it goes to print the message, it's going to be garbage, because the stack was unwound, and the static array is gone.
> 
> Honestly, I'd argue that it's a bug that it allows you provide a message as a char[] instead of immutable(char)[]. It seems that the compiler is implicitly converting char[] to immutable(char)[] in this case, which is very bad. It would matter less if you were giving the assertion a char[] that referred to memory on the heap, but it still shouldn't be allowed.

Yeah, this is a bug.  I filed an issue:

	https://issues.dlang.org/show_bug.cgi?id=18003


T

-- 
The peace of mind---from knowing that viruses which exploit Microsoft system vulnerabilities cannot touch Linux---is priceless. -- Frustrated system administrator.
November 21, 2017
On Tuesday, November 21, 2017 18:59:14 David Zhang via Digitalmars-d-learn wrote:
> On Tuesday, 21 November 2017 at 18:56:03 UTC, Adam D. Ruppe wrote:
> > On Tuesday, 21 November 2017 at 18:49:40 UTC, David  Zhang
> >
> > wrote:
> >>    assert(false, chars[0..str.length]);
> >>
> >> }
> >>
> >> What am I missing here?
> >
> > You're escaping a reference to a local variable there. chars[] is a pointer to the stack, which is promptly smashed when the assert error starts working up the call chain, so by the time it actually prints, you have undefined behavior.
>
> So I'd have to allocate the buffer on the heap then...
> Is there any way to do this without allocating to the stack?

Why would you need to allocate on the stack to allocate on the heap? If you have a string to pass assert for its message, then just pass it the string. If you have a char[], then idup it. If you have a static array on the stack, then idup it. And if you're iduping in the assert statement, then it should only idup when the assertion fails.

- Jonathan M Davis

November 21, 2017
On Tuesday, November 21, 2017 19:13:36 David Zhang via Digitalmars-d-learn wrote:
> On Tuesday, 21 November 2017 at 19:05:21 UTC, Jonathan M Davis
>
> wrote:
> > Well, the assertion is going to throw an AssertError, which takes a string for its message. It doesn't copy the contents of the string. It's just taking a slice just like whenever you pass a string to any other function. So, it refers to the same memory as what's passed in. So, if what it's passed is a string that refers to memory on the stack, then when it goes to print the message, it's going to be garbage, because the stack was unwound, and the static array is gone.
> >
> > Honestly, I'd argue that it's a bug that it allows you provide a message as a char[] instead of immutable(char)[]. It seems that the compiler is implicitly converting char[] to immutable(char)[] in this case, which is very bad. It would matter less if you were giving the assertion a char[] that referred to memory on the heap, but it still shouldn't be allowed.
> >
> > You really should be giving assertions a value that is an actual string that lives on the heap when providing a message, not a char[], and definitely not a char[] that's a slice of a static array.
> >
> > - Jonathan M Davis
>
> Right. So a literal would work, because it's in the .data segment, or inlined. Basically, only strings from the heap, or that are compiled into the code are valid for assert messages.

Or any kind of exception message.

> What kind of behavior would an assert from stdc have (id,
> betterC)?

I don't know. It calls the C implementation at that point, so it won't throw an AssertError, but I don't know exactly how it's implemented. It probably just prints the message and then kills the program, so it'll probably work even with a slice of a static array, but I don't know for sure. However, unless the code is specifically versioned to only be betterC, it would be a really bad idea to rely on that behavior. And if assert ever gets fixed such that it properly rejects messages that aren't actually strings, passing it a char[] wouldn't work anymore anyway.

- Jonathan M Davis