www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - null dereference

reply luka8088 <luka8088 owave.net> writes:
I was thinking and I am not sure about the reason for not having some
king of safeguard for null dereferencing in version(assert)/debug builds.

One possible reason that comes to mind is that it somewhat affects
performance but should this really be an issue in version(assert)/debug
build? Especially given the benefit of having file/line number and stack
information outputted.




module program;

import std.stdio;

void main () {

  A a1 = new A();

  // auto inserted by the compiler before accessing object member
  // on version(assert) (or maybe on debug?)
  version(assert)
    if (a1 is null)
      throw new NullDereferenceError("Null Dereference");

  a1.f();

  A a2;

  // auto inserted by the compiler before accessing object member
  // on version(assert) (or maybe on debug?)
  version(assert)
    if (a2 is null)
      throw new NullDereferenceError("Null Dereference");

  a1.f();

}

class A {

  void f () {
    writeln("A.f called");
  }

}

class NullDereferenceError : Error {
  this (string msg, string file = __FILE__, size_t line = __LINE__) {
    super(msg, file, line);
  }
}
Mar 15 2014
next sibling parent reply "bearophile" <bearophileHUGS lycos.com> writes:
luka8088:

 I was thinking and I am not sure about the reason for not 
 having some
 king of safeguard for null dereferencing in 
 version(assert)/debug builds.

Eventually reference deference in D will be guarded by an assert in non-release builds. This desire is a raising tide that eventually can't be stopped. Bye, bearophile
Mar 15 2014
next sibling parent luka8088 <luka8088 owave.net> writes:
On 15.3.2014. 12:25, bearophile wrote:
 luka8088:
 
 I was thinking and I am not sure about the reason for not having some
 king of safeguard for null dereferencing in version(assert)/debug builds.

Eventually reference deference in D will be guarded by an assert in non-release builds. This desire is a raising tide that eventually can't be stopped. Bye, bearophile

I am very glad to hear that! It is very frustrating for a program to segfault without giving any information (on linux) even in debug mode.
Mar 15 2014
prev sibling parent reply Ary Borenszweig <ary esperanto.org.ar> writes:
On 3/15/14, 8:25 AM, bearophile wrote:
 luka8088:

 I was thinking and I am not sure about the reason for not having some
 king of safeguard for null dereferencing in version(assert)/debug builds.

Eventually reference deference in D will be guarded by an assert in non-release builds. This desire is a raising tide that eventually can't be stopped. Bye, bearophile

Really? I thought Walter was against this. He always says you can fire up a debugger and check where the dereference occurred.
Mar 15 2014
parent luka8088 <luka8088 owave.net> writes:
On 15.3.2014. 14:34, Ary Borenszweig wrote:
 On 3/15/14, 8:25 AM, bearophile wrote:
 luka8088:

 I was thinking and I am not sure about the reason for not having some
 king of safeguard for null dereferencing in version(assert)/debug
 builds.

Eventually reference deference in D will be guarded by an assert in non-release builds. This desire is a raising tide that eventually can't be stopped. Bye, bearophile

Really? I thought Walter was against this. He always says you can fire up a debugger and check where the dereference occurred.

Hm, that is true, but i think it should be a default behavior in version(assert). I saw many discussions on this topic and many arguments but I am still not able to digest that the following produces invalid memory operation: void main () safe { Object o = null; o.toHash(); }
Mar 15 2014
prev sibling next sibling parent reply "Adam D. Ruppe" <destructionator gmail.com> writes:
There is a hidden module on Linux which can activate this, sort 
of:

void main() {
    // these two lines turn it on
    import etc.linux.memoryerror;
    registerMemoryErrorHandler();

    Object o = null;
    o.toString(); // trigger it here
}

etc.linux.memoryerror.NullPointerError src/etc/linux/memoryerror.d(325):
----------------
./test56(void etc.linux.memoryerror.sigsegvDataHandler()+0xb) 
[0x805d44b]
./test56(_Dmain+0xa) [0x805c8ea]
./test56(void rt.dmain2._d_run_main(int, char**, extern (C) int 
function(char[][])*).runAll().void __lambda1()+0x10) [0x805cb58]
./test56(void rt.dmain2._d_run_main(int, char**, extern (C) int 
function(char[][])*).tryExec(scope void delegate())+0x18) 
[0x805cad0]
./test56(void rt.dmain2._d_run_main(int, char**, extern (C) int 
function(char[][])*).runAll()+0x27) [0x805cb1f]
./test56(void rt.dmain2._d_run_main(int, char**, extern (C) int 
function(char[][])*).tryExec(scope void delegate())+0x18) 
[0x805cad0]
./test56(_d_run_main+0x117) [0x805ca67]
./test56(main+0x14) [0x805c90c]
/lib/libc.so.6(__libc_start_main+0xe6) [0xf75f3b86]
./test56() [0x805c831]




As you can see there, the top line doesn't really help much, it 
just lists the  druntime module, but the stack trace can help: it 
shows _Dmain in there, and if you add other functions, you can 
see them too.

So still not quite a file+line number for the exact place (you 
can get that in a debugger fairly easily though) but helps find 
where it is, especially if you have fairly small functions.
Mar 15 2014
parent luka8088 <luka8088 owave.net> writes:
On 15.3.2014. 16:37, Adam D. Ruppe wrote:
 There is a hidden module on Linux which can activate this, sort of:
 
 void main() {
    // these two lines turn it on
    import etc.linux.memoryerror;
    registerMemoryErrorHandler();
 
    Object o = null;
    o.toString(); // trigger it here
 }
 
 etc.linux.memoryerror.NullPointerError src/etc/linux/memoryerror.d(325):
 ----------------
 ../test56(void etc.linux.memoryerror.sigsegvDataHandler()+0xb) [0x805d44b]
 ../test56(_Dmain+0xa) [0x805c8ea]
 ../test56(void rt.dmain2._d_run_main(int, char**, extern (C) int
 function(char[][])*).runAll().void __lambda1()+0x10) [0x805cb58]
 ../test56(void rt.dmain2._d_run_main(int, char**, extern (C) int
 function(char[][])*).tryExec(scope void delegate())+0x18) [0x805cad0]
 ../test56(void rt.dmain2._d_run_main(int, char**, extern (C) int
 function(char[][])*).runAll()+0x27) [0x805cb1f]
 ../test56(void rt.dmain2._d_run_main(int, char**, extern (C) int
 function(char[][])*).tryExec(scope void delegate())+0x18) [0x805cad0]
 ../test56(_d_run_main+0x117) [0x805ca67]
 ../test56(main+0x14) [0x805c90c]
 /lib/libc.so.6(__libc_start_main+0xe6) [0xf75f3b86]
 ../test56() [0x805c831]
 
 
 
 
 As you can see there, the top line doesn't really help much, it just
 lists the  druntime module, but the stack trace can help: it shows
 _Dmain in there, and if you add other functions, you can see them too.
 
 So still not quite a file+line number for the exact place (you can get
 that in a debugger fairly easily though) but helps find where it is,
 especially if you have fairly small functions.

I was not aware of this. Thanks!
Mar 15 2014
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Saturday, March 15, 2014 10:34:39 Ary Borenszweig wrote:
 On 3/15/14, 8:25 AM, bearophile wrote:
 luka8088:
 I was thinking and I am not sure about the reason for not having some
 king of safeguard for null dereferencing in version(assert)/debug builds.

Eventually reference deference in D will be guarded by an assert in non-release builds. This desire is a raising tide that eventually can't be stopped. Bye, bearophile

Really? I thought Walter was against this. He always says you can fire up a debugger and check where the dereference occurred.

He is against it. Very much so. Maybe it'll happen at some point, but the entire basis for Bearophile's assertion that it's going to happen appears to be simply the fact that many folks want it. And yes, that might eventually sway Walter, but it's far from guaranteed, and honestly, I'd be surprised if it happened. - Jonathan M Davis
Mar 15 2014
prev sibling next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Saturday, March 15, 2014 11:05:42 luka8088 wrote:
 I was thinking and I am not sure about the reason for not having some
 king of safeguard for null dereferencing in version(assert)/debug builds.
 
 One possible reason that comes to mind is that it somewhat affects
 performance but should this really be an issue in version(assert)/debug
 build? Especially given the benefit of having file/line number and stack
 information outputted.

Essentially what it comes down to is the fact that because the OS already detects null pointer dereferences for you (hence the segfault or access violation that you get when it occurs), Walter considers it unnecessary. If you want more details, look at the resulting core dump in a debugger or run the program in a debugger to begin with. Now, that obviously doesn't always work, which is part of why many folks argue in favor of adding additional checks, but that's Walter's position. I believe that there was some work done to make it so that druntime would detect a segfault and print a stacktrace when that happens, but it's not enabled normally, and I don't know quite what state it's in. That would probably be the ideal solution though, since it gives you the stacktrace without requiring additional checks. - Jonathan M Davis
Mar 15 2014
parent luka8088 <luka8088 owave.net> writes:
On 16.3.2014. 0:22, Jonathan M Davis wrote:
 On Saturday, March 15, 2014 11:05:42 luka8088 wrote:
 I was thinking and I am not sure about the reason for not having some
 king of safeguard for null dereferencing in version(assert)/debug builds.

 One possible reason that comes to mind is that it somewhat affects
 performance but should this really be an issue in version(assert)/debug
 build? Especially given the benefit of having file/line number and stack
 information outputted.

Essentially what it comes down to is the fact that because the OS already detects null pointer dereferences for you (hence the segfault or access violation that you get when it occurs), Walter considers it unnecessary. If you want more details, look at the resulting core dump in a debugger or run the program in a debugger to begin with. Now, that obviously doesn't always work, which is part of why many folks argue in favor of adding additional checks, but that's Walter's position. I believe that there was some work done to make it so that druntime would detect a segfault and print a stacktrace when that happens, but it's not enabled normally, and I don't know quite what state it's in. That would probably be the ideal solution though, since it gives you the stacktrace without requiring additional checks. - Jonathan M Davis

That is great. At least in theory, but at the end of the day the following code still performs invalid memory operation and ends up being killed by the os without any change of recovery. void main () safe { Object o = null; o.toHash(); } I saw multiple failed attempts to implement handlers for such operation and throwing exceptions instead. I don't remember why, and I can't find the post, but there where claims that it will never be turned on by default. etc.linux.memoryerror.registerMemoryErrorHandler(); that Adam pointed out is great but somewhat looses it's point if it not turned on by default. Consider a simple scenario. I am writing a vibed application, and I deploy it to a linux server. Now for testing purposes I make a non-release build and add: try { ... } catch (Throwable t) { logCrash(t); } Unfortunately null deference happened and I have no info why. For argument purposes let's say that this happens rarely and I am not able to reproduce it on my machine. Now I go to the docs looking for a way to get more info out of this. And there is nothing about it in the docs.
Mar 16 2014
prev sibling parent "Francesco Cattoglio" <francesco.cattoglio gmail.com> writes:
On Saturday, 15 March 2014 at 23:23:04 UTC, Jonathan M Davis 
wrote:
 I believe that there was some work done to make it so that 
 druntime would
 detect a segfault and print a stacktrace when that happens, but 
 it's not
 enabled normally, and I don't know quite what state it's in. 
 That would
 probably be the ideal solution though, since it gives you the 
 stacktrace
 without requiring additional checks.

 - Jonathan M Davis

I read some _very_ old threads, and apparently a null dereference raised an exception only on Windows, while on linux it still needed work to be implemented properly. Things probably changed a lot in the mean time, but I think the default behaviour should be "throw an exception" on every platform. A core dump with zero extra info is bad. I don't care if 95% of times you can fire up a debugger and find out what happened. This is not something that can be always done.
Mar 16 2014