I can crash program (core.exception.InvalidMemoryOperationError) by using __FUNCTION__ in a class ctor

Steven Schveighoffer schveiguy at gmail.com
Fri Jan 18 15:35:12 UTC 2019


On 1/18/19 10:29 AM, James Blachly wrote:
> First, let me apologize for not being able to reduce this to a minimal 
> test case. I have tried to do so but was unsuccessful. Offending code, 
> and links to codebase at bottom.
> 
> TL;DR: I believe this is a compiler error. Please help me prove it, or 
> teach me where I've gone wrong.
> 
> ---
> 
> Briefly, I have a class, SAMRecord, that contains: a default 
> constructor, a non-default ctor, and a destructor, each of which for 
> debugging purposes emits a log-line that includes __FUNCTION__.
> 
> When the default ctor passes __FUNCTION__ to the log function, the 
> program will crash after about 15k or 30k objects are created when using 
> dmd or ldc2, respectively.  Further, if I change __FUNCTION__ to any 
> other string (except __MODULE__, which induces the same behavior), the 
> program operates flawlessly.
> 
> The error is:
> 
> core.exception.InvalidMemoryOperationError at src/core/exception.d(700): 
> Invalid memory operation
> 
> and is thrown at the the time that when under normal operation the GC 
> would start clearing out the old objects.
> 
> _Importantly, my code never calls the default constructor_ -- This part 
> puzzles me the most.
> 
> Additional data points:
> 1. I added a dummy log function to demonstrate it (likely?) not my 
> library code offending
> 2. adding assert statements into the default constructor seems to elide 
> the offending behaviour (i.e., I can leave the __FUNCTION__ in without 
> memory error)
> 3. Commenting out the usage of __FUNCTION__ in the non-default ctor, 
> which IS being used, does not prevent the error.
> 
> Overall, the error seems to be related to passing __FUNCTION__ out of 
> default class constructors, even when that constructor is not called 
> explicitly by user code. Altering compiler behavior by e.g. adding 
> assert(0) prevents the error. Thus, I conclude this is almost surely a 
> compiler error, but I would be glad to be proven wrong.
> 
> I would be happy to provide any more information, or to help develop an 
> appropriate minimal reproducible bug, if given appropriate guidance. 
> Otherwise, to replicate this you will need to install a C library. 
> Details below.
> 
> Thanks all in advance.
> James
> 
> ---
> DMD: v2.081.2
> LDC2: 1.11.0
> repository: https://github.com/blachlylab/dhtslib/
> commit: ee87434d0c48919aaa0ff6d04d6e0ca403564618 (current as of this post)
> requirement: htslib; https://github.com/samtools/htslib
> 
> To replicate:
> 1. Download any BAM file. (random BAM file: 
> https://www.encodeproject.org/files/ENCFF765NLQ/@@download/ENCFF765NLQ.bam 
> )
> 2. Update test/samreader.d to open your BAM file (sorry I have not 
> parameterized this, have been tearing hair about re this crash for hours)
> 3. `dub build -c sam_test && ./samreader`
> 
> Attempt at minimal reproducible test-case (that does NOT trigger the 
> error): repo/test/bug_minimal.d
> 
> Offending code section:
> (note that I wrote test_log as a substitute for hts_log_debug to prove 
> that the error is still triggered even when not calling library code)
> ```
> import core.stdc.stdlib: calloc, free;
> import std.format;
> import std.parallelism: totalCPUs;
> import std.stdio: writeln, writefln;
> import std.string: fromStringz, toStringz;
> 
> import dhtslib.htslib.hts: htsFile, hts_open, hts_close;
> import dhtslib.htslib.hts: hts_itr_t;
> import dhtslib.htslib.hts: seq_nt16_str;
> import dhtslib.htslib.hts: hts_set_threads;
> 
> import dhtslib.htslib.hts_log;
> import dhtslib.htslib.kstring;
> import dhtslib.htslib.sam;
> 
> void test_log(string ctx, string msg)
> {
>      import std.stdio;
>      stderr.writeln(ctx, msg);
> }
> 
> /**
> Encapsulates a SAM/BAM/CRAM record,
> using the bam1_t type for memory efficiency,
> and the htslib helper functions for speed.
> **/
> class SAMRecord {
>      ///
>      bam1_t *b;
> 
>      ///
>      this()
>      {
>          //debug(dhtslib_debug) hts_log_debug(__FUNCTION__, "ctor()"); 
> /// This line triggers memory error when __FUNCTION__, but not when 
> "Other string"
>          test_log(__FUNCTION__, "ctor()");   /// This line will also 
> trigger the memory error when __FUNCTION__, but not other strings
>          //writeln(__FUNCTION__);    // this will not trigger the memory 
> error
>          this.b = bam_init1();
>          //assert(0);                // This will elide(?) the memory error
>          //assert(1 == 2);           // This will elide(?) the memory error
>      }
>      ///
>      this(bam1_t *b)
>      {
>          debug(dhtslib_debug) hts_log_debug(__FUNCTION__, "ctor(bam1_t 
> *b)");
>          this.b = b;
>      }
>      ~this()
>      {
>          writeln("Record dtor");
>          debug(dhtslib_debug) hts_log_debug(__FUNCTION__, "dtor");
>          //bam_destroy1(this.b); // we don't own it!
>      }
> }
> ```


https://github.com/blachlylab/dhtslib/blob/master/source/dhtslib/htslib/hts_log.d#L94

That is your problem. toStringz is going to attempt to allocate using 
the GC. Allocating during a GC collection causes an 
InvalidMemoryOperationError.

Take that out of your destructor, and you should not cause that error.

Alternatively, fix the logger so it doesn't use the GC.

-Steve


More information about the Digitalmars-d mailing list