/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the qmake application of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qmakeparser.h" #include "qmakevfs.h" #include "ioutils.h" using namespace QMakeInternal; #include #ifdef PROPARSER_THREAD_SAFE # include #endif QT_BEGIN_NAMESPACE /////////////////////////////////////////////////////////////////////// // // ProFileCache // /////////////////////////////////////////////////////////////////////// ProFileCache::ProFileCache() { QMakeVfs::ref(); } ProFileCache::~ProFileCache() { for (const Entry &ent : qAsConst(parsed_files)) if (ent.pro) ent.pro->deref(); QMakeVfs::deref(); } void ProFileCache::discardFile(const QString &fileName, QMakeVfs *vfs) { int eid = vfs->idForFileName(fileName, QMakeVfs::VfsExact | QMakeVfs::VfsAccessedOnly); if (eid) discardFile(eid); int cid = vfs->idForFileName(fileName, QMakeVfs::VfsCumulative | QMakeVfs::VfsAccessedOnly); if (cid && cid != eid) discardFile(cid); } void ProFileCache::discardFile(int id) { #ifdef PROPARSER_THREAD_SAFE QMutexLocker lck(&mutex); #endif auto it = parsed_files.find(id); if (it != parsed_files.end()) { #ifdef PROPARSER_THREAD_SAFE if (it->locker) { if (!it->locker->done) { ++it->locker->waiters; it->locker->cond.wait(&mutex); if (!--it->locker->waiters) { delete it->locker; it->locker = 0; } } } #endif if (it->pro) it->pro->deref(); parsed_files.erase(it); } } void ProFileCache::discardFiles(const QString &prefix, QMakeVfs *vfs) { #ifdef PROPARSER_THREAD_SAFE QMutexLocker lck(&mutex); #endif auto it = parsed_files.begin(), end = parsed_files.end(); while (it != end) { // Note: this is empty for virtual files from other VFSes. QString fn = vfs->fileNameForId(it.key()); if (fn.startsWith(prefix)) { #ifdef PROPARSER_THREAD_SAFE if (it->locker) { if (!it->locker->done) { ++it->locker->waiters; it->locker->cond.wait(&mutex); if (!--it->locker->waiters) { delete it->locker; it->locker = 0; } } } #endif if (it->pro) it->pro->deref(); it = parsed_files.erase(it); } else { ++it; } } } ////////// Parser /////////// #define fL1S(s) QString::fromLatin1(s) namespace { // MSVC2010 doesn't seem to know the semantics of "static" ... static struct { QString strelse; QString strfor; QString strdefineTest; QString strdefineReplace; QString strbypassNesting; QString stroption; QString strreturn; QString strnext; QString strbreak; QString strhost_build; QString strLINE; QString strFILE; QString strLITERAL_HASH; QString strLITERAL_DOLLAR; QString strLITERAL_WHITESPACE; } statics; } void QMakeParser::initialize() { if (!statics.strelse.isNull()) return; statics.strelse = QLatin1String("else"); statics.strfor = QLatin1String("for"); statics.strdefineTest = QLatin1String("defineTest"); statics.strdefineReplace = QLatin1String("defineReplace"); statics.strbypassNesting = QLatin1String("bypassNesting"); statics.stroption = QLatin1String("option"); statics.strreturn = QLatin1String("return"); statics.strnext = QLatin1String("next"); statics.strbreak = QLatin1String("break"); statics.strhost_build = QLatin1String("host_build"); statics.strLINE = QLatin1String("_LINE_"); statics.strFILE = QLatin1String("_FILE_"); statics.strLITERAL_HASH = QLatin1String("LITERAL_HASH"); statics.strLITERAL_DOLLAR = QLatin1String("LITERAL_DOLLAR"); statics.strLITERAL_WHITESPACE = QLatin1String("LITERAL_WHITESPACE"); } QMakeParser::QMakeParser(ProFileCache *cache, QMakeVfs *vfs, QMakeParserHandler *handler) : m_cache(cache) , m_handler(handler) , m_vfs(vfs) { // So that single-threaded apps don't have to call initialize() for now. initialize(); } ProFile *QMakeParser::parsedProFile(const QString &fileName, ParseFlags flags) { ProFile *pro; QMakeVfs::VfsFlags vfsFlags = ((flags & ParseCumulative) ? QMakeVfs::VfsCumulative : QMakeVfs::VfsExact); int id = m_vfs->idForFileName(fileName, vfsFlags); if ((flags & ParseUseCache) && m_cache) { ProFileCache::Entry *ent; #ifdef PROPARSER_THREAD_SAFE QMutexLocker locker(&m_cache->mutex); #endif auto it = m_cache->parsed_files.find(id); if (it != m_cache->parsed_files.end()) { ent = &*it; #ifdef PROPARSER_THREAD_SAFE if (ent->locker && !ent->locker->done) { ++ent->locker->waiters; QThreadPool::globalInstance()->releaseThread(); ent->locker->cond.wait(locker.mutex()); QThreadPool::globalInstance()->reserveThread(); if (!--ent->locker->waiters) { delete ent->locker; ent->locker = 0; } } #endif if ((pro = ent->pro)) pro->ref(); } else { ent = &m_cache->parsed_files[id]; #ifdef PROPARSER_THREAD_SAFE ent->locker = new ProFileCache::Entry::Locker; locker.unlock(); #endif QString contents; if (readFile(id, flags, &contents)) { pro = parsedProBlock(QStringRef(&contents), id, fileName, 1, FullGrammar); pro->itemsRef()->squeeze(); pro->ref(); } else { pro = nullptr; } ent->pro = pro; #ifdef PROPARSER_THREAD_SAFE locker.relock(); if (ent->locker->waiters) { ent->locker->done = true; ent->locker->cond.wakeAll(); } else { delete ent->locker; ent->locker = 0; } #endif } } else { QString contents; if (readFile(id, flags, &contents)) pro = parsedProBlock(QStringRef(&contents), id, fileName, 1, FullGrammar); else pro = nullptr; } return pro; } ProFile *QMakeParser::parsedProBlock( const QStringRef &contents, int id, const QString &name, int line, SubGrammar grammar) { ProFile *pro = new ProFile(id, name); read(pro, contents, line, grammar); return pro; } void QMakeParser::discardFileFromCache(int id) { if (m_cache) m_cache->discardFile(id); } bool QMakeParser::readFile(int id, ParseFlags flags, QString *contents) { QString errStr; QMakeVfs::ReadResult result = m_vfs->readFile(id, contents, &errStr); if (result != QMakeVfs::ReadOk) { if (m_handler && ((flags & ParseReportMissing) || result != QMakeVfs::ReadNotFound)) m_handler->message(QMakeParserHandler::ParserIoError, fL1S("Cannot read %1: %2").arg(m_vfs->fileNameForId(id), errStr)); return false; } return true; } void QMakeParser::putTok(ushort *&tokPtr, ushort tok) { *tokPtr++ = tok; } void QMakeParser::putBlockLen(ushort *&tokPtr, uint len) { *tokPtr++ = (ushort)len; *tokPtr++ = (ushort)(len >> 16); } void QMakeParser::putBlock(ushort *&tokPtr, const ushort *buf, uint len) { memcpy(tokPtr, buf, len * 2); tokPtr += len; } void QMakeParser::putHashStr(ushort *&pTokPtr, const ushort *buf, uint len) { uint hash = ProString::hash((const QChar *)buf, len); ushort *tokPtr = pTokPtr; *tokPtr++ = (ushort)hash; *tokPtr++ = (ushort)(hash >> 16); *tokPtr++ = (ushort)len; if (len) // buf may be nullptr; don't pass that to memcpy (-> undefined behavior) memcpy(tokPtr, buf, len * 2); pTokPtr = tokPtr + len; } void QMakeParser::finalizeHashStr(ushort *buf, uint len) { buf[-4] = TokHashLiteral; buf[-1] = len; uint hash = ProString::hash((const QChar *)buf, len); buf[-3] = (ushort)hash; buf[-2] = (ushort)(hash >> 16); } void QMakeParser::read(ProFile *pro, const QStringRef &in, int line, SubGrammar grammar) { m_proFile = pro; m_lineNo = line; // Final precompiled token stream buffer QString tokBuff; // Worst-case size calculations: // - line marker adds 1 (2-nl) to 1st token of each line // - empty assignment "A=":2 => // TokHashLiteral(1) + hash(2) + len(1) + "A"(1) + TokAssign(1) + size_hint(1) + // TokValueTerminator(1) == 8 (9) // - non-empty assignment "A=B C":5 => // TokHashLiteral(1) + hash(2) + len(1) + "A"(1) + TokAssign(1) + size_hint(1) + // TokLiteral(1) + len(1) + "B"(1) + // TokLiteral(1) + len(1) + "C"(1) + TokValueTerminator(1) == 14 (15) // - variable expansion: "$$f":3 => // TokVariable(1) + hash(2) + len(1) + "f"(1) = 5 // - function expansion: "$$f()":5 => // TokFuncName(1) + hash(2) + len(1) + "f"(1) + TokFuncTerminator(1) = 6 // - test literal: "X":1 => // TokHashLiteral(1) + hash(2) + len(1) + "A"(1) + TokCondition(1) = 6 (7) // - scope: "X:":2 => // TokHashLiteral(1) + hash(2) + len(1) + "A"(1) + TokCondition(1) + // TokBranch(1) + len(2) + ... + len(2) + ... == 11 (12) // - test call: "X():":4 => // TokHashLiteral(1) + hash(2) + len(1) + "A"(1) + TokTestCall(1) + TokFuncTerminator(1) + // TokBranch(1) + len(2) + ... + len(2) + ... == 12 (13) // - "for(A,B):":9 => // TokForLoop(1) + hash(2) + len(1) + "A"(1) + // len(2) + TokLiteral(1) + len(1) + "B"(1) + TokValueTerminator(1) + // len(2) + ... + TokTerminator(1) == 14 (15) // One extra for possibly missing trailing newline. tokBuff.reserve((in.size() + 1) * 7); ushort *tokPtr = (ushort *)tokBuff.constData(); // Current writing position // Expression precompiler buffer. QString xprBuff; xprBuff.reserve(tokBuff.capacity()); // Excessive, but simple ushort *buf = (ushort *)xprBuff.constData(); // Parser state m_blockstack.clear(); m_blockstack.resize(1); QStack xprStack; xprStack.reserve(10); const ushort *cur = (const ushort *)in.unicode(); const ushort *inend = cur + in.length(); m_canElse = false; freshLine: m_state = StNew; m_invert = 0; m_operator = NoOperator; m_markLine = m_lineNo; m_inError = false; int parens = 0; // Braces in value context int argc = 0; int wordCount = 0; // Number of words in currently accumulated expression int lastIndent = 0; // Previous line's indentation, to detect accidental continuation abuse bool lineMarked = true; // For in-expression markers ushort needSep = TokNewStr; // Met unquoted whitespace ushort quote = 0; ushort term = 0; Context context; ushort *ptr; if (grammar == ValueGrammar) { context = CtxPureValue; ptr = tokPtr + 2; } else { context = CtxTest; ptr = buf + 4; } ushort *xprPtr = ptr; #define FLUSH_LHS_LITERAL() \ do { \ if ((tlen = ptr - xprPtr)) { \ finalizeHashStr(xprPtr, tlen); \ if (needSep) { \ wordCount++; \ needSep = 0; \ } \ } else { \ ptr -= 4; \ } \ } while (0) #define FLUSH_RHS_LITERAL() \ do { \ if ((tlen = ptr - xprPtr)) { \ xprPtr[-2] = TokLiteral | needSep; \ xprPtr[-1] = tlen; \ if (needSep) { \ wordCount++; \ needSep = 0; \ } \ } else { \ ptr -= 2; \ } \ } while (0) #define FLUSH_LITERAL() \ do { \ if (context == CtxTest) \ FLUSH_LHS_LITERAL(); \ else \ FLUSH_RHS_LITERAL(); \ } while (0) #define FLUSH_VALUE_LIST() \ do { \ if (wordCount > 1) { \ xprPtr = tokPtr; \ if (*xprPtr == TokLine) \ xprPtr += 2; \ tokPtr[-1] = ((*xprPtr & TokMask) == TokLiteral) ? wordCount : 0; \ } else { \ tokPtr[-1] = 0; \ } \ tokPtr = ptr; \ putTok(tokPtr, TokValueTerminator); \ } while (0) const ushort *end; // End of this line const ushort *cptr; // Start of next line bool lineCont; int indent; if (context == CtxPureValue) { end = inend; cptr = nullptr; lineCont = false; indent = 0; // just gcc being stupid goto nextChr; } forever { ushort c; // First, skip leading whitespace for (indent = 0; ; ++cur, ++indent) { if (cur == inend) { cur = nullptr; goto flushLine; } c = *cur; if (c == '\n') { ++cur; goto flushLine; } if (c != ' ' && c != '\t' && c != '\r') break; } // Then strip comments. Yep - no escaping is possible. for (cptr = cur;; ++cptr) { if (cptr == inend) { end = cptr; break; } c = *cptr; if (c == '#') { end = cptr; while (++cptr < inend) { if (*cptr == '\n') { ++cptr; break; } } if (end == cur) { // Line with only a comment (sans whitespace) if (m_markLine == m_lineNo) m_markLine++; // Qmake bizarreness: such lines do not affect line continuations goto ignore; } break; } if (c == '\n') { end = cptr++; break; } } // Then look for line continuations. Yep - no escaping here as well. forever { // We don't have to check for underrun here, as we already determined // that the line is non-empty. ushort ec = *(end - 1); if (ec == '\\') { --end; lineCont = true; break; } if (ec != ' ' && ec != '\t' && ec != '\r') { lineCont = false; break; } --end; } // Finally, do the tokenization ushort tok, rtok; int tlen; newWord: do { if (cur == end) goto lineEnd; c = *cur++; } while (c == ' ' || c == '\t'); forever { if (c == '$') { if (*cur == '$') { // may be EOF, EOL, WS, '#' or '\\' if past end cur++; FLUSH_LITERAL(); if (!lineMarked) { lineMarked = true; *ptr++ = TokLine; *ptr++ = (ushort)m_lineNo; } term = 0; tok = TokVariable; c = *cur; if (c == '[') { ptr += 4; tok = TokProperty; term = ']'; c = *++cur; } else if (c == '{') { ptr += 4; term = '}'; c = *++cur; } else if (c == '(') { ptr += 2; tok = TokEnvVar; term = ')'; c = *++cur; } else { ptr += 4; } xprPtr = ptr; rtok = tok; while ((c & 0xFF00) || c == '.' || c == '_' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || (c == '/' && term)) { *ptr++ = c; if (++cur == end) { c = 0; goto notfunc; } c = *cur; } if (tok == TokVariable && c == '(') tok = TokFuncName; notfunc: if (ptr == xprPtr) languageWarning(fL1S("Missing name in expansion")); if (quote) tok |= TokQuoted; if (needSep) { tok |= needSep; wordCount++; } tlen = ptr - xprPtr; if (rtok != TokVariable || !resolveVariable(xprPtr, tlen, needSep, &ptr, &buf, &xprBuff, &tokPtr, &tokBuff, cur, in)) { if (rtok == TokVariable || rtok == TokProperty) { xprPtr[-4] = tok; uint hash = ProString::hash((const QChar *)xprPtr, tlen); xprPtr[-3] = (ushort)hash; xprPtr[-2] = (ushort)(hash >> 16); xprPtr[-1] = tlen; } else { xprPtr[-2] = tok; xprPtr[-1] = tlen; } } if ((tok & TokMask) == TokFuncName) { cur++; funcCall: { xprStack.resize(xprStack.size() + 1); ParseCtx &top = xprStack.top(); top.parens = parens; top.quote = quote; top.terminator = term; top.context = context; top.argc = argc; top.wordCount = wordCount; } parens = 0; quote = 0; term = 0; argc = 1; context = CtxArgs; nextToken: wordCount = 0; nextWord: ptr += (context == CtxTest) ? 4 : 2; xprPtr = ptr; needSep = TokNewStr; goto newWord; } if (term) { checkTerm: if (c != term) { parseError(fL1S("Missing %1 terminator [found %2]") .arg(QChar(term)) .arg(c ? QString(c) : QString::fromLatin1("end-of-line"))); m_inError = true; // Just parse on, as if there was a terminator ... } else { cur++; } } joinToken: ptr += (context == CtxTest) ? 4 : 2; xprPtr = ptr; needSep = 0; goto nextChr; } } else if (c == '\\') { static const char symbols[] = "[]{}()$\\'\""; ushort c2; if (cur != end && !((c2 = *cur) & 0xff00) && strchr(symbols, c2)) { c = c2; cur++; } else { deprecationWarning(fL1S("Unescaped backslashes are deprecated")); } } else if (quote) { if (c == quote) { quote = 0; goto nextChr; } else if (c == '!' && ptr == xprPtr && context == CtxTest) { m_invert++; goto nextChr; } } else if (c == '\'' || c == '"') { quote = c; goto nextChr; } else if (context == CtxArgs) { // Function arg context if (c == ' ' || c == '\t') { FLUSH_RHS_LITERAL(); goto nextWord; } else if (c == '(') { ++parens; } else if (c == ')') { if (--parens < 0) { FLUSH_RHS_LITERAL(); *ptr++ = TokFuncTerminator; int theargc = argc; { ParseCtx &top = xprStack.top(); parens = top.parens; quote = top.quote; term = top.terminator; context = top.context; argc = top.argc; wordCount = top.wordCount; xprStack.resize(xprStack.size() - 1); } if (term == ':') { finalizeCall(tokPtr, buf, ptr, theargc); goto nextItem; } else if (term == '}') { c = (cur == end) ? 0 : *cur; goto checkTerm; } else { Q_ASSERT(!term); goto joinToken; } } } else if (!parens && c == ',') { FLUSH_RHS_LITERAL(); *ptr++ = TokArgSeparator; argc++; goto nextToken; } } else if (context == CtxTest) { // Test or LHS context if (c == ' ' || c == '\t') { FLUSH_LHS_LITERAL(); goto nextWord; } else if (c == '(') { FLUSH_LHS_LITERAL(); if (wordCount != 1) { if (wordCount) parseError(fL1S("Extra characters after test expression.")); else parseError(fL1S("Opening parenthesis without prior test name.")); ptr = buf; // Put empty function name } *ptr++ = TokTestCall; term = ':'; goto funcCall; } else if (c == '!' && ptr == xprPtr) { m_invert++; goto nextChr; } else if (c == ':') { FLUSH_LHS_LITERAL(); finalizeCond(tokPtr, buf, ptr, wordCount); warnOperator("in front of AND operator"); if (m_state == StNew) parseError(fL1S("AND operator without prior condition.")); else m_operator = AndOperator; nextItem: ptr = buf; goto nextToken; } else if (c == '|') { FLUSH_LHS_LITERAL(); finalizeCond(tokPtr, buf, ptr, wordCount); warnOperator("in front of OR operator"); if (m_state != StCond) parseError(fL1S("OR operator without prior condition.")); else m_operator = OrOperator; goto nextItem; } else if (c == '{') { FLUSH_LHS_LITERAL(); finalizeCond(tokPtr, buf, ptr, wordCount); if (m_operator == AndOperator) { languageWarning(fL1S("Excess colon in front of opening brace.")); m_operator = NoOperator; } failOperator("in front of opening brace"); flushCond(tokPtr); m_state = StNew; // Reset possible StCtrl, so colons get rejected. ++m_blockstack.top().braceLevel; if (grammar == TestGrammar) parseError(fL1S("Opening scope not permitted in this context.")); goto nextItem; } else if (c == '}') { FLUSH_LHS_LITERAL(); finalizeCond(tokPtr, buf, ptr, wordCount); m_state = StNew; // De-facto newline closeScope: flushScopes(tokPtr); failOperator("in front of closing brace"); if (!m_blockstack.top().braceLevel) { parseError(fL1S("Excess closing brace.")); } else if (!--m_blockstack.top().braceLevel && m_blockstack.count() != 1) { leaveScope(tokPtr); m_state = StNew; m_canElse = false; m_markLine = m_lineNo; } goto nextItem; } else if (c == '+') { tok = TokAppend; goto do2Op; } else if (c == '-') { tok = TokRemove; goto do2Op; } else if (c == '*') { tok = TokAppendUnique; goto do2Op; } else if (c == '~') { tok = TokReplace; do2Op: if (*cur == '=') { cur++; goto doOp; } } else if (c == '=') { tok = TokAssign; doOp: FLUSH_LHS_LITERAL(); flushCond(tokPtr); acceptColon("in front of assignment"); putLineMarker(tokPtr); if (grammar == TestGrammar) { parseError(fL1S("Assignment not permitted in this context.")); } else if (wordCount != 1) { parseError(fL1S("Assignment needs exactly one word on the left hand side.")); // Put empty variable name. } else { putBlock(tokPtr, buf, ptr - buf); } putTok(tokPtr, tok); context = CtxValue; ptr = ++tokPtr; goto nextToken; } } else if (context == CtxValue) { if (c == ' ' || c == '\t') { FLUSH_RHS_LITERAL(); goto nextWord; } else if (c == '{') { ++parens; } else if (c == '}') { if (!parens) { FLUSH_RHS_LITERAL(); FLUSH_VALUE_LIST(); context = CtxTest; goto closeScope; } --parens; } else if (c == '=') { if (indent < lastIndent) languageWarning(fL1S("Possible accidental line continuation")); } } *ptr++ = c; nextChr: if (cur == end) goto lineEnd; c = *cur++; } lineEnd: if (lineCont) { if (quote) { *ptr++ = ' '; } else { FLUSH_LITERAL(); needSep = TokNewStr; ptr += (context == CtxTest) ? 4 : 2; xprPtr = ptr; } } else { cur = cptr; flushLine: FLUSH_LITERAL(); if (quote) { parseError(fL1S("Missing closing %1 quote").arg(QChar(quote))); if (!xprStack.isEmpty()) { context = xprStack.at(0).context; xprStack.clear(); } goto flErr; } else if (!xprStack.isEmpty()) { parseError(fL1S("Missing closing parenthesis in function call")); context = xprStack.at(0).context; xprStack.clear(); flErr: pro->setOk(false); if (context == CtxValue) { tokPtr[-1] = 0; // sizehint putTok(tokPtr, TokValueTerminator); } else if (context == CtxPureValue) { putTok(tokPtr, TokValueTerminator); } else { bogusTest(tokPtr, QString()); } } else if (context == CtxValue) { FLUSH_VALUE_LIST(); if (parens) languageWarning(fL1S("Possible braces mismatch")); } else if (context == CtxPureValue) { tokPtr = ptr; putTok(tokPtr, TokValueTerminator); } else { finalizeCond(tokPtr, buf, ptr, wordCount); warnOperator("at end of line"); } if (!cur) break; ++m_lineNo; goto freshLine; } lastIndent = indent; lineMarked = false; ignore: cur = cptr; ++m_lineNo; } flushScopes(tokPtr); if (m_blockstack.size() > 1 || m_blockstack.top().braceLevel) parseError(fL1S("Missing closing brace(s).")); while (m_blockstack.size()) leaveScope(tokPtr); tokBuff.resize(tokPtr - (ushort *)tokBuff.constData()); // Reserved capacity stays *pro->itemsRef() = tokBuff; #undef FLUSH_VALUE_LIST #undef FLUSH_LITERAL #undef FLUSH_LHS_LITERAL #undef FLUSH_RHS_LITERAL } void QMakeParser::putLineMarker(ushort *&tokPtr) { if (m_markLine) { *tokPtr++ = TokLine; *tokPtr++ = (ushort)m_markLine; m_markLine = 0; } } void QMakeParser::enterScope(ushort *&tokPtr, bool special, ScopeState state) { uchar nest = m_blockstack.top().nest; m_blockstack.resize(m_blockstack.size() + 1); m_blockstack.top().special = special; m_blockstack.top().start = tokPtr; m_blockstack.top().nest = nest; tokPtr += 2; m_state = state; m_canElse = false; if (special) m_markLine = m_lineNo; } void QMakeParser::leaveScope(ushort *&tokPtr) { if (m_blockstack.top().inBranch) { // Put empty else block putBlockLen(tokPtr, 0); } if (ushort *start = m_blockstack.top().start) { putTok(tokPtr, TokTerminator); uint len = tokPtr - start - 2; start[0] = (ushort)len; start[1] = (ushort)(len >> 16); } m_blockstack.resize(m_blockstack.size() - 1); } // If we are on a fresh line, close all open one-line scopes. void QMakeParser::flushScopes(ushort *&tokPtr) { if (m_state == StNew) { while (!m_blockstack.top().braceLevel && m_blockstack.size() > 1) leaveScope(tokPtr); if (m_blockstack.top().inBranch) { m_blockstack.top().inBranch = false; // Put empty else block putBlockLen(tokPtr, 0); } m_canElse = false; } } // If there is a pending conditional, enter a new scope, otherwise flush scopes. void QMakeParser::flushCond(ushort *&tokPtr) { if (m_state == StCond) { putTok(tokPtr, TokBranch); m_blockstack.top().inBranch = true; enterScope(tokPtr, false, StNew); } else { flushScopes(tokPtr); } } void QMakeParser::warnOperator(const char *msg) { if (m_invert) { languageWarning(fL1S("Stray NOT operator %1.").arg(fL1S(msg))); m_invert = 0; } if (m_operator == AndOperator) { languageWarning(fL1S("Stray AND operator %1.").arg(fL1S(msg))); m_operator = NoOperator; } else if (m_operator == OrOperator) { languageWarning(fL1S("Stray OR operator %1.").arg(fL1S(msg))); m_operator = NoOperator; } } bool QMakeParser::failOperator(const char *msg) { bool fail = false; if (m_invert) { parseError(fL1S("Unexpected NOT operator %1.").arg(fL1S(msg))); m_invert = 0; fail = true; } if (m_operator == AndOperator) { parseError(fL1S("Unexpected AND operator %1.").arg(fL1S(msg))); m_operator = NoOperator; fail = true; } else if (m_operator == OrOperator) { parseError(fL1S("Unexpected OR operator %1.").arg(fL1S(msg))); m_operator = NoOperator; fail = true; } return fail; } bool QMakeParser::acceptColon(const char *msg) { if (m_operator == AndOperator) m_operator = NoOperator; return !failOperator(msg); } void QMakeParser::putOperator(ushort *&tokPtr) { if (m_operator== AndOperator) { // A colon must be used after else and for() if no brace is used, // but in this case it is obviously not a binary operator. if (m_state == StCond) putTok(tokPtr, TokAnd); m_operator = NoOperator; } else if (m_operator == OrOperator) { putTok(tokPtr, TokOr); m_operator = NoOperator; } } void QMakeParser::finalizeTest(ushort *&tokPtr) { flushScopes(tokPtr); putLineMarker(tokPtr); putOperator(tokPtr); if (m_invert & 1) putTok(tokPtr, TokNot); m_invert = 0; m_state = StCond; m_canElse = true; } void QMakeParser::bogusTest(ushort *&tokPtr, const QString &msg) { if (!msg.isEmpty()) parseError(msg); flushScopes(tokPtr); m_operator = NoOperator; m_invert = 0; m_state = StCond; m_canElse = true; } void QMakeParser::finalizeCond(ushort *&tokPtr, ushort *uc, ushort *ptr, int wordCount) { if (wordCount != 1) { if (wordCount) bogusTest(tokPtr, fL1S("Extra characters after test expression.")); return; } // Check for magic tokens if (*uc == TokHashLiteral) { uint nlen = uc[3]; ushort *uce = uc + 4 + nlen; if (uce == ptr) { m_tmp.setRawData((QChar *)uc + 4, nlen); if (!m_tmp.compare(statics.strelse, Qt::CaseInsensitive)) { if (failOperator("in front of else")) return; BlockScope &top = m_blockstack.top(); if (m_canElse && (!top.special || top.braceLevel)) { // A list of tests (the last one likely with side effects), // but no assignment, scope, etc. putTok(tokPtr, TokBranch); // Put empty then block putBlockLen(tokPtr, 0); enterScope(tokPtr, false, StCtrl); return; } forever { BlockScope &top = m_blockstack.top(); if (top.inBranch && (!top.special || top.braceLevel)) { top.inBranch = false; enterScope(tokPtr, false, StCtrl); return; } if (top.braceLevel || m_blockstack.size() == 1) break; leaveScope(tokPtr); } parseError(fL1S("Unexpected 'else'.")); return; } } } finalizeTest(tokPtr); putBlock(tokPtr, uc, ptr - uc); putTok(tokPtr, TokCondition); } void QMakeParser::finalizeCall(ushort *&tokPtr, ushort *uc, ushort *ptr, int argc) { // Check for magic tokens if (*uc == TokHashLiteral) { uint nlen = uc[3]; ushort *uce = uc + 4 + nlen; if (*uce == TokTestCall) { uce++; m_tmp.setRawData((QChar *)uc + 4, nlen); const QString *defName; ushort defType; if (m_tmp == statics.strfor) { if (!acceptColon("in front of for()")) { bogusTest(tokPtr, QString()); return; } flushCond(tokPtr); putLineMarker(tokPtr); --ptr; Q_ASSERT(*ptr == TokFuncTerminator); if (*uce == (TokLiteral|TokNewStr)) { nlen = uce[1]; uc = uce + 2 + nlen; if (uc == ptr) { // for(literal) (only "ever" would be legal if qmake was sane) putTok(tokPtr, TokForLoop); putHashStr(tokPtr, nullptr, (uint)0); putBlockLen(tokPtr, 1 + 3 + nlen + 1); putTok(tokPtr, TokHashLiteral); putHashStr(tokPtr, uce + 2, nlen); didFor: putTok(tokPtr, TokValueTerminator); enterScope(tokPtr, true, StCtrl); m_blockstack.top().nest |= NestLoop; return; } else if (*uc == TokArgSeparator && argc == 2) { // for(var, something) uc++; putTok(tokPtr, TokForLoop); putHashStr(tokPtr, uce + 2, nlen); doFor: nlen = ptr - uc; putBlockLen(tokPtr, nlen + 1); putBlock(tokPtr, uc, nlen); goto didFor; } } else if (argc == 1) { // for(non-literal) (this wouldn't be here if qmake was sane) putTok(tokPtr, TokForLoop); putHashStr(tokPtr, nullptr, (uint)0); uc = uce; goto doFor; } parseError(fL1S("Syntax is for(var, list), for(var, forever) or for(ever).")); return; } else if (m_tmp == statics.strdefineReplace) { defName = &statics.strdefineReplace; defType = TokReplaceDef; goto deffunc; } else if (m_tmp == statics.strdefineTest) { defName = &statics.strdefineTest; defType = TokTestDef; deffunc: if (m_invert) { bogusTest(tokPtr, fL1S("Unexpected NOT operator in front of function definition.")); return; } flushScopes(tokPtr); putLineMarker(tokPtr); if (*uce == (TokLiteral|TokNewStr)) { uint nlen = uce[1]; if (uce[nlen + 2] == TokFuncTerminator) { putOperator(tokPtr); putTok(tokPtr, defType); putHashStr(tokPtr, uce + 2, nlen); enterScope(tokPtr, true, StCtrl); m_blockstack.top().nest = NestFunction; return; } } parseError(fL1S("%1(function) requires one literal argument.").arg(*defName)); return; } else if (m_tmp == statics.strbypassNesting) { if (*uce != TokFuncTerminator) { bogusTest(tokPtr, fL1S("%1() requires zero arguments.").arg(m_tmp)); return; } if (!(m_blockstack.top().nest & NestFunction)) { bogusTest(tokPtr, fL1S("Unexpected %1().").arg(m_tmp)); return; } if (m_invert) { bogusTest(tokPtr, fL1S("Unexpected NOT operator in front of %1().").arg(m_tmp)); return; } flushScopes(tokPtr); putLineMarker(tokPtr); putOperator(tokPtr); putTok(tokPtr, TokBypassNesting); enterScope(tokPtr, true, StCtrl); return; } else if (m_tmp == statics.strreturn) { if (m_blockstack.top().nest & NestFunction) { if (argc > 1) { bogusTest(tokPtr, fL1S("return() requires zero or one argument.")); return; } } else { if (*uce != TokFuncTerminator) { bogusTest(tokPtr, fL1S("Top-level return() requires zero arguments.")); return; } } defType = TokReturn; goto ctrlstm2; } else if (m_tmp == statics.strnext) { defType = TokNext; goto ctrlstm; } else if (m_tmp == statics.strbreak) { defType = TokBreak; ctrlstm: if (*uce != TokFuncTerminator) { bogusTest(tokPtr, fL1S("%1() requires zero arguments.").arg(m_tmp)); return; } if (!(m_blockstack.top().nest & NestLoop)) { bogusTest(tokPtr, fL1S("Unexpected %1().").arg(m_tmp)); return; } ctrlstm2: if (m_invert) { bogusTest(tokPtr, fL1S("Unexpected NOT operator in front of %1().").arg(m_tmp)); return; } finalizeTest(tokPtr); putBlock(tokPtr, uce, ptr - uce - 1); // Only for TokReturn putTok(tokPtr, defType); return; } else if (m_tmp == statics.stroption) { if (m_state != StNew || m_blockstack.top().braceLevel || m_blockstack.size() > 1 || m_invert || m_operator != NoOperator) { bogusTest(tokPtr, fL1S("option() must appear outside any control structures.")); return; } if (*uce == (TokLiteral|TokNewStr)) { uint nlen = uce[1]; if (uce[nlen + 2] == TokFuncTerminator) { m_tmp.setRawData((QChar *)uce + 2, nlen); if (m_tmp == statics.strhost_build) m_proFile->setHostBuild(true); else parseError(fL1S("Unknown option() %1.").arg(m_tmp)); return; } } parseError(fL1S("option() requires one literal argument.")); return; } } } finalizeTest(tokPtr); putBlock(tokPtr, uc, ptr - uc); } bool QMakeParser::resolveVariable(ushort *xprPtr, int tlen, int needSep, ushort **ptr, ushort **buf, QString *xprBuff, ushort **tokPtr, QString *tokBuff, const ushort *cur, const QStringRef &in) { QString out; m_tmp.setRawData((const QChar *)xprPtr, tlen); if (m_tmp == statics.strLINE) { out.setNum(m_lineNo); } else if (m_tmp == statics.strFILE) { out = m_proFile->fileName(); // The string is typically longer than the variable reference, so we need // to ensure that there is enough space in the output buffer - as unlikely // as an overflow is to actually happen in practice. int need = (in.length() - (cur - (const ushort *)in.constData()) + 2) * 5 + out.length(); int tused = *tokPtr - (ushort *)tokBuff->constData(); int xused; int total; bool ptrFinal = xprPtr >= (ushort *)tokBuff->constData() && xprPtr < (ushort *)tokBuff->constData() + tokBuff->capacity(); if (ptrFinal) { xused = xprPtr - (ushort *)tokBuff->constData(); total = xused + need; } else { xused = xprPtr - *buf; total = tused + xused + need; } if (tokBuff->capacity() < total) { tokBuff->reserve(total); *tokPtr = (ushort *)tokBuff->constData() + tused; xprBuff->reserve(total); *buf = (ushort *)xprBuff->constData(); xprPtr = (ptrFinal ? (ushort *)tokBuff->constData() : *buf) + xused; } } else if (m_tmp == statics.strLITERAL_HASH) { out = QLatin1String("#"); } else if (m_tmp == statics.strLITERAL_DOLLAR) { out = QLatin1String("$"); } else if (m_tmp == statics.strLITERAL_WHITESPACE) { out = QLatin1String("\t"); } else { return false; } xprPtr -= 2; // Was set up for variable reference xprPtr[-2] = TokLiteral | needSep; xprPtr[-1] = out.length(); memcpy(xprPtr, out.constData(), out.length() * 2); *ptr = xprPtr + out.length(); return true; } void QMakeParser::message(int type, const QString &msg) const { if (!m_inError && m_handler) m_handler->message(type, msg, m_proFile->fileName(), m_lineNo); } #ifdef PROPARSER_DEBUG #define BOUNDS_CHECK(need) \ do { \ int have = limit - offset; \ if (have < (int)need) { \ *outStr += fL1S("").arg(need).arg(have); \ return false; \ } \ } while (0) static bool getRawUshort(const ushort *tokens, int limit, int &offset, ushort *outVal, QString *outStr) { BOUNDS_CHECK(1); uint val = tokens[offset++]; *outVal = val; return true; } static bool getUshort(const ushort *tokens, int limit, int &offset, ushort *outVal, QString *outStr) { *outStr += fL1S(" << H("); if (!getRawUshort(tokens, limit, offset, outVal, outStr)) return false; *outStr += QString::number(*outVal) + QLatin1Char(')'); return true; } static bool getRawUint(const ushort *tokens, int limit, int &offset, uint *outVal, QString *outStr) { BOUNDS_CHECK(2); uint val = tokens[offset++]; val |= (uint)tokens[offset++] << 16; *outVal = val; return true; } static bool getUint(const ushort *tokens, int limit, int &offset, uint *outVal, QString *outStr) { *outStr += fL1S(" << I("); if (!getRawUint(tokens, limit, offset, outVal, outStr)) return false; *outStr += QString::number(*outVal) + QLatin1Char(')'); return true; } static bool getRawStr(const ushort *tokens, int limit, int &offset, int strLen, QString *outStr) { BOUNDS_CHECK(strLen); *outStr += fL1S("L\""); bool attn = false; for (int i = 0; i < strLen; i++) { ushort val = tokens[offset++]; switch (val) { case '"': *outStr += fL1S("\\\""); break; case '\n': *outStr += fL1S("\\n"); break; case '\r': *outStr += fL1S("\\r"); break; case '\t': *outStr += fL1S("\\t"); break; case '\\': *outStr += fL1S("\\\\"); break; default: if (val < 32 || val > 126) { *outStr += (val > 255 ? fL1S("\\u") : fL1S("\\x")) + QString::number(val, 16); attn = true; continue; } if (attn && isxdigit(val)) *outStr += fL1S("\"\""); *outStr += QChar(val); break; } attn = false; } *outStr += QLatin1Char('"'); return true; } static bool getStr(const ushort *tokens, int limit, int &offset, QString *outStr) { *outStr += fL1S(" << S("); ushort len; if (!getRawUshort(tokens, limit, offset, &len, outStr)) return false; if (!getRawStr(tokens, limit, offset, len, outStr)) return false; *outStr += QLatin1Char(')'); return true; } static bool getHashStr(const ushort *tokens, int limit, int &offset, QString *outStr) { *outStr += fL1S(" << HS("); uint hash; if (!getRawUint(tokens, limit, offset, &hash, outStr)) return false; ushort len; if (!getRawUshort(tokens, limit, offset, &len, outStr)) return false; const QChar *chars = (const QChar *)tokens + offset; if (!getRawStr(tokens, limit, offset, len, outStr)) return false; uint realhash = ProString::hash(chars, len); if (realhash != hash) *outStr += fL1S(" /* Bad hash ") + QString::number(hash) + fL1S(" */"); *outStr += QLatin1Char(')'); return true; } static bool getBlock(const ushort *tokens, int limit, int &offset, QString *outStr, int indent); static bool getSubBlock(const ushort *tokens, int limit, int &offset, QString *outStr, int indent, const char *scope) { *outStr += fL1S("\n /* %1 */ ").arg(offset, 5) + QString(indent * 4, QLatin1Char(' ')) + fL1S("/* ") + fL1S(scope) + fL1S(" */"); uint len; if (!getUint(tokens, limit, offset, &len, outStr)) return false; if (len) { BOUNDS_CHECK(len); int tmpOff = offset; offset += len; forever { if (!getBlock(tokens, offset, tmpOff, outStr, indent + 1)) break; // Error was already reported, try to continue if (tmpOff == offset) break; *outStr += QLatin1Char('\n') + QString(20 + indent * 4, QLatin1Char(' ')) + fL1S("/* Warning: Excess tokens follow. */"); } } return true; } static bool getBlock(const ushort *tokens, int limit, int &offset, QString *outStr, int indent) { static const char * const tokNames[] = { "TokTerminator", "TokLine", "TokAssign", "TokAppend", "TokAppendUnique", "TokRemove", "TokReplace", "TokValueTerminator", "TokLiteral", "TokHashLiteral", "TokVariable", "TokProperty", "TokEnvVar", "TokFuncName", "TokArgSeparator", "TokFuncTerminator", "TokCondition", "TokTestCall", "TokReturn", "TokBreak", "TokNext", "TokNot", "TokAnd", "TokOr", "TokBranch", "TokForLoop", "TokTestDef", "TokReplaceDef", "TokBypassNesting" }; while (offset != limit) { *outStr += fL1S("\n /* %1 */").arg(offset, 5) + QString(indent * 4, QLatin1Char(' ')); BOUNDS_CHECK(1); ushort tok = tokens[offset++]; ushort maskedTok = tok & TokMask; if (maskedTok >= sizeof(tokNames)/sizeof(tokNames[0]) || (tok & ~(TokNewStr | TokQuoted | TokMask))) { *outStr += fL1S(" << {invalid token %1}").arg(tok); return false; } *outStr += fL1S(" << H(") + fL1S(tokNames[maskedTok]); if (tok & TokNewStr) *outStr += fL1S(" | TokNewStr"); if (tok & TokQuoted) *outStr += fL1S(" | TokQuoted"); *outStr += QLatin1Char(')'); bool ok; switch (maskedTok) { case TokFuncTerminator: // Recursion, but not a sub-block return true; case TokArgSeparator: case TokValueTerminator: // Not recursion case TokTerminator: // Recursion, and limited by (sub-)block length case TokCondition: case TokReturn: case TokBreak: case TokNext: case TokNot: case TokAnd: case TokOr: ok = true; break; case TokTestCall: ok = getBlock(tokens, limit, offset, outStr, indent + 1); break; case TokBranch: ok = getSubBlock(tokens, limit, offset, outStr, indent, "then branch"); if (ok) ok = getSubBlock(tokens, limit, offset, outStr, indent, "else branch"); break; default: switch (maskedTok) { case TokAssign: case TokAppend: case TokAppendUnique: case TokRemove: case TokReplace: // The parameter is the sizehint for the output. // fallthrough case TokLine: { ushort dummy; ok = getUshort(tokens, limit, offset, &dummy, outStr); break; } case TokLiteral: case TokEnvVar: ok = getStr(tokens, limit, offset, outStr); break; case TokHashLiteral: case TokVariable: case TokProperty: ok = getHashStr(tokens, limit, offset, outStr); break; case TokFuncName: ok = getHashStr(tokens, limit, offset, outStr); if (ok) ok = getBlock(tokens, limit, offset, outStr, indent + 1); break; case TokForLoop: ok = getHashStr(tokens, limit, offset, outStr); if (ok) ok = getSubBlock(tokens, limit, offset, outStr, indent, "iterator"); if (ok) ok = getSubBlock(tokens, limit, offset, outStr, indent, "body"); break; case TokTestDef: case TokReplaceDef: ok = getHashStr(tokens, limit, offset, outStr); if (ok) ok = getSubBlock(tokens, limit, offset, outStr, indent, "body"); break; case TokBypassNesting: ok = getSubBlock(tokens, limit, offset, outStr, indent, "block"); break; default: Q_ASSERT(!"unhandled token"); } } if (!ok) return false; } return true; } QString QMakeParser::formatProBlock(const QString &block) { QString outStr; outStr += fL1S("\n << TS("); int offset = 0; getBlock(reinterpret_cast(block.constData()), block.length(), offset, &outStr, 0); outStr += QLatin1Char(')'); return outStr; } #endif // PROPARSER_DEBUG QT_END_NAMESPACE