diff --git a/Makefile b/Makefile index db1ca25..ba2435d 100644 --- a/Makefile +++ b/Makefile @@ -4,11 +4,13 @@ CXXFLAGS = -Wall -Wextra -pedantic -Wcast-qual -Wfloat-equal -Wmissing-declarati LDFLAGS = -g %.o: %.cpp simplecpp.h - $(CXX) $(CXXFLAGS) -c $< + $(CXX) $(CXXFLAGS) -c $< $(LIB_FUZZING_ENGINE) +fuzz_no.o: fuzz.cpp + $(CXX) $(CXXFLAGS) -DNO_FUZZ -c -o $@ fuzz.cpp testrunner: test.o simplecpp.o - $(CXX) $(LDFLAGS) simplecpp.o test.o -o testrunner + $(CXX) $(LDFLAGS) -o $@ $^ test: testrunner simplecpp # The -std=c++03 makes sure that simplecpp.cpp is C++03 conformant. We don't require a C++11 compiler @@ -16,11 +18,19 @@ test: testrunner simplecpp ./testrunner python3 run-tests.py +fuzz: fuzz.o simplecpp.o + # TODO: use -stdlib=libc++ -lc++ + # make fuzz CXX=clang++ CXXFLAGS="-O2 -fno-omit-frame-pointer -g -gline-tables-only -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION -fsanitize=address,undefined -fsanitize-address-use-after-scope -fno-sanitize=integer -fno-sanitize-recover=undefined" LIB_FUZZING_ENGINE="-fsanitize=fuzzer" + $(CXX) $(LDFLAGS) $(CXXFLAGS) -o $@ $^ $(LIB_FUZZING_ENGINE) + +no-fuzz: fuzz_no.o simplecpp.o + $(CXX) $(LDFLAGS) $(CXXFLAGS) -o $@ $^ + selfcheck: simplecpp ./selfcheck.sh simplecpp: main.o simplecpp.o - $(CXX) $(LDFLAGS) main.o simplecpp.o -o simplecpp + $(CXX) $(LDFLAGS) -o $@ $^ clean: - rm -f testrunner simplecpp *.o + rm -f testrunner fuzz no-fuzz simplecpp *.o diff --git a/fuzz.cpp b/fuzz.cpp new file mode 100644 index 0000000..151c8a9 --- /dev/null +++ b/fuzz.cpp @@ -0,0 +1,67 @@ +/* + * simplecpp - A simple and high-fidelity C/C++ preprocessor library + * Copyright (C) 2016-2024 simplecpp team + */ + +#include "simplecpp.h" + +#include + +#ifdef NO_FUZZ +#include +#include +#include +#include +#endif + +static void doProcess(const uint8_t *data, size_t dataSize) +{ + simplecpp::OutputList outputList; + std::vector files; + simplecpp::TokenList rawtokens(data, dataSize, files, "test.cpp", &outputList); + rawtokens.removeComments(); + + simplecpp::TokenList outputTokens(files); + std::map filedata; + simplecpp::DUI dui; + dui.removeComments = true; + std::list macroUsage; + std::list ifCond; + simplecpp::preprocess(outputTokens, rawtokens, files, filedata, dui, &outputList, ¯oUsage, &ifCond); + + simplecpp::cleanup(filedata); +} + +#ifndef NO_FUZZ +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t dataSize); + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t dataSize) +{ + doProcess(data, dataSize); + return 0; +} +#else +int main(int argc, char * argv[]) +{ + if (argc < 2 || argc > 3) + return EXIT_FAILURE; + + std::ifstream f(argv[1]); + if (!f.is_open()) + return EXIT_FAILURE; + + std::ostringstream oss; + oss << f.rdbuf(); + + if (!f.good()) + return EXIT_FAILURE; + + const int cnt = (argc == 3) ? std::stoi(argv[2]) : 1; + + const std::string code = oss.str(); + for (int i = 0; i < cnt; ++i) + doProcess(reinterpret_cast(code.data()), code.size()); + + return EXIT_SUCCESS; +} +#endif diff --git a/main.cpp b/main.cpp index 3f02773..8173e99 100644 --- a/main.cpp +++ b/main.cpp @@ -121,6 +121,10 @@ int main(int argc, char **argv) std::cout << "error: could not open file '" << filename << "'" << std::endl; std::exit(1); } + if (!simplecpp::isFile(filename)) { + std::cout << "error: could not open file '" << filename << "' - not a regular file" << std::endl; + std::exit(1); + } rawtokens = new simplecpp::TokenList(f, files,filename,&outputList); } else { diff --git a/simplecpp.cpp b/simplecpp.cpp index 9c3f3a0..66859d3 100755 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #if __cplusplus >= 201103L #ifdef SIMPLECPP_WINDOWS #include @@ -39,6 +40,12 @@ #include #include +#ifdef _WIN32 +using mode_t = unsigned short; +#else +#include +#endif + #ifdef SIMPLECPP_WINDOWS #include #undef ERROR @@ -3077,9 +3084,11 @@ static std::string openHeader(std::ifstream &f, const std::string &path) if (nonExistingFilesCache.contains(simplePath)) return ""; // file is known not to exist, skip expensive file open call #endif - f.open(simplePath.c_str()); - if (f.is_open()) - return simplePath; + if (simplecpp::isFile(simplePath)) { + f.open(simplePath.c_str()); + if (f.is_open()) + return simplePath; + } #ifdef SIMPLECPP_WINDOWS nonExistingFilesCache.add(simplePath); #endif @@ -3184,18 +3193,19 @@ std::map simplecpp::load(const simplecpp::To if (ret.find(filename) != ret.end()) continue; - std::ifstream fin(filename.c_str()); - if (!fin.is_open()) { - if (outputList) { - simplecpp::Output err(filenames); - err.type = simplecpp::Output::EXPLICIT_INCLUDE_NOT_FOUND; - err.location = Location(filenames); - err.msg = "Can not open include file '" + filename + "' that is explicitly included."; - outputList->push_back(err); + { + std::ifstream fin(filename.c_str()); + if (!fin.is_open() || !isFile(filename)) { + if (outputList) { + simplecpp::Output err(filenames); + err.type = simplecpp::Output::EXPLICIT_INCLUDE_NOT_FOUND; + err.location = Location(filenames); + err.msg = "Can not open include file '" + filename + "' that is explicitly included."; + outputList->push_back(err); + } + continue; } - continue; } - fin.close(); TokenList *tokenlist = new TokenList(filename, filenames, outputList); if (!tokenlist->front()) { @@ -3835,6 +3845,24 @@ std::string simplecpp::getCppStdString(const std::string &std) return ""; } +static mode_t file_type(const std::string &path) +{ + struct stat file_stat; + if (stat(path.c_str(), &file_stat) == -1) + return 0; + return file_stat.st_mode & S_IFMT; +} + +bool simplecpp::isFile(const std::string &path) +{ + return file_type(path) == S_IFREG; +} + +bool simplecpp::isDirectory(const std::string &path) +{ + return file_type(path) == S_IFDIR; +} + #if (__cplusplus < 201103L) && !defined(__APPLE__) #undef nullptr #endif diff --git a/simplecpp.h b/simplecpp.h index 398024d..c2938be 100755 --- a/simplecpp.h +++ b/simplecpp.h @@ -373,6 +373,20 @@ namespace simplecpp { /** Returns the __cplusplus value for a given standard */ SIMPLECPP_LIB std::string getCppStdString(const std::string &std); + + /** + * @brief Checks if given path is a file + * @param path Path to be checked + * @return true if given path is a file + */ + SIMPLECPP_LIB bool isFile(const std::string &path); + + /** + * @brief Checks if a given path is a directory + * @param path Path to be checked + * @return true if given path is a directory + */ + SIMPLECPP_LIB bool isDirectory(const std::string &path); } #if defined(_MSC_VER) diff --git a/test.cpp b/test.cpp index d00658a..05f1668 100644 --- a/test.cpp +++ b/test.cpp @@ -1779,6 +1779,44 @@ static void missingHeader3() ASSERT_EQUALS("", toString(outputList)); } +#ifndef _WIN32 +static void missingHeader4() +{ + // this is a directory + const char code[] = "#include \"/\"\n"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("", preprocess(code, &outputList)); + ASSERT_EQUALS("file0,1,missing_header,Header not found: \"/\"\n", toString(outputList)); +} + +static void missingHeader5() +{ + // this is a directory + const char code[] = "#include \"/usr\"\n"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("", preprocess(code, &outputList)); + ASSERT_EQUALS("file0,1,missing_header,Header not found: \"/usr\"\n", toString(outputList)); +} + +static void missingHeader6() +{ + // this is a directory + const char code[] = "#include \n"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("", preprocess(code, &outputList)); + ASSERT_EQUALS("file0,1,missing_header,Header not found: \n", toString(outputList)); +} + +static void missingHeader7() +{ + // this is a directory + const char code[] = "#include \n"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("", preprocess(code, &outputList)); + ASSERT_EQUALS("file0,1,missing_header,Header not found: \n", toString(outputList)); +} +#endif + static void nestedInclude() { const char code[] = "#include \"test.h\"\n"; @@ -2959,6 +2997,12 @@ int main(int argc, char **argv) TEST_CASE(missingHeader1); TEST_CASE(missingHeader2); TEST_CASE(missingHeader3); +#ifndef _WIN32 + TEST_CASE(missingHeader4); + TEST_CASE(missingHeader5); + TEST_CASE(missingHeader6); + TEST_CASE(missingHeader7); +#endif TEST_CASE(nestedInclude); TEST_CASE(systemInclude);