January 24, 2007
Walter Bright wrote:
> Stewart Gordon wrote:
>> Lionello Lunesu wrote:
>>> I really think this used to work (like in C++) :
>>>
>>> #class Class {}
>>> #void main(){
>>> #    Class c;
>>> #    assert(c);
>>> #}
>>>
>>> With 1.0, I get an access violation in _D9invariant12_d_invariantFC6ObjectZv, but why?
>> <snip>
>>
>> For some strange reason, assert on an object reference checks that the invariants are satisfied instead of that the reference isn't null. There's nothing to this effect in the spec, so I don't know how it came about.  While it may be useful, it certainly shouldn't do it _instead of_ checking it isn't null.
> 
> It does check if it's null. That's how the access violation exception gets thrown.

Access violations are hardware-generated, and not a particularly nice way to detect an error. Especially since no usable file & line information is generated.

The invariant checking code also contains "assert(o !is null)" before it does anything else, but that code is in Phobos. And the Phobos libs distributed with DMD seem to be compiled with '-release', stripping that code.
And even if it wasn't, the file & line displayed on failure would be Phobos' internal/invariant.d:14 or similar instead of the place the user put an assert(obj).
January 25, 2007
Walter Bright wrote:
> Stewart Gordon wrote:
>> Lionello Lunesu wrote:
>>> I really think this used to work (like in C++) :
>>>
>>> #class Class {}
>>> #void main(){
>>> #    Class c;
>>> #    assert(c);
>>> #}
>>>
>>> With 1.0, I get an access violation in _D9invariant12_d_invariantFC6ObjectZv, but why?
>> <snip>
>>
>> For some strange reason, assert on an object reference checks that the invariants are satisfied instead of that the reference isn't null. There's nothing to this effect in the spec, so I don't know how it came about.  While it may be useful, it certainly shouldn't do it _instead of_ checking it isn't null.
>>
>> Stewart.
> 
> It does check if it's null. That's how the access violation exception gets thrown.

Perhaps this is a better example.

import std.stdio;
import std.asserterror;
class Class {}
void main() {
  Class c;
  try {
    version (alt) { assert(false); }
    assert(c);
  } catch (AssertError e) {
    writefln("An assert failed: ", e);
  }
}

C:\>dmd assertNull.d
c:\dmd\bin\..\..\dm\bin\link.exe assertNull,,,user32+kernel32/noi;

C:\>assertNull
Error: Access Violation

C:\>dmd assertNull.d -version=alt
c:\dmd\bin\..\..\dm\bin\link.exe assertNull,,,user32+kernel32/noi;

C:\>assertNull
An assert failed: AssertError Failure assertNull(7)

January 26, 2007
Sean Kelly wrote:
> Walter Bright wrote:
>> It does check if it's null. That's how the access violation exception gets thrown.
> 
> But that's generated by the hardware, isn't it?

Yes.

> Shouldn't assert explicitly check whether c is null before calling its invariant?

Why, if the hardware does it for you (without extra bloat)?
January 26, 2007
Frits van Bommel wrote:
> Walter Bright wrote:
>> It does check if it's null. That's how the access violation exception gets thrown.
> Access violations are hardware-generated, and not a particularly nice way to detect an error. Especially since no usable file & line information is generated.

If you run it under the debugger, it should pop up with the cursor on where it faulted (line and file). If it doesn't, it's a bug in the debugger or the debug output of the compiler.
January 26, 2007
Bradley Smith wrote:
>   } catch (AssertError e) {

You can catch access errors by catching Exception, at least under Windows, because I haven't figured out how to do it under Linux.
January 26, 2007
Walter Bright wrote:
> You can catch access errors by catching Exception, at least under Windows, because I haven't figured out how to do it under Linux.

I know. That is not the point. The point is that evaluation of a null reference is inconsistent. For an if statement, null is evaluated as false, but for an assert it is an access violation. See code below.

Conceptually, assert (expression) is nothing more that "if (!expression) throw new AssertError;". However, it doesn't act that way.

import std.stdio;
class Class {}
void main() {
  Class c;
  if (!c) {
    writefln("c is false?");
  }
  assert(c);
}
January 26, 2007
> Why, if the hardware does it for you (without extra bloat)?

Is it possible to catch a segmentation fault and convert it into a InvalidPointerException and continue?

January 26, 2007
> Conceptually, assert (expression) is nothing more that "if (!expression)
> throw new AssertError;". However, it doesn't act that way.

I think it is more like
if ( expression != 0 )
  throw new AssertError;

that means .opEquals is called probably on a null reference, and so the segfault is caused.

Hm...
Or is that what '!' does?

January 26, 2007
Walter Bright wrote:
> Sean Kelly wrote:
>> Walter Bright wrote:
>>> It does check if it's null. That's how the access violation exception gets thrown.
>>
>> But that's generated by the hardware, isn't it?
> 
> Yes.
> 
>> Shouldn't assert explicitly check whether c is null before calling its invariant?
> 
> Why, if the hardware does it for you (without extra bloat)?

Just so a file and line number are available.  Though someone mentioned the code already does something like "assert(obj); obj.invariant();" and the problem here was that it was a release build?  If this is the case I'm fine with the current behavior.  I really only care about this sort of thing if asserts are enabled.


Sean
January 26, 2007
Walter Bright wrote:
> Bradley Smith wrote:
>>   } catch (AssertError e) {
> 
> You can catch access errors by catching Exception, at least under Windows, because I haven't figured out how to do it under Linux.

With Unix you have to set up a signal handler for SIGSEGV.  However, I'm not entirely sure whether exceptions can be thrown from within signal handlers.  If anyone knows I'd love to hear the answer.


Sean