HaxeFoundation / hxcpp

Runtime files for c++ backend for haxe
Other
298 stars 189 forks source link

Segfaults can be caused by the GC when adding lots of strings. #1020

Closed slontis closed 1 year ago

slontis commented 1 year ago

I am attempting to reads large amounts of bytes from a file and store it into lines of strings that can then be displayed in a UI component.

The following code produces problems in the GC. (Using Haxe 4.2.5 and hxcpp 4.2.1)

class Main {

    private static function stringTest(strs) {
          var line = "";
          var count = 0;
          var sz = 32*0x8000;
          for (i in 0...sz) {
            line = line + "$" + StringTools.hex(i & 0xFF, 2) + ",";
            if (++count == 32) {
                count  = 0;
                strs.push(line);
                line = "";
            }
          }
    }
    static function main() {
    var strs = new Array<String>();
    stringTest(strs);
    }
}

Valgrind shows the following info many times:

==733408== Conditional jump or move depends on uninitialised value(s) ==733408== at 0x1FBB75: GlobalAllocator::IsAllBlock(BlockData) (Immix.cpp:5432) ==733408== by 0x1FBC00: GlobalAllocator::GetMemType(void) (Immix.cpp:5449) ==733408== by 0x1F4986: hx::MarkConservative(int, int, hx::MarkContext) (Immix.cpp:5563) ==733408== by 0x1FCA4F: LocalAllocator::Mark(hx::MarkContext) (Immix.cpp:6225) ==733408== by 0x1F4B63: MarkLocalAlloc(LocalAllocator, hx::MarkContext) (Immix.cpp:6297) ==733408== by 0x1FA869: GlobalAllocator::MarkAll(bool) (Immix.cpp:4677) ==733408== by 0x1FABB0: GlobalAllocator::Collect(bool, bool, bool, bool) (Immix.cpp:4847) ==733408== by 0x1F941A: GlobalAllocator::GetFreeBlock(int, hx::ImmixAllocator*) (Immix.cpp:3530) ==733408== by 0x1FC864: LocalAllocator::CallAlloc(int, unsigned int) (Immix.cpp:6156) ==733408== by 0x1F50EE: hx::InternalNew(int, bool) (Immix.cpp:6487) ==733408== by 0x217D57: hx::NewString(int) (GcCommon.cpp:116) ==733408== by 0x21C78C: String::operator+(String const&) const (String.cpp:2048)

I don't see much in the way of NULL checking in the Immix code. Is it possible that an alloc could return NULL?

hughsando commented 1 year ago

The stack scanning used by hxcpp may potentially read uninitialized memory, but is supposed to be "conservative" with random values - that is, treat as worst case and not crash. The null checking would typically happen when "blocks" are allocated, and then it is know that the block pointers are not null. However, it the strings get very long, then maybe an alternate allocator is used and the null check should happen near GCLOG("Memory Exhausted!\n"); It looks like InternalCreateConstBuffer could use a null-check, although this is unlikely to be the cause of your problem. Does this sample crash for you, or does it just show vargrind issues?

There are a number of things you can do to debug GC issues.

  1. Does it crash in "-debug" mode?
  2. You can uncomment #define HXCPP_GC_DEBUG_LEVEL 1 in Immix.cpp and see if this helps. It should also provide better information if the program crashes.
  3. You can compile haxe with "-D HXCPP_GC_CHECK_POINTER" which should help localize the issue.
slontis commented 1 year ago

Having lots of strings just forces a collect to happen. Forcing it to run the GC collect without having the strings seems to give me the same issue. I am getting an "Old object access" when I do the collect - which seems to correspond to the problem I am seeing. Not sure what to do from here, (trying to figure out the GC code is not ideal for a newbie).

slontis commented 1 year ago

I think I found the cause of the Issue.. Thanks for your help.