5

I try to run/compile OpenTibia Server on Linux64. Little tweaks, compiled and everything seemed fine. Yet, Valgrind says:

==32360== Invalid free() / delete / delete[] / realloc()
==32360==    at 0x4C2BDEC: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==32360==    by 0x6074AE4: fclose@@GLIBC_2.2.5 (iofclose.c:85)
==32360==    by 0x41CF8D: FileLoader::~FileLoader() (fileloader.cpp:49)
==32360==    by 0x45DB1B: Items::loadFromOtb(std::string) (itemloader.h:232)
==32360==    by 0x4067D7: main (otserv.cpp:564)
==32360==  Address 0x8126590 is 0 bytes inside a block of size 568 free'd
==32360==    at 0x4C2BDEC: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==32360==    by 0x6074AE4: fclose@@GLIBC_2.2.5 (iofclose.c:85)
==32360==    by 0x41D268: FileLoader::openFile(char const*, bool, bool) (fileloader.cpp:92)
==32360==    by 0x45DB00: Items::loadFromOtb(std::string) (items.cpp:230)
==32360==    by 0x4067D7: main (otserv.cpp:564)

Now the code goes, for FileLoader (especially destructor):

/*somewhere in the header*/
FILE* m_file;

FileLoader::FileLoader() {
    m_file = NULL;
    m_buffer = new unsigned char[1024];
    //cache, some cache data
    memset(m_cached_data, 0, sizeof(m_cached_data));
}

FileLoader::~FileLoader() {
    if(m_file){
        fclose(m_file);
        m_file = NULL;
     }

    delete[] m_buffer;

    for(int i = 0; i < CACHE_BLOCKS; i++){
        if(m_cached_data[i].data)
            delete m_cached_data[i].data;
        }
}

bool FileLoader::openFile(const char* filename, bool write, bool caching /*= false*/){
    if(write) {/*unimportant*/}
    else {
    unsigned long version;
    m_file = fopen(filename, "rb");
    if(m_file){
        fread(&version, sizeof(unsigned long), 1, m_file);
        if(version > 0){/*version is 0*/}
            else{
                if(caching){
                    m_use_cache = true;
                    fseek(m_file, 0, SEEK_END);
                    int file_size = ftell(m_file);
                    m_cache_size = min(32768, max(file_size/20, 8192)) & ~0x1FFF;
                }
                return true;
            }
        }
        else{
            m_lastError = ERROR_CAN_NOT_OPEN;
            return false;
        }
    }
}

ItemLoader is just an extension to FileLoader:

class ItemLoader : public FileLoader {/*Overrides nothing*/};

Now to the function in Items:

int Items::loadFromOtb(std::string file) {
    ItemLoader f;
    if(!f.openFile(file.c_str(), false, true)){return f.getError();}

    //...Loading, processing, reading from file and stuff...

    //delete &f; //I tried this but didn't change anything
    return ERROR_NONE;
}

The question is, does Valgrind point to problem with fclose or rather something else? Also note that the application uses libboost (if this has anything to do). I tried to be as specific as possible

David Schwartz
  • 179,497
  • 17
  • 214
  • 278
pfoof
  • 195
  • 1
  • 13
  • Is `FileLoader` derived from any other class? How does `m_file` get set? We don't see enough code to figure out the problem. – David Schwartz Sep 12 '15 at 17:23
  • 2
    Are you following the rule of three/five? The base class should also have a virtual destructor. – chris Sep 12 '15 at 17:24
  • Just use `std::vector` for a buffer and let it deal with new/delete. – MSalters Sep 12 '15 at 17:29
  • okay, I added the code, `m_file` is created like normal file, it certainly is because otherwise errors are returned – pfoof Sep 12 '15 at 17:30
  • @MSalters so basically, valgrind is lying about fclose? – pfoof Sep 12 '15 at 17:31
  • @pfoof: There is no sensible link between my comment and your response ?! – MSalters Sep 12 '15 at 17:37
  • `m_file` is a global variable, why isn't it an attribute of the class? If many `FileLoader` objects are created they will concurrently use `m_file`. Moreover, is `FileLoader` destructor `virtual`? If not, it will not be called when `ItemLoader` object is destroyed. Finaly as asked by chris, did you follow the rule of three? – jpo38 Sep 12 '15 at 17:40
  • 2
    Bad question. Lot of information given, but still missing the pertinent information. Is m_file a member of FileLoader? Probably, but that's guessing. Is `f` copied in `//...Loading, processing, reading from file and stuff...`? Probably, but that's guessing. Quick check: Do you ever call any functions or methods with `f` as a parameter? If so, do you pass by reference or by value? If value, you've made a copy and the copy's destructor will close and free `m_file`. Game over. – user4581301 Sep 12 '15 at 17:45
  • Why `new` this: `m_buffer = new unsigned char[1024];`? You know the size and It's constant. Go with a static allocation by defining `m_buffer` as `unsigned char m_buffer[1024];` and watch the memory troubles you'll get from this nasty go away. – user4581301 Sep 12 '15 at 17:48
  • This looks to be C code camouflaging as C++. Avoid manual resource management, use RAII. You are juggling with razor blades for no reason. You do not need to do manual caching, C library does that for you. You need to check for errors. – Maxim Egorushkin Sep 12 '15 at 19:34

4 Answers4

4

Vagrind shows you the problem directly -- you're calling fclose on the same FILE descriptor twice:

            ==32360== Invalid free() / delete / delete[] / realloc()
            ==32360==    at 0x4C2BDEC: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
second call ==32360==    by 0x6074AE4: fclose@@GLIBC_2.2.5 (iofclose.c:85)
--------->> ==32360==    by 0x41CF8D: FileLoader::~FileLoader() (fileloader.cpp:49)
            ==32360==    by 0x45DB1B: Items::loadFromOtb(std::string) (itemloader.h:232)
            ==32360==    by 0x4067D7: main (otserv.cpp:564)
            ==32360==  Address 0x8126590 is 0 bytes inside a block of size 568 free'd
            ==32360==    at 0x4C2BDEC: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
first call  ==32360==    by 0x6074AE4: fclose@@GLIBC_2.2.5 (iofclose.c:85)
--------->> ==32360==    by 0x41D268: FileLoader::openFile(char const*, bool, bool) (fileloader.cpp:92)
            ==32360==    by 0x45DB00: Items::loadFromOtb(std::string) (items.cpp:230)
            ==32360==    by 0x4067D7: main (otserv.cpp:564)

The second call is in your destructor at line 49, the first in openFile at line 92.

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
1

It looks like FileLoader exists to do a form of caching/buffering. There is no need to do your own buffering when using FILE* or IOStreams, they do that for you. FileLoader adds another buffer on top of that.

You may like to refactor FileLoader to drop all buffering and only provide serialization functionality for your classes, while delegating all I/O and associated buffering to FILE* or IOStreams.

Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
1

The elephant in the room: Ahrrr. This be C++. Why use FILE? fstream will take care of this nonsense for you (well... Some of it) without dropping down into the leftovers of C.

Onto the error.

Looks to me like Valgrind says the program is closing m_file twice. Not a good idea.

Why is the program closing the m_file twice? Most likely answer is FileLoader destructor is being called twice.

Here I wander into undefined territory because all I can do is infer information left out of the question from reading between the lines. Can't even close this question and point it to What is The Rule of Three? because we can't be sure. It can be closed for being unclear and unanswerable though, so before it is...

Here are my assumptions:

  1. m_file is a class member of FileLoader. If it isn't and it's global, that's a bad design not worth trying to fix.
  2. somewhere in //...Loading, processing, reading from file and stuff... f is being copied. Please give f a real, descriptive name. The debugging time you save could be your own.
  3. FileLoader and ItemLoader violate the Rule of Three. If you don't know what I'm talking about, read the above link about the Rule of Three. Seriously. Read it. Even if I'm wrong, read it.

How this can happen:

Say you have function void doesStuff(ItemLoader loader) and it gets called in the black hole of //...Loading, processing, reading from file and stuff.... Note the pass by value of loader. This means it's going to be copied and become a temporary variable with its lifetime limited by the scope of the function.

Because FileLoader is not Rule of Three compliant, loader gets a copy of f's m_file and whatever other members FileLoader has.

doesStuff does stuff and returns. loader goes out of scope and is destroyed. ~FileLoader() is called. m_file is not null, the file is closed and m_file is set to null. No point setting it to null, by the way. It's about to vanish.

We return to the calling function where f now has an invalid FILE pointer because the copy just closed the file.

How to test the theory:

Put std::cout << "~FileLoader() called. m_file = " << m_file << std::endl; at the top of FileLoader's destructor to see now many times it gets called compared to the number of times you thought you opened it.

The fix:

Make your objects Rule of Three compliant OR make them uncopyable and always pass by reference.

Making FileLoader Rule of Three compliant is non-trivial effort. FILE pointers and do not copy well, and this leave you playing games with weak pointers to ensure the sucker isn't closed before everyone is done with it.

It also makes a good case for deleting the copy constructor and assignment operator so FileLoader can't be copied. That way the compiler can warn you when you're trying to do something dumb like pass by value when you want to pass by reference.

fstream does not copy at all, preventing you from getting in this mess in the first place. But it does give an arcane stream of error messages if you don't know what a deleted function is.

Here is a blob of test code to show what I think is happening:

#include <iostream>
#include <cstdio>
class FILETest
{
public:
    FILE* filep;
    FILETest(): filep(NULL)
    {
        std::cout << "FILETest constructor" << std::endl;
        
    }
    ~FILETest()
    {
        std::cout << "FILETest destructor" << std::endl;
    }
};

void func(FILETest t)
{
    (void)t;
}
int main(int argc, char** argv)
{
    (void) argc;
    (void) argv;

    FILETest t;

    func(t);
    return 0;
}

And how fstream would have prevented this:

#include <iostream>
#include <fstream>


class fstreamtest
{
public:
    std::fstream file;
    fstreamtest()
    {
        std::cout << "fstreamtest constructor" << std::endl;

    }
    ~fstreamtest()
    {
        std::cout << "fstreamtest destructor" << std::endl;
    }
};

void func(fstreamtest t)
{
    (void)t;
}
int main(int argc, char** argv)
{
    (void) argc;
    (void) argv;

    fstreamtest t;

    func(t);
    return 0;
}
Community
  • 1
  • 1
user4581301
  • 33,082
  • 7
  • 33
  • 54
  • Every time I work with iostream I wonder creating my own stream on top of FILE. Probably I'm not proficient with it and it's a problem with me, but most times I find that it can't do something and I fallback to FILE. i'm not saying it is bad or without use, the solutions I design out are not to be implemented by std streams ^^ XD – CoffeDeveloper Sep 12 '15 at 19:51
  • 1
    Streams are great for a huge number of cases. But not all. For those cases I tend to stick with the stream and use the read and write methods just like I do in C with FILE. – user4581301 Sep 12 '15 at 19:55
0

Okay, disregard the previous. Sorry for misinforming, because I forgot to tell about the metaimportant facts:

  1. Code is 100% working for Win32. I am compiling it on Linux64.
  2. The destructor of FileLoader (is virtual) and fires.
  3. I added the following

    FileLoader::~FileLoader() {
       std::cout << "destructor fired\n";
       if(m_file){
           fclose(m_file);
           m_file = NULL;
           std::cout << "destructor closed file\n";
        }
    
        delete[] m_buffer;
        std::cout << "destructor deleted buffer\n";
    
       for(int i = 0; i < CACHE_BLOCKS; i++){
            if(m_cached_data[i].data)
               delete m_cached_data[i].data;
            }
       std::cout << "destructor deleted cache\n";
    }
    

    And the output was:

    destructor fired
    <valgrind crying>
    destructor closed file
    

    So it didn't have anything to do with buffers. I found the problem, the version actually mismatched, closed file, but didn't NULL it. Thus was actually a bug in original code.

The real solution to the problem was that Win32 long = Linux64 int. That solved part of the issues.

pfoof
  • 195
  • 1
  • 13