Jump to page: 1 2
Thread overview
Throwing InvalidMemoryOperationError
Jun 04, 2015
Etienne
Jun 04, 2015
Etienne Cimon
Jun 04, 2015
Adam D. Ruppe
Jun 04, 2015
Etienne Cimon
Jun 04, 2015
Adam D. Ruppe
Jun 04, 2015
Etienne Cimon
Jun 04, 2015
Adam D. Ruppe
Jun 04, 2015
Adam D. Ruppe
Jun 04, 2015
Etienne Cimon
Jun 04, 2015
Adam D. Ruppe
Jun 04, 2015
Etienne Cimon
Jun 04, 2015
Etienne Cimon
June 04, 2015
What's up with throwing this error?

It's completely unrecoverable when the called through the GC finalization process, the GC mutex doesn't unlock and all you get is a deadlock.

Why not just let it fail with an access violation of some sort? At least you get a clean stack trace from a debugger of choice, and you're not stuck figuring out which completely random operation occurred.
June 04, 2015
On another note, considering the unimaginable amount of bugs that can stem from throwing in a constructor or destructor, I don't see why D shouldn't just enforce a nothrow on them.
June 04, 2015
On Thursday, 4 June 2015 at 16:12:54 UTC, Etienne Cimon wrote:
> On another note, considering the unimaginable amount of bugs that can stem from throwing in a constructor

Throwing from a constructor is kinda important as it is the only way to signal failure on its input...
June 04, 2015
On Thursday, 4 June 2015 at 16:20:28 UTC, Adam D. Ruppe wrote:
> On Thursday, 4 June 2015 at 16:12:54 UTC, Etienne Cimon wrote:
>> On another note, considering the unimaginable amount of bugs that can stem from throwing in a constructor
>
> Throwing from a constructor is kinda important as it is the only way to signal failure on its input...

Wouldn't that be with `this() in { assert() }` ?

My concern is the fact that the destructor won't be called. Or will it?
June 04, 2015
On Thursday, 4 June 2015 at 16:32:39 UTC, Etienne Cimon wrote:
> Wouldn't that be with `this() in { assert() }` ?

Not necessarily. Consider something like a file wrapper, if fopen is null inside the ctor, you'd generally throw on that.

> My concern is the fact that the destructor won't be called. Or will it?

It won't be for deterministic objects (structs on the stack) but is for GC'd objects (eventually).

I don't think it should be called since if the constructor fails, the object doesn't really exist and there's nothing to destroy. You can reliably clean up intermediate things in a constructor using scope(failure):

struct Foo {
   FILE* file, file2;
   this(something somename) {
        file = fopen(somename);
        if(file is null) throw FileException(somename);
        scope(failure) { fclose(file); file = null; }
        file2 = fopen(somename2);
        if(file2 is null) throw FileException(somename2);
   }
   ~this() {
      if(file !is null) fclose(file);
      if(file2 !is null) fclose(file2);
   }
}



That'd work whether the destructor is called automatically or not and isn't too hard to write since scope(failure) is pretty convenient.
June 04, 2015
On Thursday, 4 June 2015 at 16:49:07 UTC, Adam D. Ruppe wrote:
> On Thursday, 4 June 2015 at 16:32:39 UTC, Etienne Cimon wrote:
>> Wouldn't that be with `this() in { assert() }` ?
>
> Not necessarily. Consider something like a file wrapper, if fopen is null inside the ctor, you'd generally throw on that.
>
>> My concern is the fact that the destructor won't be called. Or will it?
>
> It won't be for deterministic objects (structs on the stack) but is for GC'd objects (eventually).
>
> I don't think it should be called since if the constructor fails, the object doesn't really exist and there's nothing to destroy. You can reliably clean up intermediate things in a constructor using scope(failure):
>
> struct Foo {
>    FILE* file, file2;
>    this(something somename) {
>         file = fopen(somename);
>         if(file is null) throw FileException(somename);
>         scope(failure) { fclose(file); file = null; }
>         file2 = fopen(somename2);
>         if(file2 is null) throw FileException(somename2);
>    }
>    ~this() {
>       if(file !is null) fclose(file);
>       if(file2 !is null) fclose(file2);
>    }
> }
>
>
>
> That'd work whether the destructor is called automatically or not and isn't too hard to write since scope(failure) is pretty convenient.

Nice, I'll try and use that once I find the reason I get this error:

https://travis-ci.org/etcimon/botan/jobs/65410185#L426

Somewhere random in a 100k line code base, a deadlock is triggered in the GC by some object's destructor. :/
June 04, 2015
On Thursday, 4 June 2015 at 16:05:37 UTC, Etienne wrote:
> Why not just let it fail with an access violation of some sort?

You can do this at least with a filthy hack. Add this to your file with main():

extern(C) void onInvalidMemoryOperationError(void*) {
   asm { int 3; }
}


Then recompile your program.

class Fail {
        ~this() { auto a = new int; }
}
/*
extern(C) void onInvalidMemoryOperationError(void*) {
   asm { int 3; }
}
*/
void main() {
        auto fail = new Fail();
}


Before:

core.exception.InvalidMemoryOperationError@(0)

After:

Trace/breakpoint trap



`int 3;` is x86/x86_64 talk for "please invoke the debugger" and the extern(C) onInvalidMemoryOperationError is what the GC calls to throw that exception.

Since the function is in the druntime.lib file though.... you can easily override it by simply providing a replacement file in your main application. The linker prefers versions of functions from .o files over .lib files.

(This also works on Windows btw, I'm just using the linux terms cuz that's what I'm on right now)



So now the magic comes if we run the program in gdb:

$ dmd b.d -g

$ gdb b
(gdb) r

Program received signal SIGTRAP, Trace/breakpoint trap.
0x000000000041d0c5 in onInvalidMemoryOperationError ()
=> 0x000000000041d0c5 <onInvalidMemoryOperationError+13>:       c9      leave

(gdb) where
#0  onInvalidMemoryOperationError (
    _param_0=0x6446a0 <gc.gc.GC.mutexStorage()+16>) at b.d:7
#1  0x0000000000425dfc in gc.gc.GC.malloc() ()
#2  0x0000000000421525 in gc_qalloc ()
#3  0x000000000041e8e1 in _d_newitemT ()
#4  0x000000000041d0b3 in b.Fail.__dtor() (this=0x7ffff7eb8000) at b.d:2
#5  0x0000000000435878 in rt_finalize2 ()
#6  0x000000000042d016 in rt_finalizeFromGC ()
#7  0x0000000000429930 in gc.gc.Gcx.sweep() ()
#8  0x000000000042a04b in gc.gc.Gcx.fullcollect() ()
#9  0x000000000042bed9 in gc_term ()
#10 0x0000000000421a9b in rt_term ()
#11 0x000000000041e73e in rt.dmain2._d_run_main() ()
#12 0x000000000041e6ca in rt.dmain2._d_run_main() ()
#13 0x000000000041e644 in _d_run_main ()
#14 0x000000000041d115 in main ()



Line #4 there is the winner! b.d, line 2, my destructor allocation :)
June 04, 2015
If int 3 doesn't work for some reason btw, you could always just deliberately write to a null pointer and trigger a segfault in the overridden function, would have the same result in the debugger.

I feel like this onError thing is meant to be overridable by importing core.exception too, but I don't see that in the source. The linker-based override definately works today though!
June 04, 2015
On Thursday, 4 June 2015 at 17:43:35 UTC, Adam D. Ruppe wrote:
> If int 3 doesn't work for some reason btw, you could always just deliberately write to a null pointer and trigger a segfault in the overridden function, would have the same result in the debugger.
>
> I feel like this onError thing is meant to be overridable by importing core.exception too, but I don't see that in the source. The linker-based override definately works today though!

So far I've tried the null pointer to get a segmentation fault. It failed. I'm trying to rebuild gdb because this error is what I got:

     Message: Process 61701 (gdb) of user 0 dumped core.

                Stack trace of thread 61701:
                #0  0x0000000000629bdf make_vector_type (gdb)
                #1  0x0000000000670a18 read_type_die (gdb)
                #2  0x000000000066ee87 lookup_die_type (gdb)
                #3  0x000000000067002a read_type_die (gdb)
                #4  0x000000000066ee87 lookup_die_type (gdb)
                #5  0x0000000000672aeb new_symbol_full (gdb)
                #6  0x0000000000674e1f process_die (gdb)
                #7  0x0000000000674a2b process_die (gdb)
                #8  0x0000000000675031 process_die (gdb)
                #9  0x0000000000678e57 dw2_do_instantiate_symtab (gdb)
                #10 0x0000000000679f38 dwarf2_read_symtab (gdb)
                #11 0x00000000005e4451 psymtab_to_symtab (gdb)
                #12 0x00000000005e54d3 find_pc_sect_symtab_from_partial (g
                #13 0x00000000005e0143 find_pc_sect_symtab (gdb)
                #14 0x00000000005dc3dd blockvector_for_pc_sect (gdb)
                #15 0x00000000005dc57d block_for_pc (gdb)
                #16 0x00000000006e70bb inline_frame_sniffer (gdb)
                #17 0x00000000006e52c6 frame_unwind_try_unwinder (gdb)
                #18 0x00000000006e567f frame_unwind_find_by_frame (gdb)
                #19 0x00000000006e1d2b get_prev_frame_if_no_cycle (gdb)
                #20 0x00000000006e4029 get_prev_frame_always (gdb)
                #21 0x00000000006e4761 get_prev_frame (gdb)
                #22 0x00000000006e4a3c unwind_to_current_frame (gdb)
                #23 0x0000000000610b71 catch_exceptions_with_msg (gdb)
                #24 0x00000000006e1e40 get_current_frame (gdb)
                #25 0x0000000000603909 handle_inferior_event.part.32 (gdb)
                #26 0x00000000006055ee fetch_inferior_event (gdb)
                #27 0x000000000061c7f2 inferior_event_handler (gdb)
                #28 0x000000000061a7d1 process_event (gdb)
                #29 0x000000000061abca gdb_do_one_event (gdb)
                #30 0x000000000061ae3e start_event_loop (gdb)
                #31 0x0000000000613c13 captured_command_loop (gdb)
                #32 0x0000000000610d3a catch_errors (gdb)
                #33 0x0000000000615526 captured_main (gdb)
                #34 0x0000000000610d3a catch_errors (gdb)
                #35 0x000000000061568b gdb_main (gdb)
                #36 0x00000000004604a5 main (gdb)
                #37 0x00007f3d3893ffe0 __libc_start_main (libc.so.6)
                #38 0x00000000004604e8 _start (gdb)
June 04, 2015
On Thursday, 4 June 2015 at 17:51:31 UTC, Etienne Cimon wrote:
> I'm trying to rebuild gdb because this error is what I got:

wow that's messed up. Did you try it with dmd -gc too? Or a non-debug version of the program entirely? Maybe your version of gdb has a bug in reading D debugging info.

With a non-debug, you won't get line numbers in the stack trace, but the mangled function name should still really narrow down your search. (there's a ddemangle program that comes with dmd that can translate it or reading by eyeball isn't bad either, should see your class name in there)
« First   ‹ Prev
1 2