FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

GitHub Viewer

/* * simplecpp - A simple and high-fidelity C/C++ preprocessor library * Copyright (C) 2016-2023 simplecpp team */ #if defined(_WIN32) || defined(__CYGWIN__) || defined(__MINGW32__) #define SIMPLECPP_WINDOWS #define NOMINMAX #endif #include "simplecpp.h" #include #include #include #include #include // IWYU pragma: keep #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if __cplusplus >= 201103L #ifdef SIMPLECPP_WINDOWS #include #endif #include #endif #include #include #ifdef SIMPLECPP_WINDOWS #include #undef ERROR #endif #if __cplusplus >= 201103L #define OVERRIDE override #define EXPLICIT explicit #else #define OVERRIDE #define EXPLICIT #endif #if (__cplusplus < 201103L) && !defined(__APPLE__) #define nullptr NULL #endif static bool isHex(const std::string &s) { return s.size()>2 && (s.compare(0,2,"0x")==0 || s.compare(0,2,"0X")==0); } static bool isOct(const std::string &s) { return s.size()>1 && (s[0]=='0') && (s[1] >= '0') && (s[1] < '8'); } // TODO: added an undercore since this conflicts with a function of the same name in utils.h from Cppcheck source when building Cppcheck with MSBuild static bool isStringLiteral_(const std::string &s) { return s.size() > 1 && (s[0]=='\"') && (*s.rbegin()=='\"'); } // TODO: added an undercore since this conflicts with a function of the same name in utils.h from Cppcheck source when building Cppcheck with MSBuild static bool isCharLiteral_(const std::string &s) { // char literal patterns can include 'a', '\t', '\000', '\xff', 'abcd', and maybe '' // This only checks for the surrounding '' but doesn't parse the content. return s.size() > 1 && (s[0]=='\'') && (*s.rbegin()=='\''); } static const simplecpp::TokenString DEFINE("define"); static const simplecpp::TokenString UNDEF("undef"); static const simplecpp::TokenString INCLUDE("include"); static const simplecpp::TokenString ERROR("error"); static const simplecpp::TokenString WARNING("warning"); static const simplecpp::TokenString IF("if"); static const simplecpp::TokenString IFDEF("ifdef"); 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 ENDIF("endif"); static const simplecpp::TokenString PRAGMA("pragma"); static const simplecpp::TokenString ONCE("once"); static const simplecpp::TokenString HAS_INCLUDE("__has_include"); template static std::string toString(T t) { // NOLINTNEXTLINE(misc-const-correctness) - false positive std::ostringstream ostr; ostr > std::hex; else if (oct) istr >> std::oct; istr >> ret; return ret; } static unsigned long long stringToULL(const std::string &s) { unsigned long long ret; const bool hex = isHex(s); const bool oct = isOct(s); std::istringstream istr(hex ? s.substr(2) : oct ? s.substr(1) : s); if (hex) istr >> std::hex; else if (oct) istr >> std::oct; istr >> ret; return ret; } static bool endsWith(const std::string &s, const std::string &e) { return (s.size() >= e.size()) && std::equal(e.rbegin(), e.rend(), s.rbegin()); } static bool sameline(const simplecpp::Token *tok1, const simplecpp::Token *tok2) { return tok1 && tok2 && tok1->location.sameline(tok2->location); } static bool isAlternativeBinaryOp(const simplecpp::Token *tok, const std::string &alt) { return (tok->name && tok->str() == alt && tok->previous && tok->next && (tok->previous->number || tok->previous->name || tok->previous->op == ')') && (tok->next->number || tok->next->name || tok->next->op == '(')); } static bool isAlternativeUnaryOp(const simplecpp::Token *tok, const std::string &alt) { return ((tok->name && tok->str() == alt) && (!tok->previous || tok->previous->op == '(') && (tok->next && (tok->next->name || tok->next->number))); } static std::string replaceAll(std::string s, const std::string& from, const std::string& to) { for (size_t pos = s.find(from); pos != std::string::npos; pos = s.find(from, pos + to.size())) s.replace(pos, from.size(), to); return s; } const std::string simplecpp::Location::emptyFileName; void simplecpp::Location::adjust(const std::string &str) { if (strpbrk(str.c_str(), "\r\n") == nullptr) { col += str.size(); return; } for (std::size_t i = 0U; i < str.size(); ++i) { col++; if (str[i] == '\n' || str[i] == '\r') { col = 1; line++; if (str[i] == '\r' && (i+1)previous) tok = tok->previous; for (; tok; tok = tok->next) { if (tok->previous) { std::cout previous) ? ' ' : '\n'); } std::cout str(); } std::cout next) { if (tok != this) { std::cout previous) ? ' ' : '\n'); } std::cout str(); } std::cout = 0x80) ? 0xff : ch16)); } // Handling of newlines.. if (ch == '\r') { ch = '\n'; int ch2 = get(); if (isUtf16) { const int c2 = get(); ch2 = makeUtf16Char(ch2, c2); } if (ch2 != '\n') ungetChar(); } return ch; } unsigned char peekChar() { unsigned char ch = static_cast(peek()); // For UTF-16 encoded files the BOM is 0xfeff/0xfffe. If the // character is non-ASCII character then replace it with 0xff if (isUtf16) { (void)get(); const unsigned char ch2 = static_cast(peek()); unget(); const int ch16 = makeUtf16Char(ch, ch2); ch = static_cast(((ch16 >= 0x80) ? 0xff : ch16)); } // Handling of newlines.. if (ch == '\r') ch = '\n'; return ch; } void ungetChar() { unget(); if (isUtf16) unget(); } protected: void init() { // initialize since we use peek() in getAndSkipBOM() isUtf16 = false; bom = getAndSkipBOM(); isUtf16 = (bom == 0xfeff || bom == 0xfffe); } private: inline int makeUtf16Char(const unsigned char ch, const unsigned char ch2) const { return (bom == 0xfeff) ? (chpush_back(e); } } simplecpp::TokenList::TokenList(const TokenList &other) : frontToken(nullptr), backToken(nullptr), files(other.files) { *this = other; } #if __cplusplus >= 201103L simplecpp::TokenList::TokenList(TokenList &&other) : frontToken(nullptr), backToken(nullptr), files(other.files) { *this = std::move(other); } #endif simplecpp::TokenList::~TokenList() { clear(); } simplecpp::TokenList &simplecpp::TokenList::operator=(const TokenList &other) { if (this != &other) { clear(); files = other.files; for (const Token *tok = other.cfront(); tok; tok = tok->next) push_back(new Token(*tok)); sizeOfType = other.sizeOfType; } return *this; } #if __cplusplus >= 201103L simplecpp::TokenList &simplecpp::TokenList::operator=(TokenList &&other) { if (this != &other) { clear(); frontToken = other.frontToken; other.frontToken = nullptr; backToken = other.backToken; other.backToken = nullptr; files = other.files; sizeOfType = std::move(other.sizeOfType); } return *this; } #endif void simplecpp::TokenList::clear() { backToken = nullptr; while (frontToken) { Token * const next = frontToken->next; delete frontToken; frontToken = next; } sizeOfType.clear(); } void simplecpp::TokenList::push_back(Token *tok) { if (!frontToken) frontToken = tok; else backToken->next = tok; tok->previous = backToken; backToken = tok; } void simplecpp::TokenList::dump() const { std::cout location.line < loc.line || tok->location.fileIndex != loc.fileIndex) { ret location; } while (tok->location.line > loc.line) { ret previous, tok)) ret str()); } return ret.str(); } static bool isNameChar(unsigned char ch) { return std::isalnum(ch) || ch == '_' || ch == '$'; } static std::string escapeString(const std::string &str) { std::ostringstream ostr; ostr name && isStringLiteralPrefix(cback()->str()) && ((cback()->location.col + cback()->str().size()) == location.col) && (cback()->location.line == location.line)) { prefix = cback()->str(); } // C++11 raw string literal if (ch == '\"' && !prefix.empty() && *cback()->str().rbegin() == 'R') { std::string delim; currentToken = ch; prefix.resize(prefix.size() - 1); ch = stream.readChar(); while (stream.good() && ch != '(' && ch != '\n') { delim += ch; ch = stream.readChar(); } if (!stream.good() || ch == '\n') { if (outputList) { Output err(files); err.type = Output::SYNTAX_ERROR; err.location = location; err.msg = "Invalid newline in raw string delimiter."; outputList->push_back(err); } return; } const std::string endOfRawString(')' + delim + currentToken); while (stream.good() && !(endsWith(currentToken, endOfRawString) && currentToken.size() > 1)) currentToken += stream.readChar(); if (!endsWith(currentToken, endOfRawString)) { if (outputList) { Output err(files); err.type = Output::SYNTAX_ERROR; err.location = location; err.msg = "Raw string missing terminating delimiter."; outputList->push_back(err); } return; } currentToken.erase(currentToken.size() - endOfRawString.size(), endOfRawString.size() - 1U); currentToken = escapeString(currentToken); currentToken.insert(0, prefix); back()->setstr(currentToken); location.adjust(currentToken); if (currentToken.find_first_of("\r\n") == std::string::npos) location.col += 2 + 2 * delim.size(); else location.col += 1 + delim.size(); continue; } currentToken = readUntil(stream,location,ch,ch,outputList); if (currentToken.size() < 2U) // Error is reported by readUntil() return; std::string s = currentToken; std::string::size_type pos; int newlines = 0; while ((pos = s.find_first_of("\r\n")) != std::string::npos) { s.erase(pos,1); newlines++; } if (prefix.empty()) push_back(new Token(s, location, std::isspace(stream.peekChar()))); // push string without newlines else back()->setstr(prefix + s); if (newlines > 0 ) { const Token * const llTok = lastLineTok(); if (llTok && llTok->op == '#' && llTok->next && (llTok->next->str() == "define" || llTok->next->str() == "pragma") && llTok->next->next) { multiline += newlines; location.adjust(s); continue; } } location.adjust(currentToken); continue; } else { currentToken += ch; } if (*currentToken.begin() == '= 0 && start) { if (start->op == ')') ++indentlevel; else if (start->op == '(') --indentlevel; else if (start->isOneOf(";{}")) break; start = start->previous; } if (indentlevel == -1 && start) { const Token * const ftok = start; bool isFuncDecl = ftok->name; while (isFuncDecl) { if (!start->name && start->str() != "::" && start->op != '*' && start->op != '&') isFuncDecl = false; if (!start->previous) break; if (start->previous->isOneOf(";{}:")) break; start = start->previous; } isFuncDecl &= start != ftok && start->name; if (isFuncDecl) { // TODO: we could loop through the parameters here and check if they are correct. continue; } } } tok->setstr(tok->str() + "="); deleteToken(tok->next); } else if ((tok->op == '|' || tok->op == '&') && tok->op == tok->next->op) { tok->setstr(tok->str() + tok->next->str()); deleteToken(tok->next); } else if (tok->op == ':' && tok->next->op == ':') { tok->setstr(tok->str() + tok->next->str()); deleteToken(tok->next); } else if (tok->op == '-' && tok->next->op == '>') { tok->setstr(tok->str() + tok->next->str()); deleteToken(tok->next); } else if ((tok->op == '') && tok->op == tok->next->op) { tok->setstr(tok->str() + tok->next->str()); deleteToken(tok->next); if (tok->next && tok->next->op == '=' && tok->next->next && tok->next->next->op != '=') { tok->setstr(tok->str() + tok->next->str()); deleteToken(tok->next); } } else if ((tok->op == '+' || tok->op == '-') && tok->op == tok->next->op) { if (tok->location.col + 1U != tok->next->location.col) continue; if (tok->previous && tok->previous->number) continue; if (tok->next->next && tok->next->next->number) continue; tok->setstr(tok->str() + tok->next->str()); deleteToken(tok->next); } } } static const std::string COMPL("compl"); static const std::string NOT("not"); void simplecpp::TokenList::constFoldUnaryNotPosNeg(simplecpp::Token *tok) { for (; tok && tok->op != ')'; tok = tok->next) { // "not" might be ! if (isAlternativeUnaryOp(tok, NOT)) tok->op = '!'; // "compl" might be ~ else if (isAlternativeUnaryOp(tok, COMPL)) tok->op = '~'; if (tok->op == '!' && tok->next && tok->next->number) { tok->setstr(tok->next->str() == "0" ? "1" : "0"); deleteToken(tok->next); } else if (tok->op == '~' && tok->next && tok->next->number) { tok->setstr(toString(~stringToLL(tok->next->str()))); deleteToken(tok->next); } else { if (tok->previous && (tok->previous->number || tok->previous->name)) continue; if (!tok->next || !tok->next->number) continue; switch (tok->op) { case '+': tok->setstr(tok->next->str()); deleteToken(tok->next); break; case '-': tok->setstr(tok->op + tok->next->str()); deleteToken(tok->next); break; } } } } void simplecpp::TokenList::constFoldMulDivRem(Token *tok) { for (; tok && tok->op != ')'; tok = tok->next) { if (!tok->previous || !tok->previous->number) continue; if (!tok->next || !tok->next->number) continue; long long result; if (tok->op == '*') result = (stringToLL(tok->previous->str()) * stringToLL(tok->next->str())); else if (tok->op == '/' || tok->op == '%') { const long long rhs = stringToLL(tok->next->str()); if (rhs == 0) throw std::overflow_error("division/modulo by zero"); const long long lhs = stringToLL(tok->previous->str()); if (rhs == -1 && lhs == std::numeric_limits::min()) throw std::overflow_error("division overflow"); if (tok->op == '/') result = (lhs / rhs); else result = (lhs % rhs); } else continue; tok = tok->previous; tok->setstr(toString(result)); deleteToken(tok->next); deleteToken(tok->next); } } void simplecpp::TokenList::constFoldAddSub(Token *tok) { for (; tok && tok->op != ')'; tok = tok->next) { if (!tok->previous || !tok->previous->number) continue; if (!tok->next || !tok->next->number) continue; long long result; if (tok->op == '+') result = stringToLL(tok->previous->str()) + stringToLL(tok->next->str()); else if (tok->op == '-') result = stringToLL(tok->previous->str()) - stringToLL(tok->next->str()); else continue; tok = tok->previous; tok->setstr(toString(result)); deleteToken(tok->next); deleteToken(tok->next); } } void simplecpp::TokenList::constFoldShift(Token *tok) { for (; tok && tok->op != ')'; tok = tok->next) { if (!tok->previous || !tok->previous->number) continue; if (!tok->next || !tok->next->number) continue; long long result; if (tok->str() == ">") result = stringToLL(tok->previous->str()) >> stringToLL(tok->next->str()); else continue; tok = tok->previous; tok->setstr(toString(result)); deleteToken(tok->next); deleteToken(tok->next); } } static const std::string NOTEQ("not_eq"); void simplecpp::TokenList::constFoldComparison(Token *tok) { for (; tok && tok->op != ')'; tok = tok->next) { if (isAlternativeBinaryOp(tok,NOTEQ)) tok->setstr("!="); if (!tok->startsWithOneOf("=!")) continue; if (!tok->previous || !tok->previous->number) continue; if (!tok->next || !tok->next->number) continue; int result; if (tok->str() == "==") result = (stringToLL(tok->previous->str()) == stringToLL(tok->next->str())); else if (tok->str() == "!=") result = (stringToLL(tok->previous->str()) != stringToLL(tok->next->str())); else if (tok->str() == ">") result = (stringToLL(tok->previous->str()) > stringToLL(tok->next->str())); else if (tok->str() == ">=") result = (stringToLL(tok->previous->str()) >= stringToLL(tok->next->str())); else if (tok->str() == ""; const bool canBeConcatenatedStringOrChar = isStringLiteral_(A->str()) || isCharLiteral_(A->str()); if (!A->name && !A->number && A->op != ',' && !A->str().empty() && !canBeConcatenatedWithEqual && !canBeConcatenatedStringOrChar) throw invalidHashHash::unexpectedToken(tok->location, name(), A); Token * const B = tok->next->next; if (!B->name && !B->number && B->op && !B->isOneOf("#=")) throw invalidHashHash::unexpectedToken(tok->location, name(), B); if ((canBeConcatenatedWithEqual && B->op != '=') || (!canBeConcatenatedWithEqual && B->op == '=')) throw invalidHashHash::cannotCombine(tok->location, name(), A, B); // Superficial check; more in-depth would in theory be possible _after_ expandArg if (canBeConcatenatedStringOrChar && (B->number || !B->name)) throw invalidHashHash::cannotCombine(tok->location, name(), A, B); TokenList tokensB(files); const Token *nextTok = B->next; if (canBeConcatenatedStringOrChar) { // It seems clearer to handle this case separately even though the code is similar-ish, but we don't want to merge here. // TODO The question is whether the ## or varargs may still apply, and how to provoke? if (expandArg(&tokensB, B, parametertokens)) { for (Token *b = tokensB.front(); b; b = b->next) b->location = loc; } else { tokensB.push_back(new Token(*B)); tokensB.back()->location = loc; } output->takeTokens(tokensB); } else { std::string strAB; const bool varargs = variadic && !args.empty() && B->str() == args[args.size()-1U]; if (expandArg(&tokensB, B, parametertokens)) { if (tokensB.empty()) strAB = A->str(); else if (varargs && A->op == ',') { strAB = ","; } else { strAB = A->str() + tokensB.cfront()->str(); tokensB.deleteToken(tokensB.front()); } } else { strAB = A->str() + B->str(); } // producing universal character is undefined behavior if (A->previous && A->previous->str() == "\\") { if (strAB[0] == 'u' && strAB.size() == 5) throw invalidHashHash::universalCharacterUB(tok->location, name(), A, strAB); if (strAB[0] == 'U' && strAB.size() == 9) throw invalidHashHash::universalCharacterUB(tok->location, name(), A, strAB); } if (varargs && tokensB.empty() && tok->previous->str() == ",") output->deleteToken(A); else if (strAB != "," && macros.find(strAB) == macros.end()) { A->setstr(strAB); for (Token *b = tokensB.front(); b; b = b->next) b->location = loc; output->takeTokens(tokensB); } else if (sameline(B, nextTok) && sameline(B, nextTok->next) && nextTok->op == '#' && nextTok->next->op == '#') { TokenList output2(files); output2.push_back(new Token(strAB, tok->location)); nextTok = expandHashHash(&output2, loc, nextTok, macros, expandedmacros, parametertokens); output->deleteToken(A); output->takeTokens(output2); } else { output->deleteToken(A); TokenList tokens(files); tokens.push_back(new Token(strAB, tok->location)); // for function like macros, push the (...) if (tokensB.empty() && sameline(B,B->next) && B->next->op=='(') { const MacroMap::const_iterator it = macros.find(strAB); if (it != macros.end() && expandedmacros.find(strAB) == expandedmacros.end() && it->second.functionLike()) { const Token * const tok2 = appendTokens(&tokens, loc, B->next, macros, expandedmacros, parametertokens); if (tok2) nextTok = tok2->next; } } expandToken(output, loc, tokens.cfront(), macros, expandedmacros, parametertokens); for (Token *b = tokensB.front(); b; b = b->next) b->location = loc; output->takeTokens(tokensB); } } return nextTok; } static bool isReplaced(const std::set &expandedmacros) { // return true if size > 1 std::set::const_iterator it = expandedmacros.begin(); if (it == expandedmacros.end()) return false; ++it; return (it != expandedmacros.end()); } /** name token in definition */ const Token *nameTokDef; /** arguments for macro */ std::vector args; /** first token in replacement string */ const Token *valueToken; /** token after replacement string */ const Token *endToken; /** files */ std::vector &files; /** this is used for -D where the definition is not seen anywhere in code */ TokenList tokenListDefine; /** usage of this macro */ mutable std::list usageList; /** is macro variadic? */ bool variadic; /** was the value of this macro actually defined in the code? */ bool valueDefinedInCode_; }; } namespace simplecpp { #ifdef __CYGWIN__ bool startsWith(const std::string &str, const std::string &s) { return (str.size() >= s.size() && str.compare(0, s.size(), s) == 0); } std::string convertCygwinToWindowsPath(const std::string &cygwinPath) { std::string windowsPath; std::string::size_type pos = 0; if (cygwinPath.size() >= 11 && startsWith(cygwinPath, "/cygdrive/")) { const unsigned char driveLetter = cygwinPath[10]; if (std::isalpha(driveLetter)) { if (cygwinPath.size() == 11) { windowsPath = toupper(driveLetter); windowsPath += ":\\"; // volume root directory pos = 11; } else if (cygwinPath[11] == '/') { windowsPath = toupper(driveLetter); windowsPath += ":"; pos = 11; } } } for (; pos < cygwinPath.size(); ++pos) { unsigned char c = cygwinPath[pos]; if (c == '/') c = '\\'; windowsPath += c; } return windowsPath; } #endif } #ifdef SIMPLECPP_WINDOWS #if __cplusplus >= 201103L using MyMutex = std::mutex; template using MyLock = std::lock_guard; #else class MyMutex { public: MyMutex() { InitializeCriticalSection(&m_criticalSection); } ~MyMutex() { DeleteCriticalSection(&m_criticalSection); } CRITICAL_SECTION* lock() { return &m_criticalSection; } private: CRITICAL_SECTION m_criticalSection; }; template class MyLock { public: explicit MyLock(T& m) : m_mutex(m) { EnterCriticalSection(m_mutex.lock()); } ~MyLock() { LeaveCriticalSection(m_mutex.lock()); } private: MyLock& operator=(const MyLock&); MyLock(const MyLock&); T& m_mutex; }; #endif class RealFileNameMap { public: RealFileNameMap() {} bool getCacheEntry(const std::string& path, std::string& returnPath) { MyLock lock(m_mutex); const std::map::iterator it = m_fileMap.find(path); if (it != m_fileMap.end()) { returnPath = it->second; return true; } return false; } void addToCache(const std::string& path, const std::string& actualPath) { MyLock lock(m_mutex); m_fileMap[path] = actualPath; } private: std::map m_fileMap; MyMutex m_mutex; }; static RealFileNameMap realFileNameMap; static bool realFileName(const std::string &f, std::string &result) { // are there alpha characters in last subpath? bool alpha = false; for (std::string::size_type pos = 1; pos = 3 && path[0] > 0 && std::isalpha(path[0]) && path[1] == ':' && (path[2] == '\\' || path[2] == '/')) return true; return path.length() > 1U && (path[0] == '/' || path[0] == '\\'); } #else #define realFilename(f) f static bool isAbsolutePath(const std::string &path) { return path.length() > 1U && path[0] == '/'; } #endif namespace simplecpp { /** * perform path simplifications for . and .. */ std::string simplifyPath(std::string path) { if (path.empty()) return path; std::string::size_type pos; // replace backslash separators std::replace(path.begin(), path.end(), '\\', '/'); const bool unc(path.compare(0,2,"//") == 0); // replace "//" with "/" pos = 0; while ((pos = path.find("//",pos)) != std::string::npos) { path.erase(pos,1); } // remove "./" pos = 0; while ((pos = path.find("./",pos)) != std::string::npos) { if (pos == 0 || path[pos - 1U] == '/') path.erase(pos,2); else pos += 2; } // remove trailing dot if path ends with "/." if (endsWith(path,"/.")) path.erase(path.size()-1); // simplify ".." pos = 1; // don't simplify ".." if path starts with that while ((pos = path.find("/..", pos)) != std::string::npos) { // not end of path, then string must be "/../" if (pos + 3 < path.size() && path[pos + 3] != '/') { ++pos; continue; } // get previous subpath std::string::size_type pos1 = path.rfind('/', pos - 1U); if (pos1 == std::string::npos) { pos1 = 0; } else { pos1 += 1U; } const std::string previousSubPath = path.substr(pos1, pos - pos1); if (previousSubPath == "..") { // don't simplify ++pos; } else { // remove previous subpath and ".." path.erase(pos1, pos - pos1 + 4); if (path.empty()) path = "."; // update pos pos = (pos1 == 0) ? 1 : (pos1 - 1); } } // Remove trailing '/'? //if (path.size() > 1 && endsWith(path, "/")) // path.erase(path.size()-1); if (unc) path = '/' + path; // cppcheck-suppress duplicateExpressionTernary - platform-dependent implementation return strpbrk(path.c_str(), "*?") == nullptr ? realFilename(path) : path; } } /** Evaluate sizeof(type) */ static void simplifySizeof(simplecpp::TokenList &expr, const std::map &sizeOfType) { for (simplecpp::Token *tok = expr.front(); tok; tok = tok->next) { if (tok->str() != "sizeof") continue; simplecpp::Token *tok1 = tok->next; if (!tok1) { throw std::runtime_error("missing sizeof argument"); } simplecpp::Token *tok2 = tok1->next; if (!tok2) { throw std::runtime_error("missing sizeof argument"); } if (tok1->op == '(') { tok1 = tok1->next; while (tok2->op != ')') { tok2 = tok2->next; if (!tok2) { throw std::runtime_error("invalid sizeof expression"); } } } std::string type; for (simplecpp::Token *typeToken = tok1; typeToken != tok2; typeToken = typeToken->next) { if ((typeToken->str() == "unsigned" || typeToken->str() == "signed") && typeToken->next->name) continue; if (typeToken->str() == "*" && type.find('*') != std::string::npos) continue; if (!type.empty()) type += ' '; type += typeToken->str(); } const std::map::const_iterator it = sizeOfType.find(type); if (it != sizeOfType.end()) tok->setstr(toString(it->second)); else continue; tok2 = tok2->next; while (tok->next != tok2) expr.deleteToken(tok->next); } } /** Evaluate __has_include(file) */ static bool isCpp17OrLater(const simplecpp::DUI &dui) { const std::string std_ver = simplecpp::getCppStdString(dui.std); return !std_ver.empty() && (std_ver >= "201703L"); } static std::string openHeader(std::ifstream &f, const simplecpp::DUI &dui, const std::string &sourcefile, const std::string &header, bool systemheader); static void simplifyHasInclude(simplecpp::TokenList &expr, const simplecpp::DUI &dui) { if (!isCpp17OrLater(dui)) return; for (simplecpp::Token *tok = expr.front(); tok; tok = tok->next) { if (tok->str() != HAS_INCLUDE) continue; simplecpp::Token *tok1 = tok->next; if (!tok1) { throw std::runtime_error("missing __has_include argument"); } simplecpp::Token *tok2 = tok1->next; if (!tok2) { throw std::runtime_error("missing __has_include argument"); } if (tok1->op == '(') { tok1 = tok1->next; while (tok2->op != ')') { tok2 = tok2->next; if (!tok2) { throw std::runtime_error("invalid __has_include expression"); } } } const std::string &sourcefile = tok->location.file(); const bool systemheader = (tok1 && tok1->op == '') { tok3 = tok3->next; if (!tok3) { throw std::runtime_error("invalid __has_include expression"); } } for (simplecpp::Token *headerToken = tok1->next; headerToken != tok3; headerToken = headerToken->next) header += headerToken->str(); // cppcheck-suppress selfAssignment - platform-dependent implementation header = realFilename(header); } else { header = realFilename(tok1->str().substr(1U, tok1->str().size() - 2U)); } std::ifstream f; const std::string header2 = openHeader(f,dui,sourcefile,header,systemheader); tok->setstr(header2.empty() ? "0" : "1"); tok2 = tok2->next; while (tok->next != tok2) expr.deleteToken(tok->next); } } static const char * const altopData[] = {"and","or","bitand","bitor","compl","not","not_eq","xor"}; static const std::set altop(&altopData[0], &altopData[8]); static void simplifyName(simplecpp::TokenList &expr) { for (simplecpp::Token *tok = expr.front(); tok; tok = tok->next) { if (tok->name) { if (altop.find(tok->str()) != altop.end()) { bool alt; if (tok->str() == "not" || tok->str() == "compl") { alt = isAlternativeUnaryOp(tok,tok->str()); } else { alt = isAlternativeBinaryOp(tok,tok->str()); } if (alt) continue; } tok->setstr("0"); } } } /* * Reads at least minlen and at most maxlen digits (inc. prefix) in base base * from s starting at position pos and converts them to a * unsigned long long value, updating pos to point to the first * unused element of s. * Returns ULLONG_MAX if the result is not representable and * throws if the above requirements were not possible to satisfy. */ static unsigned long long stringToULLbounded( const std::string& s, std::size_t& pos, int base = 0, std::ptrdiff_t minlen = 1, std::size_t maxlen = std::string::npos ) { const std::string sub = s.substr(pos, maxlen); const char * const start = sub.c_str(); char* end; const unsigned long long value = std::strtoull(start, &end, base); pos += end - start; if (end - start < minlen) throw std::runtime_error("expected digit"); return value; } /* Converts character literal (including prefix, but not ud-suffix) * to long long value. * * Assumes ASCII-compatible single-byte encoded str for narrow literals * and UTF-8 otherwise. * * For target assumes * - execution character set encoding matching str * - UTF-32 execution wide-character set encoding * - requirements for __STDC_UTF_16__, __STDC_UTF_32__ and __STDC_ISO_10646__ satisfied * - char16_t is 16bit wide * - char32_t is 32bit wide * - wchar_t is 32bit wide and unsigned * - matching char signedness to host * - matching sizeof(int) to host * * For host assumes * - ASCII-compatible execution character set * * For host and target assumes * - CHAR_BIT == 8 * - two's complement * * Implements multi-character narrow literals according to GCC's behavior, * except multi code unit universal character names are not supported. * Multi-character wide literals are not supported. * Limited support of universal character names for non-UTF-8 execution character set encodings. */ long long simplecpp::characterLiteralToLL(const std::string& str) { // default is wide/utf32 bool narrow = false; bool utf8 = false; bool utf16 = false; std::size_t pos; if (!str.empty() && str[0] == '\'') { narrow = true; pos = 1; } else if (str.size() >= 2 && str[0] == 'u' && str[1] == '\'') { utf16 = true; pos = 2; } else if (str.size() >= 3 && str[0] == 'u' && str[1] == '8' && str[2] == '\'') { utf8 = true; pos = 3; } else if (str.size() >= 2 && (str[0] == 'L' || str[0] == 'U') && str[1] == '\'') { pos = 2; } else throw std::runtime_error("expected a character literal"); unsigned long long multivalue = 0; std::size_t nbytes = 0; while (pos + 1 < str.size()) { if (str[pos] == '\'' || str[pos] == '\n') throw std::runtime_error("raw single quotes and newlines not allowed in character literals"); if (nbytes >= 1 && !narrow) throw std::runtime_error("multiple characters only supported in narrow character literals"); unsigned long long value; if (str[pos] == '\\') { pos++; const char escape = str[pos++]; if (pos >= str.size()) throw std::runtime_error("unexpected end of character literal"); switch (escape) { // obscure GCC extensions case '%': case '(': case '[': case '{': // standard escape sequences case '\'': case '"': case '?': case '\\': value = static_cast(escape); break; case 'a': value = static_cast('\a'); break; case 'b': value = static_cast('\b'); break; case 'f': value = static_cast('\f'); break; case 'n': value = static_cast('\n'); break; case 'r': value = static_cast('\r'); break; case 't': value = static_cast('\t'); break; case 'v': value = static_cast('\v'); break; // GCC extension for ESC character case 'e': case 'E': value = static_cast('\x1b'); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': // octal escape sequences consist of 1 to 3 digits value = stringToULLbounded(str, --pos, 8, 1, 3); break; case 'x': // hexadecimal escape sequences consist of at least 1 digit value = stringToULLbounded(str, pos, 16); break; case 'u': case 'U': { // universal character names have exactly 4 or 8 digits const std::size_t ndigits = (escape == 'u' ? 4 : 8); value = stringToULLbounded(str, pos, 16, ndigits, ndigits); // UTF-8 encodes code points above 0x7f in multiple code units // code points above 0x10ffff are not allowed if (((narrow || utf8) && value > 0x7f) || (utf16 && value > 0xffff) || value > 0x10ffff) throw std::runtime_error("code point too large"); if (value >= 0xd800 && value = 0x80) { // Assuming this is a UTF-8 encoded code point. // This decoder may not completely validate the input. // Noncharacters are neither rejected nor replaced. int additional_bytes; if (value >= 0xf5) // higher values would result in code points above 0x10ffff throw std::runtime_error("assumed UTF-8 encoded source, but sequence is invalid"); if (value >= 0xf0) additional_bytes = 3; else if (value >= 0xe0) additional_bytes = 2; else if (value >= 0xc2) // 0xc0 and 0xc1 are always overlong 2-bytes encodings additional_bytes = 1; else throw std::runtime_error("assumed UTF-8 encoded source, but sequence is invalid"); value &= (1 = str.size()) throw std::runtime_error("assumed UTF-8 encoded source, but character literal ends unexpectedly"); const unsigned char c = str[pos++]; if (((c >> 6) != 2) // ensure c has form 0xb10xxxxxx || (!value && additional_bytes == 1 && c < 0xa0) // overlong 3-bytes encoding || (!value && additional_bytes == 2 && c < 0x90)) // overlong 4-bytes encoding throw std::runtime_error("assumed UTF-8 encoded source, but sequence is invalid"); value = (value 0xffff) || value > 0x10ffff) throw std::runtime_error("code point too large"); } } if (((narrow || utf8) && value > std::numeric_limits::max()) || (utf16 && value >> 16) || value >> 32) throw std::runtime_error("numeric escape sequence too large"); multivalue setstr(toString(simplecpp::characterLiteralToLL(tok->str()))); } } static void simplifyComments(simplecpp::TokenList &expr) { for (simplecpp::Token *tok = expr.front(); tok;) { simplecpp::Token * const d = tok; tok = tok->next; if (d->comment) expr.deleteToken(d); } } static long long evaluate(simplecpp::TokenList &expr, const simplecpp::DUI &dui, const std::map &sizeOfType) { simplifyComments(expr); simplifySizeof(expr, sizeOfType); simplifyHasInclude(expr, dui); simplifyName(expr); simplifyNumbers(expr); expr.constFold(); // TODO: handle invalid expressions return expr.cfront() && expr.cfront() == expr.cback() && expr.cfront()->number ? stringToLL(expr.cfront()->str()) : 0LL; } static const simplecpp::Token *gotoNextLine(const simplecpp::Token *tok) { const unsigned int line = tok->location.line; const unsigned int file = tok->location.fileIndex; while (tok && tok->location.line == line && tok->location.fileIndex == file) tok = tok->next; return tok; } #ifdef SIMPLECPP_WINDOWS class NonExistingFilesCache { public: NonExistingFilesCache() {} bool contains(const std::string& path) { MyLock lock(m_mutex); return (m_pathSet.find(path) != m_pathSet.end()); } void add(const std::string& path) { MyLock lock(m_mutex); m_pathSet.insert(path); } void clear() { MyLock lock(m_mutex); m_pathSet.clear(); } private: std::set m_pathSet; MyMutex m_mutex; }; static NonExistingFilesCache nonExistingFilesCache; #endif static std::string openHeader(std::ifstream &f, const std::string &path) { std::string simplePath = simplecpp::simplifyPath(path); #ifdef SIMPLECPP_WINDOWS 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; #ifdef SIMPLECPP_WINDOWS nonExistingFilesCache.add(simplePath); #endif return ""; } static std::string getRelativeFileName(const std::string &sourcefile, const std::string &header) { if (sourcefile.find_first_of("\\/") != std::string::npos) return simplecpp::simplifyPath(sourcefile.substr(0, sourcefile.find_last_of("\\/") + 1U) + header); return simplecpp::simplifyPath(header); } static std::string openHeaderRelative(std::ifstream &f, const std::string &sourcefile, const std::string &header) { return openHeader(f, getRelativeFileName(sourcefile, header)); } static std::string getIncludePathFileName(const std::string &includePath, const std::string &header) { std::string path = includePath; if (!path.empty() && path[path.size()-1U]!='/' && path[path.size()-1U]!='\\') path += '/'; return path + header; } static std::string openHeaderIncludePath(std::ifstream &f, const simplecpp::DUI &dui, const std::string &header) { for (std::list::const_iterator it = dui.includePaths.begin(); it != dui.includePaths.end(); ++it) { std::string simplePath = openHeader(f, getIncludePathFileName(*it, header)); if (!simplePath.empty()) return simplePath; } return ""; } static std::string openHeader(std::ifstream &f, const simplecpp::DUI &dui, const std::string &sourcefile, const std::string &header, bool systemheader) { if (isAbsolutePath(header)) return openHeader(f, header); std::string ret; if (systemheader) { ret = openHeaderIncludePath(f, dui, header); return ret; } ret = openHeaderRelative(f, sourcefile, header); if (ret.empty()) return openHeaderIncludePath(f, dui, header); return ret; } static std::string getFileName(const std::map &filedata, const std::string &sourcefile, const std::string &header, const simplecpp::DUI &dui, bool systemheader) { if (filedata.empty()) { return ""; } if (isAbsolutePath(header)) { return (filedata.find(header) != filedata.end()) ? simplecpp::simplifyPath(header) : ""; } if (!systemheader) { const std::string relativeFilename = getRelativeFileName(sourcefile, header); if (filedata.find(relativeFilename) != filedata.end()) return relativeFilename; } for (std::list::const_iterator it = dui.includePaths.begin(); it != dui.includePaths.end(); ++it) { std::string s = simplecpp::simplifyPath(getIncludePathFileName(*it, header)); if (filedata.find(s) != filedata.end()) return s; } if (systemheader && filedata.find(header) != filedata.end()) return header; return ""; } static bool hasFile(const std::map &filedata, const std::string &sourcefile, const std::string &header, const simplecpp::DUI &dui, bool systemheader) { return !getFileName(filedata, sourcefile, header, dui, systemheader).empty(); } std::map simplecpp::load(const simplecpp::TokenList &rawtokens, std::vector &filenames, const simplecpp::DUI &dui, simplecpp::OutputList *outputList) { #ifdef SIMPLECPP_WINDOWS if (dui.clearIncludeCache) nonExistingFilesCache.clear(); #endif std::map ret; std::list filelist; // -include files for (std::list::const_iterator it = dui.includes.begin(); it != dui.includes.end(); ++it) { const std::string &filename = realFilename(*it); 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); } continue; } fin.close(); TokenList *tokenlist = new TokenList(filename, filenames, outputList); if (!tokenlist->front()) { delete tokenlist; continue; } if (dui.removeComments) tokenlist->removeComments(); ret[filename] = tokenlist; filelist.push_back(tokenlist->front()); } for (const Token *rawtok = rawtokens.cfront(); rawtok || !filelist.empty(); rawtok = rawtok ? rawtok->next : nullptr) { if (rawtok == nullptr) { rawtok = filelist.back(); filelist.pop_back(); } if (rawtok->op != '#' || sameline(rawtok->previousSkipComments(), rawtok)) continue; rawtok = rawtok->nextSkipComments(); if (!rawtok || rawtok->str() != INCLUDE) continue; const std::string &sourcefile = rawtok->location.file(); const Token * const htok = rawtok->nextSkipComments(); if (!sameline(rawtok, htok)) continue; const bool systemheader = (htok->str()[0] == '') { TokenString hdr; // TODO: Sometimes spaces must be added in the string // Somehow preprocessToken etc must be told that the location should be source location not destination location for (const Token *tok = inc2.cfront(); tok; tok = tok->next) { hdr += tok->str(); } inc2.clear(); inc2.push_back(new Token(hdr, inc1.cfront()->location)); inc2.front()->op = '') header += tok->str(); // cppcheck-suppress selfAssignment - platform-dependent implementation header = realFilename(header); if (tok && tok->op == '>') closingAngularBracket = true; } else { header = realFilename(tok->str().substr(1U, tok->str().size() - 2U)); closingAngularBracket = true; } std::ifstream f; const std::string header2 = openHeader(f,dui,sourcefile,header,systemheader); expr.push_back(new Token(header2.empty() ? "0" : "1", tok->location)); } if (par) tok = tok ? tok->next : nullptr; if (!tok || !sameline(rawtok,tok) || (par && tok->op != ')') || (!closingAngularBracket)) { if (outputList) { Output out(rawtok->location.files); out.type = Output::SYNTAX_ERROR; out.location = rawtok->location; out.msg = "failed to evaluate " + std::string(rawtok->str() == IF ? "#if" : "#elif") + " condition"; outputList->push_back(out); } output.clear(); return; } continue; } maybeUsedMacros[rawtok->next->str()].push_back(rawtok->next->location); const Token *tmp = tok; if (!preprocessToken(expr, &tmp, macros, files, outputList)) { output.clear(); return; } if (!tmp) break; tok = tmp->previous; } try { if (ifCond) { std::string E; for (const simplecpp::Token *tok = expr.cfront(); tok; tok = tok->next) E += (E.empty() ? "" : " ") + tok->str(); const long long result = evaluate(expr, dui, sizeOfType); conditionIsTrue = (result != 0); ifCond->push_back(IfCond(rawtok->location, E, result)); } else { const long long result = evaluate(expr, dui, sizeOfType); conditionIsTrue = (result != 0); } } catch (const std::exception &e) { if (outputList) { Output out(rawtok->location.files); out.type = Output::SYNTAX_ERROR; out.location = rawtok->location; out.msg = "failed to evaluate " + std::string(rawtok->str() == IF ? "#if" : "#elif") + " condition"; if (e.what() && *e.what()) out.msg += std::string(", ") + e.what(); outputList->push_back(out); } output.clear(); return; } } if (rawtok->str() != ELIF) { // push a new ifstate.. if (ifstates.top() != True) ifstates.push(AlwaysFalse); else ifstates.push(conditionIsTrue ? True : ElseIsTrue); } else if (ifstates.top() == True) { ifstates.top() = AlwaysFalse; } else if (ifstates.top() == ElseIsTrue && conditionIsTrue) { ifstates.top() = True; } } else if (rawtok->str() == ELSE) { ifstates.top() = (ifstates.top() == ElseIsTrue) ? True : AlwaysFalse; } else if (rawtok->str() == ENDIF) { ifstates.pop(); } else if (rawtok->str() == UNDEF) { if (ifstates.top() == True) { const Token *tok = rawtok->next; while (sameline(rawtok,tok) && tok->comment) tok = tok->next; if (sameline(rawtok, tok)) macros.erase(tok->str()); } } else if (ifstates.top() == True && rawtok->str() == PRAGMA && rawtok->next && rawtok->next->str() == ONCE && sameline(rawtok,rawtok->next)) { pragmaOnce.insert(rawtok->location.file()); } rawtok = gotoNextLine(rawtok); continue; } if (ifstates.top() != True) { // drop code rawtok = gotoNextLine(rawtok); continue; } bool hash=false, hashhash=false; if (rawtok->op == '#' && sameline(rawtok,rawtok->next)) { if (rawtok->next->op != '#') { hash = true; rawtok = rawtok->next; // skip '#' } else if (sameline(rawtok,rawtok->next->next)) { hashhash = true; rawtok = rawtok->next->next; // skip '#' '#' } } const Location loc(rawtok->location); TokenList tokens(files); if (!preprocessToken(tokens, &rawtok, macros, files, outputList)) { output.clear(); return; } if (hash || hashhash) { std::string s; for (const Token *hashtok = tokens.cfront(); hashtok; hashtok = hashtok->next) s += hashtok->str(); if (hash) output.push_back(new Token('\"' + s + '\"', loc)); else if (output.back()) output.back()->setstr(output.cback()->str() + s); else output.push_back(new Token(s, loc)); } else { output.takeTokens(tokens); } } if (macroUsage) { for (simplecpp::MacroMap::const_iterator macroIt = macros.begin(); macroIt != macros.end(); ++macroIt) { const Macro ¯o = macroIt->second; std::list usage = macro.usage(); const std::list& temp = maybeUsedMacros[macro.name()]; usage.insert(usage.end(), temp.begin(), temp.end()); for (std::list::const_iterator usageIt = usage.begin(); usageIt != usage.end(); ++usageIt) { MacroUsage mu(usageIt->files, macro.valueDefinedInCode()); mu.macroName = macro.name(); mu.macroLocation = macro.defineLocation(); mu.useLocation = *usageIt; macroUsage->push_back(mu); } } } } void simplecpp::cleanup(std::map &filedata) { for (std::map::iterator it = filedata.begin(); it != filedata.end(); ++it) delete it->second; filedata.clear(); } simplecpp::cstd_t simplecpp::getCStd(const std::string &std) { if (std == "c90" || std == "c89" || std == "iso9899:1990" || std == "iso9899:199409" || std == "gnu90" || std == "gnu89") return C89; if (std == "c99" || std == "c9x" || std == "iso9899:1999" || std == "iso9899:199x" || std == "gnu99"|| std == "gnu9x") return C99; if (std == "c11" || std == "c1x" || std == "iso9899:2011" || std == "gnu11" || std == "gnu1x") return C11; if (std == "c17" || std == "c18" || std == "iso9899:2017" || std == "iso9899:2018" || std == "gnu17"|| std == "gnu18") return C17; if (std == "c23" || std == "gnu23" || std == "c2x" || std == "gnu2x") return C23; return CUnknown; } std::string simplecpp::getCStdString(cstd_t std) { switch (std) { case C89: // __STDC_VERSION__ is not set for C90 although the macro was added in the 1994 amendments return ""; case C99: return "199901L"; case C11: return "201112L"; case C17: return "201710L"; case C23: // supported by GCC 9+ and Clang 9+ // Clang 9, 10, 11, 12, 13 return "201710L" // Clang 14, 15, 16, 17 return "202000L" // Clang 9, 10, 11, 12, 13, 14, 15, 16, 17 do not support "c23" and "gnu23" return "202311L"; case CUnknown: return ""; } return ""; } std::string simplecpp::getCStdString(const std::string &std) { return getCStdString(getCStd(std)); } simplecpp::cppstd_t simplecpp::getCppStd(const std::string &std) { if (std == "c++98" || std == "c++03" || std == "gnu++98" || std == "gnu++03") return CPP03; if (std == "c++11" || std == "gnu++11" || std == "c++0x" || std == "gnu++0x") return CPP11; if (std == "c++14" || std == "c++1y" || std == "gnu++14" || std == "gnu++1y") return CPP14; if (std == "c++17" || std == "c++1z" || std == "gnu++17" || std == "gnu++1z") return CPP17; if (std == "c++20" || std == "c++2a" || std == "gnu++20" || std == "gnu++2a") return CPP20; if (std == "c++23" || std == "c++2b" || std == "gnu++23" || std == "gnu++2b") return CPP23; if (std == "c++26" || std == "c++2c" || std == "gnu++26" || std == "gnu++2c") return CPP26; return CPPUnknown; } std::string simplecpp::getCppStdString(cppstd_t std) { switch (std) { case CPP03: return "199711L"; case CPP11: return "201103L"; case CPP14: return "201402L"; case CPP17: return "201703L"; case CPP20: // GCC 10 returns "201703L" - correct in 11+ return "202002L"; case CPP23: // supported by GCC 11+ and Clang 12+ // GCC 11, 12, 13 return "202100L" // Clang 12, 13, 14, 15, 16 do not support "c++23" and "gnu++23" and return "202101L" // Clang 17, 18 return "202302L" return "202302L"; case CPP26: // supported by Clang 17+ return "202400L"; case CPPUnknown: return ""; } return ""; } std::string simplecpp::getCppStdString(const std::string &std) { return getCppStdString(getCppStd(std)); } #if (__cplusplus < 201103L) && !defined(__APPLE__) #undef nullptr #endif

Back | FazBrowse Home | New Git URL