When does an istreambuf_iterator throw an exception? I receive an exception when the underlying stream tries to read a directory, but not in other situations. Specifically, I grabbed the script readfile_tests.sh
from Jan-Philip Gehrcke's blog and modified it slightly:
$ cat readfile_tests.sh
#!/bin/bash
COMPILATION_SOURCE=$1
NE_FILE="na"
EMPTY_FILE="empty_file"
ONE_LINE_FILE="one_line_file"
INVALID_LINE_FILE="invalid_line_file"
FILE_READ="file_read"
FILE_WRITTEN="file_written"
FILE_DENIED="/root/.bashrc"
DIR="dir"
# compile test program, resulting in a.out executable
g++ -std=c++11 $COMPILATION_SOURCE
# create test files / directories and put them in the desired state
touch $EMPTY_FILE
if [[ ! -d $DIR ]]; then
mkdir $DIR
fi
echo "rofl" > $ONE_LINE_FILE
echo -ne "validline\ninvalidline" > $INVALID_LINE_FILE
echo "i am opened to read from" > $FILE_READ
python -c 'import time; f = open("'$FILE_READ'"); time.sleep(4)' &
echo "i am opened to write to" > $FILE_WRITTEN
python -c 'import time; f = open("'$FILE_WRITTEN'", "a"); time.sleep(4)' &
# execute test cases
echo "******** testing on non-existent file.."
./a.out $NE_FILE
echo
echo "******** testing on empty file.."
./a.out $EMPTY_FILE
echo
echo "******** testing on valid file with one line content"
./a.out $ONE_LINE_FILE
echo
echo "******** testing on a file with one valid and one invalid line"
./a.out $INVALID_LINE_FILE
echo
echo "******** testing on a file that is read by another process"
./a.out $FILE_READ
echo
echo "******** testing on a file that is written to by another process"
./a.out $FILE_WRITTEN
echo
echo "******** testing on a /root/.bashrc (access should be denied)"
./a.out $FILE_DENIED
echo
echo "******** testing on a directory"
./a.out $DIR
Then, I ran the tests on the following program
$ cat test.cpp
#include <iostream>
#include <vector>
#include <iterator>
#include <fstream>
#include <string>
int main(int argc, char* argv[]) {
if(argc != 2) {
std::cerr << "Provide one argument" << std::endl;
return EXIT_FAILURE;
}
std::ifstream fin(argv[1]);
std::istreambuf_iterator <char> eos;
std::istreambuf_iterator <char> fin_it(fin.rdbuf());
std::string str;
try {
std::copy(fin_it,eos,std::back_inserter(str));
} catch(...) {
perror("Error");
throw;
}
std::cout << str;
return EXIT_SUCCESS;
}
After running it, I received the following results:
$ ./readfile_tests.sh test.cpp
******** testing on non-existent file..
******** testing on empty file..
******** testing on valid file with one line content
rofl
******** testing on a file with one valid and one invalid line
validline
invalidline
******** testing on a file that is read by another process
i am opened to read from
******** testing on a file that is written to by another process
i am opened to write to
******** testing on a /root/.bashrc (access should be denied)
******** testing on a directory
Error: Is a directory
terminate called after throwing an instance of 'std::ios_base::failure'
what(): basic_filebuf::underflow error reading the file
./readfile_tests.sh: line 51: 22563 Aborted ./a.out $DIR
These results are strange to me because an exception was thrown on the reading the directory test, but an exception was not thrown when trying to read /root/.bashrc
. As such, when does istreambuf_iterator thrown an exception? In case it matters, I use gcc version 4.7.3
.
Edit 1
Yes, opening a directory for reading is nonstandard and bad. At the same time, I need to trap this case correctly. Per the comments below, here is a piece of code that tries to read from a directory:
#include <iostream>
#include <vector>
#include <iterator>
#include <fstream>
#include <string>
void withIterators() {
std::ifstream fin("mydirectory");
if(!fin.is_open())
perror("Error opening file");
std::istreambuf_iterator <char> eos;
std::istreambuf_iterator <char> fin_it(fin.rdbuf());
std::string str;
try {
std::copy(fin_it,eos,std::back_inserter(str));
} catch(...) {
perror("Error reading file");
}
}
void noIterators() {
std::ifstream fin("mydirectory");
if(!fin.is_open())
perror("Error opening file");
std::string line;
while(getline(fin,line)) {}
if(fin.bad())
perror("Error reading file");
}
int main() {
withIterators();
noIterators();
}
After running, we receive the output:
Error reading file: Is a directory
Error reading file: Is a directory
This tells us two things. First, we can't catch the fact that we're opening a directory as a file after the constructor of fin
. Second, the exception is thrown only when using iterators. The code noIterators
above does not throw an exception when reading from a directory. Rather it sets the badbit. Why would the code throw an exception in one situation and set the badbit in the other?
Edit 2
I expanded the directory opening code from above in order to better track down what's going on
#include <iostream>
#include <vector>
#include <iterator>
#include <fstream>
#include <string>
void withIterators() {
std::ifstream fin("mydirectory");
if(!fin.is_open())
perror("Error opening file");
std::istreambuf_iterator <char> eos;
std::istreambuf_iterator <char> fin_it(fin.rdbuf());
std::string str;
try {
std::copy(fin_it,eos,std::back_inserter(str));
} catch(...) {
perror("Error reading file");
}
}
void withIteratorsUnderflow() {
std::ifstream fin("mydirectory");
try {
fin.rdbuf()->sgetc();
} catch(...) {
perror("Error reading file");
}
}
void withOtherIterators() {
std::ifstream fin("mydirectory");
if(!fin.is_open())
perror("Error opening file");
std::istream_iterator <char> eos;
try {
std::istream_iterator <char> fin_it(fin);
} catch(...) {
perror("Error making iterator");
}
}
void noIterators() {
std::ifstream fin("mydirectory");
if(!fin.is_open())
perror("Error opening file");
std::string line;
while(getline(fin,line)) {}
if(fin.bad())
perror("Error reading file");
}
int main() {
withIterators();
withIteratorsUnderflow();
withOtherIterators();
noIterators();
}
Basically, we get exceptions, or not, in different places, but for similar reasons.
On withIterators()
, the call to copy
eventually calls the code /usr/lib/gcc/i686-pc-linux-gnu/4.7.3/include/g++-v4/bits/streambuf_iterator.h:187
, which states
else if (!traits_type::eq_int_type((__ret = _M_sbuf->sgetc())
The call to sgetc()
throws the exception. Likely, this is calling underflow as @user657267 suggested.
On withIteratorsUnderflow()
, we see the exception get thrown directly by sgetc
, which confirms what happens with withIterators()
.
On withOtherIterators()
, we have the exception thrown as soon as we create the std::istream_iterator
. This differs from what happens with std::istreambuf_iterator
, which didn't throw the exception until later. In any case, during construction, we call the code /usr/lib/gcc/i686-pc-linux-gnu/4.7.3/include/g++-v4/bits/stream_iterator.h:121
, which states
*_M_stream >> _M_value;
Basically, the class istream_iterator
creates an istream
on construction that immediately tries to read a character, which throws the exception.
On noIterators()
, getline
just sets the badbit and does not throw the exception.
The bottom line is that trying to read from a directory is bad. Is there a good stl (not boost) way to detect this? Are there other situations that will throw an implementation specific exception that will need to be trapped?