I recently had a very strange crash and after some digging I found the lines I suspected the bug to lurk around. They looked something like this:
const std::string contents = readFile("myFile.txt"); const std::vector<std::string> lines = utils::split(contents, "\n"); for (std::string line : lines) { if (line.empty()) { continue; } //... // Do something elaborate with the line, e.g. printing to console std::cout << "<line>" << line.c_str() << "</line>" << std::endl; }
The crash occurred in the //...
lines because the line was not empty. Wait – what? I tested for emptiness before!
Opening the debugger revealed the following strange situation:
and the above small sample file prints on my (Windows) console:
<line>First line</line> <line>third line (second one is empty)</line> <line>fourth line</line> <line></line>
Scrolling trough the commit history, the problem turned out to be introduced with this change:
OLD CODE (working):
std::string readFile(const std::string& fname, bool binaryMode = false) { std::ios_base::openmode mode = std::ios_base::in; if (binaryMode) mode |= std::ios_base::binary; std::ifstream ifs(fname, mode); if (!ifs) { throw std::exception(("Couldn't open file: " + fname).c_str()); } return std::string(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>()); }
NEW CODE (not working):
std::string readFile(const std::string& fname, bool binaryMode = false) { std::ios_base::openmode mode = std::ios_base::in; if (binaryMode) { mode |= std::ios_base::binary; } std::ifstream in(fname, mode); if (!in) { throw std::exception(("Couldn't open file: " + fname).c_str()); } // Used C++ style reading which is more efficient than using stream buffer iterators // http://insanecoding.blogspot.de/2011/11/how-to-read-in-file-in-c.html std::string contents; in.seekg(0, std::ios::end); contents.resize(in.tellg()); in.seekg(0, std::ios::beg); in.read(&contents[0], contents.size()); in.close(); return contents; }
The problem was that reading the file with the more efficient solution resulted in the string having a bunch of null terminators if the file contained a new-line in the end. Obviously, .empty()
returns false, so the check passed. As a side note: to simulate my crash bug by showing an unexpected console output, I had to pipe the C-string. When piping the C++ string, the line is printed with some whitespaces.
This is how I fixed it:
std::string readFile(const std::string& fname, bool binaryMode = false) { std::ios_base::openmode mode = std::ios_base::in; if (binaryMode) { mode |= std::ios_base::binary; } std::ifstream in(fname, mode); if (!in) { throw std::exception(("Couldn't open file: " + fname).c_str()); } // Used C++ style reading which is more efficient than using stream buffer iterators // http://insanecoding.blogspot.de/2011/11/how-to-read-in-file-in-c.html std::string contents; in.seekg(0, std::ios::end); contents.resize(in.tellg()); in.seekg(0, std::ios::beg); in.read(&contents[0], contents.size()); in.close(); if (binaryMode) { return contents; } else { // Depending on the file, the last line might contain one or more \0 control characters. Remove them return contents.erase(contents.find_last_not_of('\0') + 1); } }