Pregunta

I am working on a library that implements a reference-counting system. In debug mode, I print out when values are being allocated and deleted. After compiling the test program and running it, it doesn't really run but actually delete itself and leave a stackdump file. Funnily enough this only happened when I used a small helper template class that implements like a scope-based reference manager (decrementing the reference count when the object helper object is destroyed).

Not even GDB could help me because when I instructed it to run main.exe, it said it couldn't open the process.

Description

So first, I looked at the main program that resulted in the described behavior.

// snippet 1
void test1() {
    Handle<String> v = String::New("Fooo!");
    std::cout << v->Get() << "\n";
}

Changing it to the following made the program run again:

// snippet 2
void test1() {
    String* v = String::New("Fooo!");
    std::cout << v->Get() << "\n";
    v->Decref();
}

But I knew that the Handle<T> class was working pretty well actually because the following function ran fine again, without problems or non-deallocated Values. (@agbinfo) The String class is a subclass of Value. The Handle template is designed to work with the Value class so one does not need to manually invoke Value::Decref() when the Value is not needed anymore.

// snippet 3
Value* test2() {
    Handle<HashMap> map = HashMap::New();
    Handle<Value> a = String::New("The Answer");
    Handle<Value> b = Int::New(42);
    map->Put(a, b);

    // It's easier to use the static getter functions when
    // using the Handle helper class because it makes headache
    // to cast them.
    a = map->AsString();
    std::cout << String::Get(a) << "\n";

    // Overwrite the existing entry in the map.
    a = String::New("The Answer");
    b = vv::null();
    map->Put(a, b);

    // But we could also get an actual pointer to the String. We
    // just have to make sure to decrease the reference count as
    // we don't have the Handle helper class that does that based
    // on the scope.
    String* str = map->AsString();
    std::cout << str->Get() << "\n";
    str->Decref();

    // The ownership of the returned Value* is transfered to the
    // caller with Handle<T>::Release()
    return a.Release();
}

Funnily enough, when I replace return a.Release(); in test2() with return nullptr; and invoke test1() but never ever test2(), the program worked fine again.

I reverted back to the state the program crashed and tried to find the issue somewhere deeper in the code. I looked the logging that is done when vv::Value objects become created or deleted. Removing the utils::typestr(Type()) part from vv::Value::Free() makes the program run again!

class Value {

protected:

    /**
     * Must be invoked when an object was allocated and
     * constructed.
     */
    virtual void Init()
    {
        #ifdef VV_DEBUG
        if (vv::GetFlag(SystemFlags_LogGeneration))
            logging::LogValue(this, "Created " + utils::typestr(Type()));
        #endif
    }

    /**
     * Is invoked when the object is about to be deallocated
     * from vv::Value::Decref().
     */
    virtual void Free()
    {
        #ifdef VV_DEBUG
        if (vv::GetFlag(SystemFlags_LogGeneration))
            logging::LogValue(this, "Deleted " /* + utils::typestr(Type()) */);
        #endif
    }

public:

    // ...

};

Now, in vv::utils::typestr(), I convert an integer to a string by interpreting its bytes as characters, because the values that could be returned from vv::Value::Type() are declared as

enum {
    Type_Null = 'null',
    Type_Bool = 'bool',
    Type_Int = 'intg',
    Type_Double = 'dble',
    Type_String = 'strn',
    Type_Array = 'arry',
    Type_List = 'list',
    Type_HashMap = 'hmap',
};

So the code for typestr() is

inline std::string typestr(int id)
{
    const char* buffer = reinterpret_cast<const char*>(&id);
    std::string result = "";
    for (size_t i=0; i < sizeof(int); i++) {
        result.push_back(buffer[i]);
    }

    // Reverse the string if we are on a little-endian system.
    if (vv::utils::bigendian()) {
        std::reverse(result.begin(), result.end());
    }
    return result;
}

Stackdump

For the stackdump, unfortunately it is not generated everytime and I can't currently reproduce it now to get a recent stackdump. But one of the files that were generated looked pretty much like this:

Exception: STATUS_ACCESS_VIOLATION at eip=004022A8
eax=0028ABE4 ebx=0028AC6C ecx=00000001 edx=0000001C esi=00000000 edi=610071D0
ebp=0028AC48 esp=0028AC20 program=C:\Users\niklas\Desktop\cpp-vv\main.exe, pid 6412, thread main
cs=0023 ds=002B es=002B fs=0053 gs=002B ss=002B
Stack trace:
Frame     Function  Args
0028AC48  004022A8 (00000001, 0028AC6C, 80010100, 61007FDA)
0028ACF8  61008039 (00000000, 0028CD84, 610071D0, 00000000)
0028CD58  61005E84 (0028CD84, 00000000, 00000000, 00000000)
0028FF28  61005FF6 (610071D0, 00000000, 00000000, 00000000)
0028FF48  61006F54 (00402225, 00000000, 00000000, 00000000)
0028FF68  00402592 (00402225, 00000000, 00000000, 00000000)
0028FF88  00401015 (FFFDE000, 0028FFD4, 77C49F72, FFFDE000)
0028FF94  773F336A (FFFDE000, 76B6F19F, 00000000, 00000000)
0028FFD4  77C49F72 (00401000, FFFDE000, 00000000, 00000000)
0028FFEC  77C49F45 (00401000, FFFDE000, 00000000, 78746341)
End of stack trace

Compilation Output

g++ -std=c++11 -g -Wno-multichar -Iinclude/ -DVV_DEBUG src/main.cpp -c -oobj/src/main.o
g++ -std=c++11 -g -Wno-multichar -Iinclude/ -DVV_DEBUG src/vv/system.cpp -c -oobj/src/vv/system.o
g++ -std=c++11 -g -Wno-multichar -Iinclude/ -DVV_DEBUG src/vv/logging.cpp -c -oobj/src/vv/logging.o
g++ -std=c++11 -g -Wno-multichar -Iinclude/ -DVV_DEBUG src/vv/Types.cpp -c -oobj/src/vv/Types.o
g++ -std=c++11 -g -Wno-multichar -Iinclude/ -DVV_DEBUG src/vv/Value.cpp -c -oobj/src/vv/Value.o
g++ -Wno-multichar -Iinclude/ -DVV_DEBUG obj/src/main.o obj/src/vv/system.o obj/src/vv/logging.o obj/src/vv/Types.o obj/src/vv/Value.o -o main.exe

GCC details

Cygwin on Windows 7 64Bit

g++ (GCC) 4.8.2
Copyright (C) 2013 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

So far, this seems to make the program run. But do you have any idea where this problem could come from? For my part, I can't spot the problem in typestr(). Maybe there is some pattern that you as an experienced developer can recognise and tell me about?

¿Fue útil?

Solución

Your anti-virus software's detection heuristics may be falsely recognizing your program as a virus only under very special, seemingly arbitrary circumstances. Try temporarily disabling the real-time system protection.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top