Skip to content

Odd Buffer behavior that leads to crash in iojs 3.0 #2484

Closed
@unbornchikken

Description

@unbornchikken

Hi,

We're in progress to do a PR to popular ffi module for supporting io.js 3.0. It relies on the ref module to do its kinda pointer stuff, so it is important to make ref module to support io.js 3.0 at first.

There I'm stuck with an issue that is not related to ref, that is related to io.js instead, especially to its new Buffer implementation.

I've isolated a repro case there: https://github.com/unbornchikken/ref/commit/270ca83dc3a85772ec60fba66d58920befd98d01 There is a unit test in test/iojs3issue.js:

describe('iojs3issue', function () {
    it('should not crash', function() {
        for (var i = 0; i < 10; i++) {
            gc()
            var buf = new Buffer(8)
            buf.fill(0)
            var buf2 = ref.ref(buf)
            var buf3 = ref.deref(buf2)
        }
    })
    it('should crash', function() {
        for (var i = 0; i < 10; i++) {
            gc()
            var buf = new Buffer(7)
            buf.fill(0)
            var buf2 = ref.ref(buf)
            var buf3 = ref.deref(buf2)
        }
    })
})

Fist one doesn't crash but the second does.

The explanation of the code:

ref.ref(buf) means that:

So we have a buffer that has 8 bytes (on x64) that is actually a pointer to address of buff's data.

ref.deref(buf2) means that:

Despite the described magic, in the above tests we end up having two Buffers have exactly the same memory location in those data pointer, and the first one is the owner, it has to free the memory, the other one is just referencing that (because there is an empty free callback at WrapPointer: https://github.com/unbornchikken/ref/blob/io.js-3.0-support/src/binding.cc#L145).

Now, that situation lead to a crash when invoking a GC. It seems somehow the size of the original buffer affecting the crash, because if it 7 or lower it won't happen, only after 8. The other weird thing is that if I modify the loop in the second test to have end condition of 2, the crash won't happen. It seems the trigger is 3. So it have to be some internal optimization that take account and somehow ignores the fact that there is a free callback for the second buffer, and tries to free it memory that is at that time already freed.

I've launched a debugger, and got the stack trace of the crash, but I don't have enough v8 skills to decrypt what happened:

>   iojs.exe!v8::internal::Map::instance_type() Line 4557   C++
    iojs.exe!v8::internal::ShortCircuitConsString(v8::internal::Object * * p) Line 1288 C++
    iojs.exe!v8::internal::MarkCompactMarkingVisitor::MarkObjectByPointer(v8::internal::MarkCompactCollector * collector, v8::internal::Object * * anchor_slot, v8::internal::Object * * p) Line 1364   C++
    iojs.exe!v8::internal::MarkCompactMarkingVisitor::VisitPointers(v8::internal::Heap * heap, v8::internal::Object * * start, v8::internal::Object * * end) Line 1340  C++
    iojs.exe!v8::internal::StaticMarkingVisitor<v8::internal::MarkCompactMarkingVisitor>::VisitJSArrayBuffer(v8::internal::Map * map, v8::internal::HeapObject * object) Line 538   C++
    iojs.exe!v8::internal::StaticMarkingVisitor<v8::internal::MarkCompactMarkingVisitor>::IterateBody(v8::internal::Map * map, v8::internal::HeapObject * obj) Line 405 C++
    iojs.exe!v8::internal::MarkCompactCollector::EmptyMarkingDeque() Line 2113  C++
    iojs.exe!v8::internal::RootMarkingVisitor::MarkObjectByPointer(v8::internal::Object * * p) Line 1763    C++
    iojs.exe!v8::internal::RootMarkingVisitor::VisitPointer(v8::internal::Object * * p) Line 1732   C++
    iojs.exe!v8::internal::GlobalHandles::IterateWeakRoots(v8::internal::ObjectVisitor * v) Line 608    C++
    iojs.exe!v8::internal::MarkCompactCollector::MarkLiveObjects() Line 2381    C++
    iojs.exe!v8::internal::MarkCompactCollector::CollectGarbage() Line 339  C++
    iojs.exe!v8::internal::Heap::MarkCompact() Line 1300    C++
    iojs.exe!v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector collector, const v8::GCCallbackFlags gc_callback_flags) Line 1185  C++
    iojs.exe!v8::internal::Heap::CollectGarbage(v8::internal::GarbageCollector collector, const char * gc_reason, const char * collector_reason, const v8::GCCallbackFlags gc_callback_flags) Line 911  C++
    iojs.exe!v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace space, const char * gc_reason, const v8::GCCallbackFlags callbackFlags) Line 536  C++
    iojs.exe!v8::internal::Heap::CollectAllGarbage(int flags, const char * gc_reason, const v8::GCCallbackFlags gc_callback_flags) Line 796 C++
    iojs.exe!v8::Isolate::RequestGarbageCollectionForTesting(v8::Isolate::GarbageCollectionType type) Line 6747 C++
    iojs.exe!v8::internal::GCExtension::GC(const v8::FunctionCallbackInfo<v8::Value> & args) Line 24    C++
    iojs.exe!v8::internal::FunctionCallbackArguments::Call(void (const v8::FunctionCallbackInfo<v8::Value> &) * f) Line 34  C++
    iojs.exe!v8::internal::HandleApiCallHelper<0>(v8::internal::Isolate * isolate, v8::internal::`anonymous-namespace'::BuiltinArguments<1> & args) Line 1110   C++
    iojs.exe!v8::internal::Builtin_Impl_HandleApiCall(v8::internal::`anonymous-namespace'::BuiltinArguments<1> args, v8::internal::Isolate * isolate) Line 1133 C++
    iojs.exe!v8::internal::Builtin_HandleApiCall(int args_length, v8::internal::Object * * args_object, v8::internal::Isolate * isolate) Line 1128  C++

Metadata

Metadata

Assignees

No one assigned

    Labels

    bufferIssues and PRs related to the buffer subsystem.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions