diff --git a/.clang-tidy b/.clang-tidy index e0b384bf..184cac3d 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -31,10 +31,15 @@ Checks: > -modernize-avoid-c-arrays, -modernize-loop-convert, -modernize-pass-by-value, + -modernize-use-auto, + -modernize-use-designated-initializers, -modernize-use-nodiscard, + -modernize-use-ranges, + -modernize-use-starts-ends-with, -modernize-use-trailing-return-type, -readability-avoid-nested-conditional-operator, -readability-braces-around-statements, + -readability-container-contains, -readability-function-cognitive-complexity, -readability-function-size, -readability-implicit-bool-conversion, diff --git a/.github/workflows/clang-tidy.yml b/.github/workflows/clang-tidy.yml index 333672d7..2819fb9d 100644 --- a/.github/workflows/clang-tidy.yml +++ b/.github/workflows/clang-tidy.yml @@ -40,12 +40,62 @@ jobs: run: | clang-tidy-22 --verify-config - - name: Prepare CMake + - name: Prepare CMake (C++11) run: | - cmake -S . -B cmake.output -Werror=dev --warn-uninitialized -DCMAKE_CXX_STANDARD=23 -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + cmake -S . -B cmake.output.cxx11 -Werror=dev --warn-uninitialized -DCMAKE_CXX_STANDARD=11 -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_EXPORT_COMPILE_COMMANDS=ON env: CXX: clang-22 - - name: Clang-Tidy + - name: Clang-Tidy (C++11) run: | - run-clang-tidy-22 -q -j $(nproc) -enable-check-profile -p=cmake.output + run-clang-tidy-22 -q -j $(nproc) -enable-check-profile -p=cmake.output.cxx11 + + - name: Prepare CMake (C++14) + run: | + cmake -S . -B cmake.output.cxx14 -Werror=dev --warn-uninitialized -DCMAKE_CXX_STANDARD=14 -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + env: + CXX: clang-22 + + - name: Clang-Tidy (C++14) + run: | + run-clang-tidy-22 -q -j $(nproc) -enable-check-profile -p=cmake.output.cxx14 + + - name: Prepare CMake (C++17) + run: | + cmake -S . -B cmake.output.cxx17 -Werror=dev --warn-uninitialized -DCMAKE_CXX_STANDARD=17 -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + env: + CXX: clang-22 + + - name: Clang-Tidy (C++17) + run: | + run-clang-tidy-22 -q -j $(nproc) -enable-check-profile -p=cmake.output.cxx17 + + - name: Prepare CMake (C++20) + run: | + cmake -S . -B cmake.output.cxx20 -Werror=dev --warn-uninitialized -DCMAKE_CXX_STANDARD=20 -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + env: + CXX: clang-22 + + - name: Clang-Tidy (C++20) + run: | + run-clang-tidy-22 -q -j $(nproc) -enable-check-profile -p=cmake.output.cxx20 + + - name: Prepare CMake (C++23) + run: | + cmake -S . -B cmake.output.cxx23 -Werror=dev --warn-uninitialized -DCMAKE_CXX_STANDARD=23 -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + env: + CXX: clang-22 + + - name: Clang-Tidy (C++23) + run: | + run-clang-tidy-22 -q -j $(nproc) -enable-check-profile -p=cmake.output.cxx23 + + - name: Prepare CMake (C++26) + run: | + cmake -S . -B cmake.output.cxx26 -Werror=dev --warn-uninitialized -DCMAKE_CXX_STANDARD=26 -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + env: + CXX: clang-22 + + - name: Clang-Tidy (C++26) + run: | + run-clang-tidy-22 -q -j $(nproc) -enable-check-profile -p=cmake.output.cxx26 diff --git a/.github/workflows/gcc-versions.yml b/.github/workflows/gcc-versions.yml new file mode 100644 index 00000000..9c365a24 --- /dev/null +++ b/.github/workflows/gcc-versions.yml @@ -0,0 +1,30 @@ +# the purpose of this github action is to test that simplecpp source code can be compiled using old gcc versions. +# the gcc images we use here are copied from the official gcc images on docker hub + +name: gcc-versions + +on: [push, pull_request] + +permissions: + contents: read + +jobs: + build: + + strategy: + matrix: + #image: ["ghcr.io/cppcheck-opensource/gcc:5.4", "ghcr.io/cppcheck-opensource/gcc:6.5", "ghcr.io/cppcheck-opensource/gcc:7.5", "ghcr.io/cppcheck-opensource/gcc:8.5", "ghcr.io/cppcheck-opensource/gcc:9.5"] + image: ["ghcr.io/cppcheck-opensource/gcc:5.4"] + fail-fast: false + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Build and run tests + run: | + docker run --rm -v ${{ github.workspace }}:/simplecpp -w /simplecpp ${{ matrix.image }} make -j$(nproc) simplecpp testrunner + docker run --rm -v ${{ github.workspace }}:/simplecpp -w /simplecpp ${{ matrix.image }} ./testrunner diff --git a/CMakeLists.txt b/CMakeLists.txt index 0a90efae..f2c99e89 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,13 @@ cmake_minimum_required (VERSION 3.10) project (simplecpp LANGUAGES CXX) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 11 CACHE STRING "C++ standard to use") set(CMAKE_CXX_STANDARD_REQUIRED ON) +if(CMAKE_CXX_STANDARD LESS 11) + message(FATAL_ERROR "C++ standard was set to ${CMAKE_CXX_STANDARD} but 11 is required as a minimum") +endif() + include(CheckCXXCompilerFlag) if (WIN32) diff --git a/integration_test.py b/integration_test.py index c000b27b..42dda9b2 100644 --- a/integration_test.py +++ b/integration_test.py @@ -17,7 +17,7 @@ def __test_relative_header_create_header(dir, with_pragma_once=True): #error header_was_already_included #endif const int dummy = 1; - """) + """'\n') return header_file, "error: #error header_was_already_included" def __test_relative_header_create_source(dir, include1, include2, is_include1_sys=False, is_include2_sys=False, inv=False): @@ -31,7 +31,7 @@ def __test_relative_header_create_source(dir, include1, include2, is_include1_sy #undef TEST_H_INCLUDED #include {format_include(include1, is_include1_sys)} #include {format_include(include2, is_include2_sys)} - """) + """'\n') return src_file @pytest.mark.parametrize("with_pragma_once", (False, True)) @@ -201,27 +201,27 @@ def test_same_name_header(record_property, tmpdir): #include #include TEST - """) + """'\n') with open(header_a, "wt") as f: f.write(""" #include "same_name.h" - """) + """'\n') with open(header_b, "wt") as f: f.write(""" #include "same_name.h" - """) + """'\n') with open(same_name_a, "wt") as f: f.write(""" #define TEST E - """) + """'\n') with open(same_name_b, "wt") as f: f.write(""" #define TEST OK - """) + """'\n') args = [ format_include_path_arg(include_a), @@ -279,13 +279,13 @@ def test_pragma_once_matching(record_property, tmpdir): for n in names_to_test: f.write(f""" #include {n} - """); + """'\n'); with open(once_header, "wt") as f: f.write(f""" #pragma once ONCE - """); + """'\n'); args = [ format_include_path_arg(test_dir), @@ -463,7 +463,7 @@ def test_include_header_twice(tmpdir): #ifdef BBB # error BBB is defined #endif - """) + """'\n') test_file = os.path.join(tmpdir, 'test.c') with open(test_file, 'wt') as f: @@ -473,7 +473,7 @@ def test_include_header_twice(tmpdir): # define BBB # include "test.h" - """) + """'\n') args = [test_file] @@ -507,7 +507,7 @@ def test_define(record_property, tmpdir): # #589 def test_utf16_bom(tmpdir): test_file = os.path.join(tmpdir, "test.cpp") with open(test_file, 'wb') as f: - f.write(b'\xFF\xFE\x3B\x00') + f.write(b'\xFF\xFE\x3B\x00\x0A\x00') args = [test_file] diff --git a/main.cpp b/main.cpp index 685ada12..92ca27dc 100644 --- a/main.cpp +++ b/main.cpp @@ -224,7 +224,7 @@ int main(int argc, char **argv) { simplecpp::TokenList *rawtokens; if (toklist_inf == Fstream) { - rawtokens = new simplecpp::TokenList(f,files,filename,&outputList); + rawtokens = new simplecpp::TokenList(f,files,filename,dui,&outputList); } else if (toklist_inf == Sstream || toklist_inf == CharBuffer) { std::ostringstream oss; @@ -233,16 +233,17 @@ int main(int argc, char **argv) const std::string s = oss.str(); if (toklist_inf == Sstream) { std::istringstream iss(s); - rawtokens = new simplecpp::TokenList(iss,files,filename,&outputList); + rawtokens = new simplecpp::TokenList(iss,files,filename,dui,&outputList); } else { - rawtokens = new simplecpp::TokenList({s.data(),s.size()},files,filename,&outputList); + rawtokens = new simplecpp::TokenList({s.data(),s.size()},files,filename,dui,&outputList); } } else { f.close(); - rawtokens = new simplecpp::TokenList(filename,files,&outputList); + rawtokens = new simplecpp::TokenList(filename,files,dui,&outputList); } - rawtokens->removeComments(); + if (dui.removeComments) + rawtokens->removeComments(); simplecpp::FileDataCache filedata; simplecpp::preprocess(outputTokens, *rawtokens, files, filedata, dui, &outputList); simplecpp::cleanup(filedata); @@ -272,7 +273,12 @@ int main(int argc, char **argv) case simplecpp::Output::SYNTAX_ERROR: std::cerr << "syntax error: "; break; + case simplecpp::Output::DIRECTIVE_AS_MACRO_PARAMETER: + std::cerr << "directive as macro parameter: "; + break; case simplecpp::Output::PORTABILITY_BACKSLASH: + case simplecpp::Output::PORTABILITY_LINE_DIRECTIVE: + case simplecpp::Output::PORTABILITY_NO_EOF_NEWLINE: std::cerr << "portability: "; break; case simplecpp::Output::UNHANDLED_CHAR_ERROR: diff --git a/simplecpp.cpp b/simplecpp.cpp index d2c398c4..93853617 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -96,6 +96,8 @@ static const simplecpp::TokenString IFNDEF("ifndef"); static const simplecpp::TokenString DEFINED("defined"); static const simplecpp::TokenString ELSE("else"); static const simplecpp::TokenString ELIF("elif"); +static const simplecpp::TokenString ELIFDEF("elifdef"); +static const simplecpp::TokenString ELIFNDEF("elifndef"); static const simplecpp::TokenString ENDIF("endif"); static const simplecpp::TokenString PRAGMA("pragma"); @@ -475,26 +477,26 @@ namespace { simplecpp::TokenList::TokenList(std::vector &filenames) : frontToken(nullptr), backToken(nullptr), files(filenames) {} -simplecpp::TokenList::TokenList(std::istream &istr, std::vector &filenames, const std::string &filename, OutputList *outputList) +simplecpp::TokenList::TokenList(std::istream &istr, std::vector &filenames, const std::string &filename, const DUI &dui, OutputList *outputList) : frontToken(nullptr), backToken(nullptr), files(filenames) { StdIStream stream(istr); - readfile(stream,filename,outputList); + readfile(stream,filename,dui,outputList); } -simplecpp::TokenList::TokenList(const unsigned char* data, std::size_t size, std::vector &filenames, const std::string &filename, OutputList *outputList, int /*unused*/) +simplecpp::TokenList::TokenList(const unsigned char* data, std::size_t size, std::vector &filenames, const std::string &filename, const DUI &dui, OutputList *outputList, int /*unused*/) : frontToken(nullptr), backToken(nullptr), files(filenames) { StdCharBufStream stream(data, size); - readfile(stream,filename,outputList); + readfile(stream,filename,dui,outputList); } -simplecpp::TokenList::TokenList(const std::string &filename, std::vector &filenames, OutputList *outputList) +simplecpp::TokenList::TokenList(const std::string &filename, std::vector &filenames, const DUI &dui, OutputList *outputList) : frontToken(nullptr), backToken(nullptr), files(filenames) { try { FileStream stream(filename, filenames); - readfile(stream,filename,outputList); + readfile(stream,filename,dui,outputList); } catch (const simplecpp::Output & e) { outputList->emplace_back(e); } @@ -659,18 +661,24 @@ void simplecpp::TokenList::lineDirective(unsigned int fileIndex_, unsigned int l static const std::string COMMENT_END("*/"); -void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, OutputList *outputList) +void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, const DUI &dui, OutputList *outputList) { unsigned int multiline = 0U; + bool trailing_nl = true; const Token *oldLastToken = nullptr; + const cstd_t cstd = getCStd(dui.std); + const cppstd_t cppstd = getCppStd(dui.std); + Location location(fileIndex(filename), 1, 1); while (stream.good()) { unsigned char ch = stream.readChar(); if (!stream.good()) break; + trailing_nl = false; + if (ch >= 0x80) { if (outputList) { simplecpp::Output err{ @@ -693,6 +701,7 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, } else { location.line += multiline + 1; multiline = 0U; + trailing_nl = true; } if (!multiline) location.col = 1; @@ -723,10 +732,64 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, if (ppTok->str() == "line") ppTok = advanceAndSkipComments(ppTok); + if (ppTok && (ppTok->str()[0] == '-' || ppTok->str()[0] == '+')) { + if (outputList) { + simplecpp::Output err{ + simplecpp::Output::SYNTAX_ERROR, + location, + "Invalid character in line directive: '" + ppTok->str() + "'." + }; + outputList->emplace_back(std::move(err)); + } + clear(); + return; + } + if (!ppTok || !ppTok->number) continue; - const unsigned int line = std::atol(ppTok->str().c_str()); + constexpr unsigned long line_limit = std::numeric_limits::max(); + unsigned long line; + try { + line = std::min(line_limit, std::stoul(ppTok->str())); + } catch (...) { + line = line_limit; + } + + unsigned long maxline; + if ((cstd != CUnknown && cstd < C99) || (cppstd != CPPUnknown && cppstd < CPP11)) + maxline = 32767; + else + maxline = 2147483647; + + if (line == 0 || line > maxline) { + if (outputList) { + const bool unknown_std = cstd == CUnknown && cppstd == CPPUnknown; + std::string msg = "Line number out of range: " + ppTok->str() + ". "; + if (line == 0) { + msg += "Line number zero is undefined behavior."; + } else { + msg += "Line numbers above " + std::to_string(maxline) + " are "; + if (unknown_std) + msg += "undefined behavior or conditionally supported"; + else if (cppstd >= CPP26) + msg += "conditionally supported"; + else + msg += "undefined behavior"; + if (cstd != CUnknown) + msg += std::string(" in ") + getCStdName(cstd); + else if (cppstd != CPPUnknown) + msg += std::string(" in ") + getCppStdName(cppstd); + msg += "."; + } + simplecpp::Output err{ + simplecpp::Output::PORTABILITY_LINE_DIRECTIVE, + location, msg + }; + outputList->emplace_back(std::move(err)); + } + } + ppTok = advanceAndSkipComments(ppTok); unsigned int fileindex; @@ -961,6 +1024,15 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, location.adjust(currentToken); } + if ((cstd != CUnknown || cppstd == CPPUnknown) && !trailing_nl && outputList) { + Output err{ + Output::PORTABILITY_NO_EOF_NEWLINE, + location, + "No newline at end of file is undefined behavior in C." + }; + outputList->emplace_back(std::move(err)); + } + combineOperators(); } @@ -1602,7 +1674,7 @@ namespace simplecpp { else if (rawtok->op == ')') --par; else if (rawtok->op == '#' && !sameline(rawtok->previous, rawtok)) - throw Error(rawtok->location, "it is invalid to use a preprocessor directive as macro parameter"); + throw invalidDirectiveAsMacroParameter(rawtok->location); rawtokens2.push_back(new Token(rawtok->str(), rawtok1->location, rawtok->whitespaceahead)); rawtok = rawtok->next; } @@ -1698,6 +1770,11 @@ namespace simplecpp { const std::string what; }; + struct invalidDirectiveAsMacroParameter : public Error { + invalidDirectiveAsMacroParameter(const Location &loc) + : Error(loc, "it is invalid to use a preprocessor directive as macro parameter") {} + }; + /** Struct that is thrown when macro is expanded with wrong number of parameters */ struct wrongNumberOfParameters : public Error { wrongNumberOfParameters(const Location &loc, const std::string ¯oName) : Error(loc, "Wrong number of parameters for macro \'" + macroName + "\'.") {} @@ -3126,8 +3203,8 @@ simplecpp::FileDataCache::FileDataCache() {} simplecpp::FileDataCache::~FileDataCache() = default; -simplecpp::FileDataCache::FileDataCache(FileDataCache &&) noexcept = default; -simplecpp::FileDataCache &simplecpp::FileDataCache::operator=(simplecpp::FileDataCache &&) noexcept = default; +simplecpp::FileDataCache::FileDataCache(FileDataCache &&) SIMPLECPP_NOEXCEPT = default; +simplecpp::FileDataCache &simplecpp::FileDataCache::operator=(simplecpp::FileDataCache &&) SIMPLECPP_NOEXCEPT = default; static bool getFileId(const std::string &path, FileID &id); @@ -3145,7 +3222,7 @@ std::pair simplecpp::FileDataCache::tryload(FileDat return {id_it->second, false}; } - auto *const data = new FileData {path, TokenList(path, filenames, outputList)}; + auto *const data = new FileData {path, TokenList(path, filenames, {}, outputList)}; if (dui.removeComments) data->tokens.removeComments(); @@ -3336,7 +3413,7 @@ simplecpp::FileDataCache simplecpp::load(const simplecpp::TokenList &rawtokens, return cache; } -static bool preprocessToken(simplecpp::TokenList &output, const simplecpp::Token *&tok1, simplecpp::MacroMap ¯os, std::vector &files, simplecpp::OutputList *outputList) +static bool preprocessToken(simplecpp::TokenList &output, const simplecpp::Token *&tok1, simplecpp::MacroMap ¯os, std::vector &files, simplecpp::OutputList *outputList, const simplecpp::DUI &dui) { const simplecpp::Token * const tok = tok1; const simplecpp::MacroMap::const_iterator it = tok->name ? macros.find(tok->str()) : macros.end(); @@ -3344,6 +3421,16 @@ static bool preprocessToken(simplecpp::TokenList &output, const simplecpp::Token simplecpp::TokenList value(files); try { tok1 = it->second.expand(value, tok, macros, files); + } catch (const simplecpp::Macro::invalidDirectiveAsMacroParameter &err) { + if (outputList) { + simplecpp::Output out{ + simplecpp::Output::DIRECTIVE_AS_MACRO_PARAMETER, + err.location, + "failed to expand \'" + tok->str() + "\', " + err.what + }; + outputList->emplace_back(std::move(out)); + } + return false; } catch (const simplecpp::Macro::Error &err) { if (outputList) { simplecpp::Output out{ @@ -3357,7 +3444,7 @@ static bool preprocessToken(simplecpp::TokenList &output, const simplecpp::Token } output.takeTokens(value); } else { - if (!tok->comment) + if (!tok->comment || !dui.removeComments) output.push_back(new simplecpp::Token(*tok)); tok1 = tok->next; } @@ -3552,7 +3639,9 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL continue; } - if (ifstates.size() <= 1U && (rawtok->str() == ELIF || rawtok->str() == ELSE || rawtok->str() == ENDIF)) { + if (ifstates.size() <= 1U && (rawtok->str() == ELIF || rawtok->str() == ELIFDEF || + rawtok->str() == ELIFNDEF || rawtok->str() == ELSE || + rawtok->str() == ENDIF)) { if (outputList) { simplecpp::Output err{ Output::SYNTAX_ERROR, @@ -3632,7 +3721,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL TokenList inc2(files); if (!inc1.empty() && inc1.cfront()->name) { const Token *inctok = inc1.cfront(); - if (!preprocessToken(inc2, inctok, macros, files, outputList)) { + if (!preprocessToken(inc2, inctok, macros, files, outputList, dui)) { output.clear(); return; } @@ -3693,7 +3782,8 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL rawtok = filedata->tokens.cfront(); continue; } - } else if (rawtok->str() == IF || rawtok->str() == IFDEF || rawtok->str() == IFNDEF || rawtok->str() == ELIF) { + } else if (rawtok->str() == IF || rawtok->str() == IFDEF || rawtok->str() == IFNDEF || rawtok->str() == ELIF || + rawtok->str() == ELIFDEF || rawtok->str() == ELIFNDEF) { if (!sameline(rawtok,rawtok->next)) { if (outputList) { simplecpp::Output out{ @@ -3708,15 +3798,28 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL } bool conditionIsTrue; - if (ifstates.top() == AlwaysFalse || (ifstates.top() == ElseIsTrue && rawtok->str() != ELIF)) { + if (ifstates.top() == AlwaysFalse || (ifstates.top() == ElseIsTrue && rawtok->str() != ELIF && + rawtok->str() != ELIFDEF && rawtok->str() != ELIFNDEF)) { conditionIsTrue = false; } - else if (rawtok->str() == IFDEF) { - conditionIsTrue = (macros.find(rawtok->next->str()) != macros.end() || (hasInclude && rawtok->next->str() == HAS_INCLUDE)); - maybeUsedMacros[rawtok->next->str()].emplace_back(rawtok->next->location); - } else if (rawtok->str() == IFNDEF) { - conditionIsTrue = (macros.find(rawtok->next->str()) == macros.end() && !(hasInclude && rawtok->next->str() == HAS_INCLUDE)); - maybeUsedMacros[rawtok->next->str()].emplace_back(rawtok->next->location); + else if (rawtok->str() == IFDEF || rawtok->str() == ELIFDEF) { + const std::string &name = rawtok->next->str(); + conditionIsTrue = (macros.find(name) != macros.end() || (hasInclude && name == HAS_INCLUDE)); + maybeUsedMacros[name].emplace_back(rawtok->next->location); + if (ifCond) { + const std::string E = conditionIsTrue ? "1" : "0"; + const long long result = conditionIsTrue ? 1 : 0; + ifCond->emplace_back(rawtok->location, E, result); + } + } else if (rawtok->str() == IFNDEF || rawtok->str() == ELIFNDEF) { + const std::string &name = rawtok->next->str(); + conditionIsTrue = (macros.find(name) == macros.end() && !(hasInclude && name == HAS_INCLUDE)); + maybeUsedMacros[name].emplace_back(rawtok->next->location); + if (ifCond) { + const std::string E = conditionIsTrue ? "1" : "0"; + const long long result = conditionIsTrue ? 1 : 0; + ifCond->emplace_back(rawtok->location, E, result); + } } else { /*if (rawtok->str() == IF || rawtok->str() == ELIF)*/ TokenList expr(files); for (const Token *tok = rawtok->next; tok && tok->location.sameline(rawtok->location); tok = tok->next) { @@ -3802,7 +3905,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL maybeUsedMacros[rawtok->next->str()].emplace_back(rawtok->next->location); const Token *tmp = tok; - if (!preprocessToken(expr, tmp, macros, files, outputList)) { + if (!preprocessToken(expr, tmp, macros, files, outputList, dui)) { output.clear(); return; } @@ -3839,7 +3942,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL } } - if (rawtok->str() != ELIF) { + if (rawtok->str() != ELIF && rawtok->str() != ELIFDEF && rawtok->str() != ELIFNDEF) { // push a new ifstate.. if (ifstates.top() != True) ifstates.push(AlwaysFalse); @@ -3900,7 +4003,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL const Location loc(rawtok->location); TokenList tokens(files); - if (!preprocessToken(tokens, rawtok, macros, files, outputList)) { + if (!preprocessToken(tokens, rawtok, macros, files, outputList, dui)) { output.clear(); return; } @@ -3959,6 +4062,20 @@ simplecpp::cstd_t simplecpp::getCStd(const std::string &std) return CUnknown; } +const char *simplecpp::getCStdName(cstd_t std) +{ + switch (std) { + case CUnknown: return "C"; + case C89: return "C89"; + case C99: return "C99"; + case C11: return "C11"; + case C17: return "C17"; + case C23: return "C23"; + case C2Y: return "C2Y"; + } + return ""; +} + std::string simplecpp::getCStdString(cstd_t std) { switch (std) { @@ -4011,6 +4128,21 @@ simplecpp::cppstd_t simplecpp::getCppStd(const std::string &std) return CPPUnknown; } +const char *simplecpp::getCppStdName(cppstd_t std) +{ + switch (std) { + case CPPUnknown: return "C++"; + case CPP03: return "C++03"; + case CPP11: return "C++11"; + case CPP14: return "C++14"; + case CPP17: return "C++17"; + case CPP20: return "C++20"; + case CPP23: return "C++23"; + case CPP26: return "C++26"; + } + return ""; +} + std::string simplecpp::getCppStdString(cppstd_t std) { switch (std) { diff --git a/simplecpp.h b/simplecpp.h index 3d6fc526..e64a8f7b 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -60,6 +60,15 @@ # endif #endif +#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ <= 9 +// Hack to workaround GCC bug. +// Details: https://trac.cppcheck.net/ticket/14850 +// seen on g++ before 10.x +#define SIMPLECPP_NOEXCEPT +#else +#define SIMPLECPP_NOEXCEPT noexcept +#endif + namespace simplecpp { /** C code standard */ enum cstd_t : std::int8_t { CUnknown=-1, C89, C99, C11, C17, C23, C2Y }; @@ -238,7 +247,10 @@ namespace simplecpp { MISSING_HEADER, INCLUDE_NESTED_TOO_DEEPLY, SYNTAX_ERROR, + DIRECTIVE_AS_MACRO_PARAMETER, PORTABILITY_BACKSLASH, + PORTABILITY_LINE_DIRECTIVE, + PORTABILITY_NO_EOF_NEWLINE, UNHANDLED_CHAR_ERROR, EXPLICIT_INCLUDE_NOT_FOUND, FILE_NOT_FOUND, @@ -251,6 +263,21 @@ namespace simplecpp { using OutputList = std::list; + /** + * Command line preprocessor settings. + * On the command line these are configured by -D, -U, -I, --include, -std + */ + struct SIMPLECPP_LIB DUI { + DUI() = default; + std::list defines; + std::set undefined; + std::list includePaths; + std::list includes; + std::string std; + bool clearIncludeCache{}; + bool removeComments{}; /** remove comment tokens from included files */ + }; + /** List of tokens. */ class SIMPLECPP_LIB TokenList { public: @@ -258,45 +285,45 @@ namespace simplecpp { explicit TokenList(std::vector &filenames); /** generates a token list from the given std::istream parameter */ - TokenList(std::istream &istr, std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr); + TokenList(std::istream &istr, std::vector &filenames, const std::string &filename=std::string(), const DUI &dui = {}, OutputList *outputList = nullptr); /** generates a token list from the given buffer */ template - TokenList(const char (&data)[size], std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) - : TokenList(reinterpret_cast(data), size-1, filenames, filename, outputList, 0) + TokenList(const char (&data)[size], std::vector &filenames, const std::string &filename=std::string(), const DUI &dui = {}, OutputList *outputList = nullptr) + : TokenList(reinterpret_cast(data), size-1, filenames, filename, dui, outputList, 0) {} /** generates a token list from the given buffer */ template - TokenList(const unsigned char (&data)[size], std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) - : TokenList(data, size-1, filenames, filename, outputList, 0) + TokenList(const unsigned char (&data)[size], std::vector &filenames, const std::string &filename=std::string(), const DUI &dui = {}, OutputList *outputList = nullptr) + : TokenList(data, size-1, filenames, filename, dui, outputList, 0) {} #if SIMPLECPP_TOKENLIST_ALLOW_PTR /** generates a token list from the given buffer */ - TokenList(const unsigned char* data, std::size_t size, std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) - : TokenList(data, size, filenames, filename, outputList, 0) + TokenList(const unsigned char* data, std::size_t size, std::vector &filenames, const std::string &filename=std::string(), const DUI &dui = {}, OutputList *outputList = nullptr) + : TokenList(data, size, filenames, filename, dui, outputList, 0) {} /** generates a token list from the given buffer */ - TokenList(const char* data, std::size_t size, std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) - : TokenList(reinterpret_cast(data), size, filenames, filename, outputList, 0) + TokenList(const char* data, std::size_t size, std::vector &filenames, const std::string &filename=std::string(), const DUI &dui = {}, OutputList *outputList = nullptr) + : TokenList(reinterpret_cast(data), size, filenames, filename, dui, outputList, 0) {} #endif // SIMPLECPP_TOKENLIST_ALLOW_PTR /** generates a token list from the given buffer */ - TokenList(View data, std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) - : TokenList(reinterpret_cast(data.data()), data.size(), filenames, filename, outputList, 0) + TokenList(View data, std::vector &filenames, const std::string &filename=std::string(), const DUI &dui = {}, OutputList *outputList = nullptr) + : TokenList(reinterpret_cast(data.data()), data.size(), filenames, filename, dui, outputList, 0) {} #ifdef __cpp_lib_span /** generates a token list from the given buffer */ - TokenList(std::span data, std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) - : TokenList(reinterpret_cast(data.data()), data.size(), filenames, filename, outputList, 0) + TokenList(std::span data, std::vector &filenames, const std::string &filename=std::string(), const DUI &dui = {}, OutputList *outputList = nullptr) + : TokenList(reinterpret_cast(data.data()), data.size(), filenames, filename, dui, outputList, 0) {} /** generates a token list from the given buffer */ - TokenList(std::span data, std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) - : TokenList(data.data(), data.size(), filenames, filename, outputList, 0) + TokenList(std::span data, std::vector &filenames, const std::string &filename=std::string(), const DUI &dui = {}, OutputList *outputList = nullptr) + : TokenList(data.data(), data.size(), filenames, filename, dui, outputList, 0) {} #endif // __cpp_lib_span /** generates a token list from the given filename parameter */ - TokenList(const std::string &filename, std::vector &filenames, OutputList *outputList = nullptr); + TokenList(const std::string &filename, std::vector &filenames, const DUI &dui = {}, OutputList *outputList = nullptr); TokenList(const TokenList &other); TokenList(TokenList &&other); ~TokenList(); @@ -312,7 +339,7 @@ namespace simplecpp { void dump(bool linenrs = false) const; std::string stringify(bool linenrs = false) const; - void readfile(Stream &stream, const std::string &filename=std::string(), OutputList *outputList = nullptr); + void readfile(Stream &stream, const std::string &filename=std::string(), const DUI &dui = {}, OutputList *outputList = nullptr); /** * @throws std::overflow_error thrown on overflow or division by zero * @throws std::runtime_error thrown on invalid expressions @@ -376,7 +403,7 @@ namespace simplecpp { const std::string& file(const Location& loc) const; private: - TokenList(const unsigned char* data, std::size_t size, std::vector &filenames, const std::string &filename, OutputList *outputList, int /*unused*/); + TokenList(const unsigned char* data, std::size_t size, std::vector &filenames, const std::string &filename, const DUI &dui, OutputList *outputList, int /*unused*/); void combineOperators(); @@ -425,21 +452,6 @@ namespace simplecpp { long long result; // condition result }; - /** - * Command line preprocessor settings. - * On the command line these are configured by -D, -U, -I, --include, -std - */ - struct SIMPLECPP_LIB DUI { - DUI() = default; - std::list defines; - std::set undefined; - std::list includePaths; - std::list includes; - std::string std; - bool clearIncludeCache{}; - bool removeComments{}; /** remove comment tokens from included files */ - }; - struct SIMPLECPP_LIB FileData { /** The canonical filename associated with this data */ std::string filename; @@ -453,10 +465,10 @@ namespace simplecpp { ~FileDataCache(); FileDataCache(const FileDataCache &) = delete; - FileDataCache(FileDataCache &&) noexcept; + FileDataCache(FileDataCache &&) SIMPLECPP_NOEXCEPT; FileDataCache &operator=(const FileDataCache &) = delete; - FileDataCache &operator=(FileDataCache &&) noexcept; + FileDataCache &operator=(FileDataCache &&) SIMPLECPP_NOEXCEPT; /** Get the cached data for a file, or load and then return it if it isn't cached. * returns the file data and true if the file was loaded, false if it was cached. */ @@ -578,9 +590,15 @@ namespace simplecpp { /** Returns the C version a given standard */ SIMPLECPP_LIB cstd_t getCStd(const std::string &std); + /** Returns the name of a C standard */ + SIMPLECPP_LIB const char *getCStdName(cstd_t std); + /** Returns the C++ version a given standard */ SIMPLECPP_LIB cppstd_t getCppStd(const std::string &std); + /** Returns the name of a C++ standard */ + SIMPLECPP_LIB const char *getCppStdName(cppstd_t std); + /** Returns the __STDC_VERSION__ value for a given standard */ SIMPLECPP_LIB std::string getCStdString(const std::string &std); SIMPLECPP_LIB std::string getCStdString(cstd_t std); diff --git a/test.cpp b/test.cpp index b13140aa..d059b237 100644 --- a/test.cpp +++ b/test.cpp @@ -18,6 +18,10 @@ #include #include +#ifdef __cpp_lib_string_view +#include +#endif + #ifndef SIMPLECPP_TEST_SOURCE_DIR #error "SIMPLECPP_TEST_SOURCE_DIR is not defined." #endif @@ -101,42 +105,48 @@ static void testcase(const std::string &name, void (*f)(), int argc, char * cons #define TEST_CASE(F) (testcase(#F, F, argc, argv)) -static simplecpp::TokenList makeTokenList(const char code[], std::size_t size, std::vector &filenames, const std::string &filename=std::string(), simplecpp::OutputList *outputList=nullptr) +static simplecpp::TokenList makeTokenList(const char code[], std::size_t size, std::vector &filenames, const std::string &filename=std::string(), const simplecpp::DUI &dui = {}, simplecpp::OutputList *outputList = nullptr) { switch (USE_INPUT) { case Input::Stringstream: { std::istringstream istr(std::string(code, size)); - return {istr,filenames,filename,outputList}; + return {istr,filenames,filename,dui,outputList}; } case Input::CharBuffer: - return {{code, size}, filenames, filename, outputList}; + return {{code, size}, filenames, filename, dui, outputList}; } return simplecpp::TokenList{filenames}; // unreachable - needed for GCC and Visual Studio } -static simplecpp::TokenList makeTokenList(const char code[], std::vector &filenames, const std::string &filename=std::string(), simplecpp::OutputList *outputList=nullptr) +static simplecpp::TokenList makeTokenList(const char code[], std::vector &filenames, const std::string &filename=std::string(), const simplecpp::DUI &dui = {}, simplecpp::OutputList *outputList=nullptr) { - return makeTokenList(code, strlen(code), filenames, filename, outputList); + return makeTokenList(code, strlen(code), filenames, filename, dui, outputList); } -static std::string readfile(const char code[], simplecpp::OutputList *outputList=nullptr) +static simplecpp::TokenList makeTokenList(const char code[], const simplecpp::DUI &dui = {}, simplecpp::OutputList *outputList=nullptr) { std::vector files; - return makeTokenList(code,files,std::string(),outputList).stringify(); + return makeTokenList(code, strlen(code), files, std::string(), dui, outputList); } -static std::string readfile(const char code[], std::size_t size, simplecpp::OutputList *outputList=nullptr) +static std::string readfile(const char code[], const simplecpp::DUI &dui = {}, simplecpp::OutputList *outputList = nullptr) { std::vector files; - return makeTokenList(code,size,files,std::string(),outputList).stringify(); + return makeTokenList(code,files,std::string(),dui,outputList).stringify(); } -static std::string preprocess(const char code[], const simplecpp::DUI &dui, simplecpp::OutputList *outputList, std::list *macroUsage = nullptr, std::list *ifCond = nullptr, const std::string &file = std::string()) +static std::string readfile(const char code[], std::size_t size, const simplecpp::DUI &dui = {}, simplecpp::OutputList *outputList=nullptr) +{ + std::vector files; + return makeTokenList(code,size,files,std::string(),dui,outputList).stringify(); +} + +static std::string preprocess(const char code[], std::size_t size, const simplecpp::DUI &dui, simplecpp::OutputList *outputList, std::list *macroUsage = nullptr, std::list *ifCond = nullptr, const std::string &file = std::string()) { std::vector files; simplecpp::FileDataCache cache; - simplecpp::TokenList tokens = makeTokenList(code,files, file); + simplecpp::TokenList tokens = makeTokenList(code, size, files, file, dui, outputList); if (dui.removeComments) tokens.removeComments(); simplecpp::TokenList tokens2(files); @@ -145,6 +155,11 @@ static std::string preprocess(const char code[], const simplecpp::DUI &dui, simp return tokens2.stringify(); } +static std::string preprocess(const char code[], const simplecpp::DUI &dui, simplecpp::OutputList *outputList, std::list *macroUsage = nullptr, std::list *ifCond = nullptr, const std::string &file = std::string()) +{ + return preprocess(code, strlen(code), dui, outputList, macroUsage, ifCond, file); +} + static std::string preprocess(const char code[]) { return preprocess(code, simplecpp::DUI(), nullptr); @@ -165,6 +180,11 @@ static std::string preprocess(const char code[], simplecpp::OutputList *outputLi return preprocess(code, simplecpp::DUI(), outputList); } +static std::string preprocess(const char code[], std::size_t size, simplecpp::OutputList *outputList) +{ + return preprocess(code, size, simplecpp::DUI(), outputList); +} + static std::string preprocess(const char code[], std::list *ifCond) { return preprocess(code, simplecpp::DUI(), nullptr, nullptr, ifCond); @@ -197,9 +217,18 @@ static std::string toString(const simplecpp::OutputList &outputList) case simplecpp::Output::Type::SYNTAX_ERROR: ostr << "syntax_error,"; break; + case simplecpp::Output::DIRECTIVE_AS_MACRO_PARAMETER: + ostr << "directive_as_macro_parameter,"; + break; case simplecpp::Output::Type::PORTABILITY_BACKSLASH: ostr << "portability_backslash,"; break; + case simplecpp::Output::Type::PORTABILITY_LINE_DIRECTIVE: + ostr << "portability_line_directive,"; + break; + case simplecpp::Output::Type::PORTABILITY_NO_EOF_NEWLINE: + ostr << "portability_no_eof_newline,"; + break; case simplecpp::Output::Type::UNHANDLED_CHAR_ERROR: ostr << "unhandled_char_error,"; break; @@ -224,15 +253,15 @@ static void backslash() // preprocessed differently simplecpp::OutputList outputList; - readfile("//123 \\\n456", &outputList); + readfile("//123 \\\n456\n", {}, &outputList); ASSERT_EQUALS("", toString(outputList)); - readfile("//123 \\ \n456", &outputList); + readfile("//123 \\ \n456\n", {}, &outputList); ASSERT_EQUALS("file0,1,portability_backslash,Combination 'backslash space newline' is not portable.\n", toString(outputList)); outputList.clear(); - readfile("#define A \\\n123", &outputList); + readfile("#define A \\\n123\n", {}, &outputList); ASSERT_EQUALS("", toString(outputList)); - readfile("#define A \\ \n123", &outputList); + readfile("#define A \\ \n123\n", {}, &outputList); ASSERT_EQUALS("file0,1,portability_backslash,Combination 'backslash space newline' is not portable.\n", toString(outputList)); } @@ -504,12 +533,15 @@ static void combineOperators_ellipsis() static void comment() { + simplecpp::DUI dui; + dui.removeComments = true; + ASSERT_EQUALS("// abc", readfile("// abc")); - ASSERT_EQUALS("", preprocess("// abc")); + ASSERT_EQUALS("", preprocess("// abc", dui)); ASSERT_EQUALS("/*\n\n*/abc", readfile("/*\n\n*/abc")); - ASSERT_EQUALS("\n\nabc", preprocess("/*\n\n*/abc")); + ASSERT_EQUALS("\n\nabc", preprocess("/*\n\n*/abc", dui)); ASSERT_EQUALS("* p = a / * b / * c ;", readfile("*p=a/ *b/ *c;")); - ASSERT_EQUALS("* p = a / * b / * c ;", preprocess("*p=a/ *b/ *c;")); + ASSERT_EQUALS("* p = a / * b / * c ;", preprocess("*p=a/ *b/ *c;", dui)); } static void comment_multiline() @@ -549,6 +581,27 @@ static void comment_multiline() ASSERT_EQUALS("// abc\ndef", readfile("// abc\\\ndef")); } +static void keep_comments() +{ + simplecpp::DUI dui; + dui.removeComments = false; + + { + const char code[] = "/* comment */\n"; + ASSERT_EQUALS("/* comment */", preprocess(code,dui)); + } + + { + const char code[] = "// comment\n"; + ASSERT_EQUALS("// comment", preprocess(code,dui)); + } + + { + const char code[] = "#define MACRO /* comment */\nMACRO\n"; + ASSERT_EQUALS("\n/* comment */", preprocess(code,dui)); + } +} + static void constFold() { @@ -907,12 +960,30 @@ static void define_invalid_1() static void define_invalid_2() { - const char code[] = "#define\nhas#"; + const char code[] = "#define\nhas#\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,Failed to parse #define, bad macro syntax\n", toString(outputList)); } +static void define_invalid_3() +{ + const char code[] = "#define R()\n" + "R\n"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("", preprocess(code, &outputList)); + ASSERT_EQUALS("file0,2,syntax_error,failed to expand 'R', Wrong number of parameters for macro 'R'.\n", toString(outputList)); +} + +static void define_invalid_4() +{ + const char code[] = "#define X(...)\n" + "X\n"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("", preprocess(code, &outputList)); + ASSERT_EQUALS("file0,2,syntax_error,failed to expand 'X', Wrong number of parameters for macro 'X'.\n", toString(outputList)); +} + static void define_define_1() { const char code[] = "#define A(x) (x+1)\n" @@ -1217,7 +1288,7 @@ static void define_va_opt_3() // non-escaped newline without closing parenthesis const char code1[] = "#define err(...) __VA_OPT__(printf( __VA_ARGS__);\n" ")\n" - "err()"; + "err()\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code1, &outputList)); @@ -1229,7 +1300,7 @@ static void define_va_opt_3() // non-escaped newline without open parenthesis const char code2[] = "#define err(...) __VA_OPT__\n" "(something)\n" - "err()"; + "err()\n"; ASSERT_EQUALS("", preprocess(code2, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,Failed to parse #define, In definition of 'err': Missing opening parenthesis for __VA_OPT__\n", @@ -1240,7 +1311,7 @@ static void define_va_opt_4() { // missing parenthesis const char code1[] = "#define err(...) __VA_OPT__ something\n" - "err()"; + "err()\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code1, &outputList)); @@ -1251,7 +1322,7 @@ static void define_va_opt_4() // missing open parenthesis const char code2[] = "#define err(...) __VA_OPT__ something)\n" - "err()"; + "err()\n"; ASSERT_EQUALS("", preprocess(code2, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,Failed to parse #define, In definition of 'err': Missing opening parenthesis for __VA_OPT__\n", @@ -1262,7 +1333,7 @@ static void define_va_opt_5() { // parenthesis not directly proceeding __VA_OPT__ const char code[] = "#define err(...) __VA_OPT__ something (something)\n" - "err()"; + "err()\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); @@ -1274,7 +1345,7 @@ static void define_va_opt_6() { // nested __VA_OPT__ const char code[] = "#define err(...) __VA_OPT__(__VA_OPT__(something))\n" - "err()"; + "err()\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); @@ -1285,7 +1356,7 @@ static void define_va_opt_6() static void define_va_opt_7() { // eof in __VA_OPT__ - const char code1[] = "#define err(...) __VA_OPT__"; + const char code1[] = "#define err(...) __VA_OPT__\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code1, &outputList)); @@ -1294,7 +1365,7 @@ static void define_va_opt_7() outputList.clear(); - const char code2[] = "#define err(...) __VA_OPT__("; + const char code2[] = "#define err(...) __VA_OPT__(\n"; ASSERT_EQUALS("", preprocess(code2, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,Failed to parse #define, In definition of 'err': Missing closing parenthesis for __VA_OPT__\n", @@ -1302,7 +1373,7 @@ static void define_va_opt_7() outputList.clear(); - const char code3[] = "#define err(...) __VA_OPT__(x"; + const char code3[] = "#define err(...) __VA_OPT__(x\n"; ASSERT_EQUALS("", preprocess(code3, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,Failed to parse #define, In definition of 'err': Missing closing parenthesis for __VA_OPT__\n", @@ -1312,7 +1383,7 @@ static void define_va_opt_7() static void define_va_opt_8() { const char code[] = "#define f(...) #__VA_OPT__(x)\n" - "const char* v1 = f();"; + "const char* v1 = f();\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("\nconst char * v1 = \"\" ;", preprocess(code, &outputList)); @@ -1338,8 +1409,32 @@ static void define_ifdef() simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); - ASSERT_EQUALS("file0,3,syntax_error,failed to expand 'A', it is invalid to use a preprocessor directive as macro parameter\n", toString(outputList)); + ASSERT_EQUALS("file0,3,directive_as_macro_parameter,failed to expand 'A', it is invalid to use a preprocessor directive as macro parameter\n", toString(outputList)); + +} + +static void if_invalid_1() +{ + const char code[] = "#if'\\u'\n"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("", preprocess(code, &outputList)); + ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition, expected digit\n", toString(outputList)); +} + +static void if_invalid_2() +{ + const char code[] = "#if-0xBBB4444444444444%~B\n"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("", preprocess(code, &outputList)); + ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition, division overflow\n", toString(outputList)); +} +static void if_invalid_3() +{ + const char code[] = "#if@u'\\udefa'\n"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("", preprocess(code, &outputList)); + ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition, surrogate code points not allowed in universal character names\n", toString(outputList)); } static void pragma_backslash() @@ -1394,7 +1489,7 @@ static void error3() " bla bla.\"\n"; std::vector files; simplecpp::OutputList outputList; - const simplecpp::TokenList rawtokens = makeTokenList(code, files, "test.c", &outputList); + const simplecpp::TokenList rawtokens = makeTokenList(code, files, "test.c", {}, &outputList); ASSERT_EQUALS("", toString(outputList)); } @@ -1406,7 +1501,7 @@ static void error4() simplecpp::FileDataCache cache; simplecpp::OutputList outputList; simplecpp::TokenList tokens2(files); - const simplecpp::TokenList rawtoken = makeTokenList(code, sizeof(code),files,"test.c"); + const simplecpp::TokenList rawtoken = makeTokenList(code, sizeof(code)-1,files,"test.c"); simplecpp::preprocess(tokens2, rawtoken, files, cache, simplecpp::DUI(), &outputList); ASSERT_EQUALS("file0,1,#error,#error x\n", toString(outputList)); } @@ -1419,7 +1514,7 @@ static void error5() simplecpp::FileDataCache cache; simplecpp::OutputList outputList; simplecpp::TokenList tokens2(files); - const simplecpp::TokenList rawtokens = makeTokenList(code, sizeof(code),files,"test.c"); + const simplecpp::TokenList rawtokens = makeTokenList(code, sizeof(code)-1,files,"test.c"); simplecpp::preprocess(tokens2, rawtokens, files, cache, simplecpp::DUI(), &outputList); ASSERT_EQUALS("file0,1,#error,#error x\n", toString(outputList)); } @@ -1432,7 +1527,7 @@ static void error6() simplecpp::FileDataCache cache; simplecpp::OutputList outputList; simplecpp::TokenList tokens2(files); - const simplecpp::TokenList rawtokens = makeTokenList(code, sizeof(code),files,"test.c"); + const simplecpp::TokenList rawtokens = makeTokenList(code, sizeof(code)-1,files,"test.c"); simplecpp::preprocess(tokens2, rawtokens, files, cache, simplecpp::DUI(), &outputList); ASSERT_EQUALS("file0,1,#error,#error \n", toString(outputList)); } @@ -1444,7 +1539,7 @@ static void error7() simplecpp::FileDataCache cache; simplecpp::OutputList outputList; simplecpp::TokenList tokens2(files); - const simplecpp::TokenList rawtokens = makeTokenList(code, sizeof(code),files,"test.c"); + const simplecpp::TokenList rawtokens = makeTokenList(code, sizeof(code)-1,files,"test.c"); simplecpp::preprocess(tokens2, rawtokens, files, cache, simplecpp::DUI(), &outputList); ASSERT_EQUALS("file0,1,#error,#error blabla\n", toString(outputList)); } @@ -1456,7 +1551,7 @@ static void error8() simplecpp::FileDataCache cache; simplecpp::OutputList outputList; simplecpp::TokenList tokens2(files); - const simplecpp::TokenList rawtokens = makeTokenList(code, sizeof(code),files,"test.c"); + const simplecpp::TokenList rawtokens = makeTokenList(code, sizeof(code)-1,files,"test.c"); simplecpp::preprocess(tokens2, rawtokens, files, cache, simplecpp::DUI(), &outputList); ASSERT_EQUALS("file0,1,#error,#error blabla\n", toString(outputList)); } @@ -1637,19 +1732,19 @@ static void hashhash9() simplecpp::OutputList outputList; code = "#define A +##x\n" - "A"; + "A\n"; outputList.clear(); ASSERT_EQUALS("", preprocess(code, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'A', Invalid ## usage when expanding 'A': Combining '+' and 'x' yields an invalid token.\n", toString(outputList)); code = "#define A 2##=\n" - "A"; + "A\n"; outputList.clear(); ASSERT_EQUALS("", preprocess(code, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'A', Invalid ## usage when expanding 'A': Combining '2' and '=' yields an invalid token.\n", toString(outputList)); code = "#define A <<##x\n" - "A"; + "A\n"; outputList.clear(); ASSERT_EQUALS("", preprocess(code, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'A', Invalid ## usage when expanding 'A': Combining '<<' and 'x' yields an invalid token.\n", toString(outputList)); @@ -1818,7 +1913,7 @@ static void hashhash_int_literal() static void hashhash_invalid_1() { - const char code[] = "#define f(a) (##x)\nf(1)"; + const char code[] = "#define f(a) (##x)\nf(1)\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'f', Invalid ## usage when expanding 'f': Unexpected token '('\n", toString(outputList)); @@ -1826,7 +1921,7 @@ static void hashhash_invalid_1() static void hashhash_invalid_2() { - const char code[] = "#define f(a) (x##)\nf(1)"; + const char code[] = "#define f(a) (x##)\nf(1)\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'f', Invalid ## usage when expanding 'f': Unexpected token ')'\n", toString(outputList)); @@ -1835,7 +1930,7 @@ static void hashhash_invalid_2() static void hashhash_invalid_string_number() { const char code[] = - "#define BAD(x) x##12345\nBAD(\"ABC\")"; + "#define BAD(x) x##12345\nBAD(\"ABC\")\n"; simplecpp::OutputList outputList; preprocess(code, simplecpp::DUI(), &outputList); @@ -1845,7 +1940,7 @@ static void hashhash_invalid_string_number() static void hashhash_invalid_missing_args() { const char code[] = - "#define BAD(x) ##x\nBAD()"; + "#define BAD(x) ##x\nBAD()\n"; simplecpp::OutputList outputList; preprocess(code, simplecpp::DUI(), &outputList); @@ -1879,7 +1974,7 @@ static void hashhash_va_args_unexpected() { const char code[] = "#define C(...)!##__VA_ARGS__\n" - "C(1)"; + "C(1)\n"; simplecpp::OutputList outputList; preprocess(code, simplecpp::DUI(), &outputList); ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'C', Invalid ## usage when expanding 'C': Unexpected token '!'\n", toString(outputList)); @@ -1888,7 +1983,7 @@ static void hashhash_va_args_unexpected() static void hashhash_universal_character() { const char code[] = - "#define A(x,y) x##y\nint A(\\u01,04);"; + "#define A(x,y) x##y\nint A(\\u01,04);\n"; simplecpp::OutputList outputList; preprocess(code, simplecpp::DUI(), &outputList); ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'A', Invalid ## usage when expanding 'A': Combining '\\u01' and '04' yields universal character '\\u0104'. This is undefined behavior according to C standard chapter 5.1.1.2, paragraph 4.\n", toString(outputList)); @@ -1897,7 +1992,7 @@ static void hashhash_universal_character() static void hashhash_universal_character_2() { const char code[] = - "#define A(x,y) x##y\nint A(\\U0104, 0104);"; + "#define A(x,y) x##y\nint A(\\U0104, 0104);\n"; simplecpp::OutputList outputList; preprocess(code, simplecpp::DUI(), &outputList); ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'A', Invalid ## usage when expanding 'A': Combining '\\U0104' and '0104' yields universal character '\\U01040104'. This is undefined behavior according to C standard chapter 5.1.1.2, paragraph 4.\n", toString(outputList)); @@ -2033,6 +2128,42 @@ static void has_include_6() ASSERT_EQUALS("\n\nA", preprocess(code, dui)); } +static void define_has_include_invalid_1() +{ + const char code[] = "#define A)__has_include\n" + "#if\u000BA\n"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("", preprocess(code, &outputList)); + ASSERT_EQUALS("file0,2,syntax_error,failed to evaluate #if condition, missing __has_include argument\n", toString(outputList)); +} + +static void define_has_include_invalid_2() +{ + const char code[] = "#define f __has_include\n" + "#if#f<\n"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("", preprocess(code, &outputList)); + ASSERT_EQUALS("file0,2,syntax_error,failed to evaluate #if condition, missing __has_include argument\n", toString(outputList)); +} + +static void define_has_include_invalid_3() +{ + const char code[] = "#define\u0000X\u0007__has_include(\n" + "#if%X&\n"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("", preprocess(code, sizeof(code)-1, &outputList)); + ASSERT_EQUALS("file0,2,syntax_error,failed to evaluate #if condition, invalid __has_include expression\n", toString(outputList)); +} + +static void define_has_include_invalid_4() +{ + const char code[] = "#define\u0000X\u0000__has_include<2\n" + "#if*X\n"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("", preprocess(code, sizeof(code)-1, &outputList)); + ASSERT_EQUALS("file0,2,syntax_error,failed to evaluate #if condition, invalid __has_include expression\n", toString(outputList)); +} + static void strict_ansi_1() { const char code[] = "#if __STRICT_ANSI__\n" @@ -2178,7 +2309,7 @@ static void ifDefinedNestedNoPar() static void ifDefinedInvalid1() // #50 - invalid unterminated defined { - const char code[] = "#if defined(A"; + const char code[] = "#if defined(A\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition\n", toString(outputList)); @@ -2186,7 +2317,7 @@ static void ifDefinedInvalid1() // #50 - invalid unterminated defined static void ifDefinedInvalid2() { - const char code[] = "#if defined"; + const char code[] = "#if defined\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition\n", toString(outputList)); @@ -2272,6 +2403,76 @@ static void elif() ASSERT_EQUALS("\n\n\n\n\n3", preprocess(code3)); } +static void elifdef() +{ + { + const char code[] = "#if 1\n" + "1\n" + "#elifdef X\n" + "2\n" + "#else\n" + "3\n" + "#endif"; + ASSERT_EQUALS("\n1", preprocess(code)); + } + { + const char code[] = "#define X\n" + "#if 0\n" + "1\n" + "#elifdef X\n" + "2\n" + "#else\n" + "3\n" + "#endif"; + ASSERT_EQUALS("\n\n\n\n2", preprocess(code)); + } + { + const char code[] = "#if 0\n" + "1\n" + "#elifdef X\n" + "2\n" + "#else\n" + "3\n" + "#endif"; + ASSERT_EQUALS("\n\n\n\n\n3", preprocess(code)); + } +} + +static void elifndef() +{ + { + const char code[] = "#if 1\n" + "1\n" + "#elifndef X\n" + "2\n" + "#else\n" + "3\n" + "#endif"; + ASSERT_EQUALS("\n1", preprocess(code)); + } + { + const char code[] = "#if 0\n" + "1\n" + "#elifndef X\n" + "2\n" + "#else\n" + "3\n" + "#endif"; + ASSERT_EQUALS("\n\n\n2", preprocess(code)); + } + { + const char code[] = "#define X\n" + "#if 0\n" + "1\n" + "#elifndef X\n" + "2\n" + "#else\n" + "3\n" + "#endif"; + ASSERT_EQUALS("\n\n\n\n\n\n3", preprocess(code)); + } +} + static void ifif() { // source code from LLVM @@ -2477,15 +2678,19 @@ static void location11() static void location12() { + simplecpp::DUI dui; + dui.removeComments = true; + const char code[] = "/**//**/#/**//**/line/**//**/3/**//**/\"file.c\"/**/\n" "__LINE__ __FILE__\n"; ASSERT_EQUALS("\n" "#line 3 \"file.c\"\n" "3 \"file.c\"", - preprocess(code)); + preprocess(code, dui)); } + static void missingHeader1() { const char code[] = "#include \"notexist.h\"\n"; @@ -2749,6 +2954,221 @@ static void nullDirective3() ASSERT_EQUALS("\n\n\n\nx = 1 ;", preprocess(code)); } +static void lineDirective() +{ + for (const std::string std : {"c89", "c90", "c++03"}) { + std::string std_name; + if (simplecpp::getCStd(std) != simplecpp::CUnknown) + std_name = simplecpp::getCStdName(simplecpp::getCStd(std)); + else + std_name = simplecpp::getCppStdName(simplecpp::getCppStd(std)); + + simplecpp::DUI dui; + dui.std = std; + + { + const char code[] = + "#line -1\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("file0,2,syntax_error,Invalid character in line directive: '-'.\n", + toString(outputList)); + } + + { + const char code[] = + "#line 0\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("file0,2,portability_line_directive,Line number out of range: 0. Line number zero is undefined behavior.\n", + toString(outputList)); + } + + { + const char code[] = + "#line 32767\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("", toString(outputList)); + } + + { + const char code[] = + "#line 32768\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("file0,2,portability_line_directive,Line number out of range: 32768. Line numbers above 32767 are undefined behavior in " + std_name + ".\n", + toString(outputList)); + } + + { + const char code[] = + "#line 18446744073709551617\n" // 2^64 + 1 + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("file0,2,portability_line_directive,Line number out of range: 18446744073709551617. Line numbers above 32767 are undefined behavior in " + std_name + ".\n", + toString(outputList)); + } + } + + for (const std::string std : {"c99", "c11", "c17", "c23", "c2y", "c++11", "c++14", "c++17", "c++20", "c++23"}) { + std::string std_name; + if (simplecpp::getCStd(std) != simplecpp::CUnknown) + std_name = simplecpp::getCStdName(simplecpp::getCStd(std)); + else + std_name = simplecpp::getCppStdName(simplecpp::getCppStd(std)); + + simplecpp::DUI dui; + dui.std = std; + + { + const char code[] = + "#line -1\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("file0,2,syntax_error,Invalid character in line directive: '-'.\n", + toString(outputList)); + } + + { + const char code[] = + "#line 0\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("file0,2,portability_line_directive,Line number out of range: 0. Line number zero is undefined behavior.\n", + toString(outputList)); + } + + { + const char code[] = + "#line 32767\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("", toString(outputList)); + } + + { + const char code[] = + "#line 32768\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("", toString(outputList)); + } + + { + const char code[] = + "#line 2147483647\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("", toString(outputList)); + } + + { + const char code[] = + "#line 2147483648\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("file0,2,portability_line_directive,Line number out of range: 2147483648. Line numbers above 2147483647 are undefined behavior in " + std_name + ".\n", + toString(outputList)); + } + + { + const char code[] = + "#line 18446744073709551617\n" // 2^64 + 1 + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("file0,2,portability_line_directive,Line number out of range: 18446744073709551617. Line numbers above 2147483647 are undefined behavior in " + std_name + ".\n", + toString(outputList)); + } + } + + { + const std::string std_name = "C++26"; + + simplecpp::DUI dui; + dui.std = "c++26"; + + { + const char code[] = + "#line -1\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("file0,2,syntax_error,Invalid character in line directive: '-'.\n", + toString(outputList)); + } + + { + const char code[] = + "#line 0\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("file0,2,portability_line_directive,Line number out of range: 0. Line number zero is undefined behavior.\n", + toString(outputList)); + } + + { + const char code[] = + "#line 32767\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("", toString(outputList)); + } + + { + const char code[] = + "#line 32768\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("", toString(outputList)); + } + + { + const char code[] = + "#line 2147483647\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("", toString(outputList)); + } + + { + const char code[] = + "#line 2147483648\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("file0,2,portability_line_directive,Line number out of range: 2147483648. Line numbers above 2147483647 are conditionally supported in " + std_name + ".\n", + toString(outputList)); + } + + { + const char code[] = + "#line 18446744073709551617\n" // 2^64 + 1 + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("file0,2,portability_line_directive,Line number out of range: 18446744073709551617. Line numbers above 2147483647 are conditionally supported in " + std_name + ".\n", + toString(outputList)); + } + } +} + static void include1() { const char code[] = "#include \"A.h\"\n"; @@ -2921,6 +3341,7 @@ static void include9() simplecpp::TokenList out(files); simplecpp::DUI dui; dui.includePaths.emplace_back("."); + dui.removeComments = true; simplecpp::preprocess(out, rawtokens_c, files, cache, dui); ASSERT_EQUALS("\n#line 2 \"1.h\"\nx = 1 ;", out.stringify()); @@ -2986,9 +3407,9 @@ static void include11() // #669 - -include with preprocess() static void readfile_nullbyte() { - const char code[] = "ab\0cd"; + const char code[] = "ab\0cd\n"; simplecpp::OutputList outputList; - ASSERT_EQUALS("ab cd", readfile(code,sizeof(code), &outputList)); + ASSERT_EQUALS("ab cd", readfile(code,sizeof(code)-1,{},&outputList)); ASSERT_EQUALS(true, outputList.empty()); // should warning be written? } @@ -3016,11 +3437,11 @@ static void readfile_char_error() { simplecpp::OutputList outputList; - readfile("A = L's", &outputList); + readfile("A = L's", {}, &outputList); ASSERT_EQUALS("file0,1,syntax_error,No pair for character (\'). Can't process file. File is either invalid or unicode, which is currently not supported.\n", toString(outputList)); outputList.clear(); - readfile("A = 's\n'", &outputList); + readfile("A = 's\n'", {}, &outputList); ASSERT_EQUALS("file0,1,syntax_error,No pair for character (\'). Can't process file. File is either invalid or unicode, which is currently not supported.\n", toString(outputList)); } @@ -3074,36 +3495,36 @@ static void readfile_string_error() { simplecpp::OutputList outputList; - readfile("A = \"abs", &outputList); + readfile("A = \"abs", {}, &outputList); ASSERT_EQUALS("file0,1,syntax_error,No pair for character (\"). Can't process file. File is either invalid or unicode, which is currently not supported.\n", toString(outputList)); outputList.clear(); - readfile("A = u8\"abs\n\"", &outputList); + readfile("A = u8\"abs\n\"", {}, &outputList); ASSERT_EQUALS("file0,1,syntax_error,No pair for character (\"). Can't process file. File is either invalid or unicode, which is currently not supported.\n", toString(outputList)); outputList.clear(); - readfile("A = R\"as\n(abc)as\"", &outputList); + readfile("A = R\"as\n(abc)as\"", {}, &outputList); ASSERT_EQUALS("file0,1,syntax_error,Invalid newline in raw string delimiter.\n", toString(outputList)); outputList.clear(); - readfile("A = u8R\"as\n(abc)as\"", &outputList); + readfile("A = u8R\"as\n(abc)as\"", {}, &outputList); ASSERT_EQUALS("file0,1,syntax_error,Invalid newline in raw string delimiter.\n", toString(outputList)); outputList.clear(); - readfile("A = R\"as(abc)a\"", &outputList); + readfile("A = R\"as(abc)a\"", {}, &outputList); ASSERT_EQUALS("file0,1,syntax_error,Raw string missing terminating delimiter.\n", toString(outputList)); outputList.clear(); - readfile("A = LR\"as(abc)a\"", &outputList); + readfile("A = LR\"as(abc)a\"", {}, &outputList); ASSERT_EQUALS("file0,1,syntax_error,Raw string missing terminating delimiter.\n", toString(outputList)); outputList.clear(); - readfile("#define A \"abs", &outputList); + readfile("#define A \"abs", {}, &outputList); ASSERT_EQUALS("file0,1,syntax_error,No pair for character (\"). Can't process file. File is either invalid or unicode, which is currently not supported.\n", toString(outputList)); outputList.clear(); // Don't warn for a multiline define - readfile("#define A \"abs\\\n\"", &outputList); + readfile("#define A \"abs\\\n\"\n", {}, &outputList); ASSERT_EQUALS("", toString(outputList)); } @@ -3115,11 +3536,11 @@ static void readfile_cpp14_number() static void readfile_unhandled_chars() { simplecpp::OutputList outputList; - readfile("// 你好世界", &outputList); + readfile("// 你好世界\n", {}, &outputList); ASSERT_EQUALS("", toString(outputList)); - readfile("s=\"你好世界\"", &outputList); + readfile("s=\"你好世界\"\n", {}, &outputList); ASSERT_EQUALS("", toString(outputList)); - readfile("int 你好世界=0;", &outputList); + readfile("int 你好世界=0;\n", {}, &outputList); ASSERT_EQUALS("file0,1,unhandled_char_error,The code contains unhandled character(s) (character code=228). Neither unicode nor extended ascii is supported.\n", toString(outputList)); } @@ -3135,10 +3556,82 @@ static void readfile_file_not_found() { simplecpp::OutputList outputList; std::vector files; - (void)simplecpp::TokenList("NotAFile", files, &outputList); + (void)simplecpp::TokenList("NotAFile", files, {}, &outputList); ASSERT_EQUALS("file0,0,file_not_found,File is missing: NotAFile\n", toString(outputList)); } +static void readfile_no_eof_newline() +{ + { + const char code[] = ""; + simplecpp::OutputList outputList; + readfile(code, sizeof(code)-1, {}, &outputList); + ASSERT_EQUALS("", toString(outputList)); + } + { + const char code[] = "\n"; + simplecpp::OutputList outputList; + readfile(code, sizeof(code)-1, {}, &outputList); + ASSERT_EQUALS("", toString(outputList)); + } + { + const char code[] = "\\\n"; + simplecpp::OutputList outputList; + readfile(code, sizeof(code)-1, {}, &outputList); + ASSERT_EQUALS("file0,1,portability_no_eof_newline,No newline at end of file is undefined behavior in C.\n", toString(outputList)); + } + { + const char code[] = "\\\n"; + simplecpp::DUI dui; + dui.std = "c++03"; + simplecpp::OutputList outputList; + readfile(code, sizeof(code)-1, dui, &outputList); + ASSERT_EQUALS("", toString(outputList)); + } + { + const char code[] = "#define A"; + simplecpp::OutputList outputList; + readfile(code, sizeof(code)-1, {}, &outputList); + ASSERT_EQUALS("file0,1,portability_no_eof_newline,No newline at end of file is undefined behavior in C.\n", toString(outputList)); + } + { + const char code[] = "#define A\n"; + simplecpp::OutputList outputList; + readfile(code, sizeof(code)-1, {}, &outputList); + ASSERT_EQUALS("", toString(outputList)); + } + { + const char code[] = "#define A\\"; + simplecpp::OutputList outputList; + readfile(code, sizeof(code)-1, {}, &outputList); + ASSERT_EQUALS("file0,1,portability_no_eof_newline,No newline at end of file is undefined behavior in C.\n", toString(outputList)); + } + { + const char code[] = "// comment"; + simplecpp::OutputList outputList; + readfile(code, sizeof(code)-1, {}, &outputList); + ASSERT_EQUALS("file0,1,portability_no_eof_newline,No newline at end of file is undefined behavior in C.\n", toString(outputList)); + } + { + const char code[] = "// comment\n"; + simplecpp::OutputList outputList; + readfile(code, sizeof(code)-1, {}, &outputList); + ASSERT_EQUALS("", toString(outputList)); + } + { + const char code[] = "/* comment \n comment */"; + simplecpp::OutputList outputList; + readfile(code, sizeof(code)-1, {}, &outputList); + ASSERT_EQUALS("file0,2,portability_no_eof_newline,No newline at end of file is undefined behavior in C.\n", toString(outputList)); + } + { + const char code[] = "/* comment \n comment */\n"; + simplecpp::OutputList outputList; + readfile(code, sizeof(code)-1, {}, &outputList); + ASSERT_EQUALS("", toString(outputList)); + } +} + static void stringify1() { const char code_c[] = "#include \"A.h\"\n" @@ -3282,31 +3775,31 @@ static void unicode() { { const char code[] = "\xFE\xFF\x00\x31\x00\x32"; - ASSERT_EQUALS("12", readfile(code, sizeof(code))); + ASSERT_EQUALS("12", readfile(code, sizeof(code)-1)); } { const char code[] = "\xFF\xFE\x31\x00\x32\x00"; - ASSERT_EQUALS("12", readfile(code, sizeof(code))); + ASSERT_EQUALS("12", readfile(code, sizeof(code)-1)); } { const char code[] = "\xFE\xFF\x00\x2f\x00\x2f\x00\x0a\x00\x31"; - ASSERT_EQUALS("//\n1", readfile(code, sizeof(code))); + ASSERT_EQUALS("//\n1", readfile(code, sizeof(code)-1)); } { const char code[] = "\xFF\xFE\x2f\x00\x2f\x00\x0a\x00\x31\x00"; - ASSERT_EQUALS("//\n1", readfile(code, sizeof(code))); + ASSERT_EQUALS("//\n1", readfile(code, sizeof(code)-1)); } { const char code[] = "\xFE\xFF\x00\x22\x00\x61\x00\x22"; - ASSERT_EQUALS("\"a\"", readfile(code, sizeof(code))); + ASSERT_EQUALS("\"a\"", readfile(code, sizeof(code)-1)); } { const char code[] = "\xFF\xFE\x22\x00\x61\x00\x22\x00"; - ASSERT_EQUALS("\"a\"", readfile(code, sizeof(code))); + ASSERT_EQUALS("\"a\"", readfile(code, sizeof(code)-1)); } { const char code[] = "\xff\xfe\x0d\x00\x0a\x00\x2f\x00\x2f\x00\x31\x00\x0d\x00\x0a\x00"; - ASSERT_EQUALS("\n//1", readfile(code, sizeof(code))); + ASSERT_EQUALS("\n//1", readfile(code, sizeof(code)-1)); } } @@ -3314,41 +3807,41 @@ static void unicode_invalid() { { const char code[] = "\xFF"; - ASSERT_EQUALS("", readfile(code, sizeof(code))); + ASSERT_EQUALS("", readfile(code, sizeof(code)-1)); } { const char code[] = "\xFE"; - ASSERT_EQUALS("", readfile(code, sizeof(code))); + ASSERT_EQUALS("", readfile(code, sizeof(code)-1)); } { - const char code[] = "\xFE\xFF\x31"; - ASSERT_EQUALS("", readfile(code, sizeof(code))); + const char code[] = "\xFE\xFF\x31\x00"; + ASSERT_EQUALS("", readfile(code, sizeof(code)-1)); } { - const char code[] = "\xFF\xFE\x31"; - ASSERT_EQUALS("1", readfile(code, sizeof(code))); + const char code[] = "\xFF\xFE\x31\x00"; + ASSERT_EQUALS("1", readfile(code, sizeof(code)-1)); } { const char code[] = "\xFE\xFF\x31\x32"; - ASSERT_EQUALS("", readfile(code, sizeof(code))); + ASSERT_EQUALS("", readfile(code, sizeof(code)-1)); } { const char code[] = "\xFF\xFE\x31\x32"; - ASSERT_EQUALS("", readfile(code, sizeof(code))); + ASSERT_EQUALS("", readfile(code, sizeof(code)-1)); } { - const char code[] = "\xFE\xFF\x00\x31\x00\x32\x33"; - ASSERT_EQUALS("", readfile(code, sizeof(code))); + const char code[] = "\xFE\xFF\x00\x31\x00\x32\x33\x00"; + ASSERT_EQUALS("", readfile(code, sizeof(code)-1)); } { - const char code[] = "\xFF\xFE\x31\x00\x32\x00\x33"; - ASSERT_EQUALS("123", readfile(code, sizeof(code))); + const char code[] = "\xFF\xFE\x31\x00\x32\x00\x33\x00"; + ASSERT_EQUALS("123", readfile(code, sizeof(code)-1)); } } static void warning() { - const char code[] = "#warning MSG\n1"; + const char code[] = "#warning MSG\n1\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("\n1", preprocess(code, &outputList)); ASSERT_EQUALS("file0,1,#warning,#warning MSG\n", toString(outputList)); @@ -3444,17 +3937,17 @@ static void preprocessSizeOf() { simplecpp::OutputList outputList; - ASSERT_EQUALS("", preprocess("#if 3 > sizeof", &outputList)); + ASSERT_EQUALS("", preprocess("#if 3 > sizeof\n", &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition, missing sizeof argument\n", toString(outputList)); outputList.clear(); - ASSERT_EQUALS("", preprocess("#if 3 > sizeof A", &outputList)); + ASSERT_EQUALS("", preprocess("#if 3 > sizeof A\n", &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition, missing sizeof argument\n", toString(outputList)); outputList.clear(); - ASSERT_EQUALS("", preprocess("#if 3 > sizeof(int", &outputList)); + ASSERT_EQUALS("", preprocess("#if 3 > sizeof(int\n", &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition, invalid sizeof expression\n", toString(outputList)); } @@ -3710,19 +4203,19 @@ static void tokenlist_api() // sized array + size { char input[] = "code"; // NOLINT(misc-const-correctness) - simplecpp::TokenList(input,sizeof(input),filenames,""); + simplecpp::TokenList(input,sizeof(input)-1,filenames,""); } { const char input[] = "code"; - simplecpp::TokenList(input,sizeof(input),filenames,""); + simplecpp::TokenList(input,sizeof(input)-1,filenames,""); } { unsigned char input[] = "code"; // NOLINT(misc-const-correctness) - simplecpp::TokenList(input,sizeof(input),filenames,""); + simplecpp::TokenList(input,sizeof(input)-1,filenames,""); } { const unsigned char input[] = "code"; - simplecpp::TokenList(input,sizeof(input),filenames,""); + simplecpp::TokenList(input,sizeof(input)-1,filenames,""); } #endif // !defined(__cpp_lib_string_view) && !defined(__cpp_lib_span) // pointer via View @@ -3742,11 +4235,11 @@ static void tokenlist_api() // sized array + size via View/std::span { char input[] = "code"; // NOLINT(misc-const-correctness) - simplecpp::TokenList({input,sizeof(input)},filenames,""); + simplecpp::TokenList({input,sizeof(input)-1},filenames,""); } { const char input[] = "code"; - simplecpp::TokenList({input,sizeof(input)},filenames,""); + simplecpp::TokenList({input,sizeof(input)-1},filenames,""); } // sized array { @@ -3864,6 +4357,27 @@ static void ifCond() ASSERT_EQUALS("0", it->E); ASSERT_EQUALS(0, it->result); } + { + const char code[] = "#ifdef NOTDEFINED\n" + "#endif\n" + "#ifndef NOTDEFINED\n" + "#endif\n"; + std::list ifCond; + ASSERT_EQUALS("", preprocess(code, &ifCond)); + ASSERT_EQUALS(2, ifCond.size()); + auto it = ifCond.cbegin(); + ASSERT_EQUALS(0, it->location.fileIndex); + ASSERT_EQUALS(1, it->location.line); + ASSERT_EQUALS(2, it->location.col); + ASSERT_EQUALS("0", it->E); + ASSERT_EQUALS(0, it->result); + ++it; + ASSERT_EQUALS(0, it->location.fileIndex); + ASSERT_EQUALS(3, it->location.line); + ASSERT_EQUALS(2, it->location.col); + ASSERT_EQUALS("1", it->E); + ASSERT_EQUALS(1, it->result); + } } static void macroUsage() @@ -3975,6 +4489,7 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(comment); TEST_CASE(comment_multiline); + TEST_CASE(keep_comments); TEST_CASE(constFold); @@ -4007,6 +4522,8 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(define23); // #40 TEST_CASE(define_invalid_1); TEST_CASE(define_invalid_2); + TEST_CASE(define_invalid_3); + TEST_CASE(define_invalid_4); TEST_CASE(define_define_1); TEST_CASE(define_define_2); TEST_CASE(define_define_3); @@ -4052,6 +4569,10 @@ static void runTests(int argc, char **argv, Input input) // UB: #ifdef as macro parameter TEST_CASE(define_ifdef); + TEST_CASE(if_invalid_1); + TEST_CASE(if_invalid_2); + TEST_CASE(if_invalid_3); + TEST_CASE(dollar); TEST_CASE(error1); @@ -4114,6 +4635,11 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(has_include_5); TEST_CASE(has_include_6); + TEST_CASE(define_has_include_invalid_1); + TEST_CASE(define_has_include_invalid_2); + TEST_CASE(define_has_include_invalid_3); + TEST_CASE(define_has_include_invalid_4); + TEST_CASE(strict_ansi_1); TEST_CASE(strict_ansi_2); TEST_CASE(strict_ansi_3); @@ -4135,6 +4661,8 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(ifLogical); TEST_CASE(ifSizeof); TEST_CASE(elif); + TEST_CASE(elifdef); + TEST_CASE(elifndef); TEST_CASE(ifif); TEST_CASE(ifoverflow); TEST_CASE(ifdiv0); @@ -4167,6 +4695,8 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(nullDirective2); TEST_CASE(nullDirective3); + TEST_CASE(lineDirective); + TEST_CASE(include1); TEST_CASE(include2); TEST_CASE(include3); @@ -4199,6 +4729,7 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(readfile_unhandled_chars); TEST_CASE(readfile_error); TEST_CASE(readfile_file_not_found); + TEST_CASE(readfile_no_eof_newline); TEST_CASE(stringify1);