1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the Qt Linguist of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21 ** included in the packaging of this file. Please review the following
22 ** information to ensure the GNU General Public License requirements will
23 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24 **
25 ** $QT_END_LICENSE$
26 **
27 ****************************************************************************/
28 
29 #include "qmakeparser.h"
30 
31 #include "qmakevfs.h"
32 #include "ioutils.h"
33 using namespace QMakeInternal;
34 
35 #include <qfile.h>
36 #ifdef PROPARSER_THREAD_SAFE
37 # include <qthreadpool.h>
38 #endif
39 
40 QT_BEGIN_NAMESPACE
41 
42 ///////////////////////////////////////////////////////////////////////
43 //
44 // ProFileCache
45 //
46 ///////////////////////////////////////////////////////////////////////
47 
ProFileCache()48 ProFileCache::ProFileCache()
49 {
50     QMakeVfs::ref();
51 }
52 
~ProFileCache()53 ProFileCache::~ProFileCache()
54 {
55     for (const Entry &ent : qAsConst(parsed_files))
56         if (ent.pro)
57             ent.pro->deref();
58     QMakeVfs::deref();
59 }
60 
discardFile(const QString & fileName,QMakeVfs * vfs)61 void ProFileCache::discardFile(const QString &fileName, QMakeVfs *vfs)
62 {
63     int eid = vfs->idForFileName(fileName, QMakeVfs::VfsExact | QMakeVfs::VfsAccessedOnly);
64     if (eid)
65         discardFile(eid);
66     int cid = vfs->idForFileName(fileName, QMakeVfs::VfsCumulative | QMakeVfs::VfsAccessedOnly);
67     if (cid && cid != eid)
68         discardFile(cid);
69 }
70 
discardFile(int id)71 void ProFileCache::discardFile(int id)
72 {
73 #ifdef PROPARSER_THREAD_SAFE
74     QMutexLocker lck(&mutex);
75 #endif
76     auto it = parsed_files.find(id);
77     if (it != parsed_files.end()) {
78 #ifdef PROPARSER_THREAD_SAFE
79         if (it->locker) {
80             if (!it->locker->done) {
81                 ++it->locker->waiters;
82                 it->locker->cond.wait(&mutex);
83                 if (!--it->locker->waiters) {
84                     delete it->locker;
85                     it->locker = 0;
86                 }
87             }
88         }
89 #endif
90         if (it->pro)
91             it->pro->deref();
92         parsed_files.erase(it);
93     }
94 }
95 
discardFiles(const QString & prefix,QMakeVfs * vfs)96 void ProFileCache::discardFiles(const QString &prefix, QMakeVfs *vfs)
97 {
98 #ifdef PROPARSER_THREAD_SAFE
99     QMutexLocker lck(&mutex);
100 #endif
101     auto it = parsed_files.begin(), end = parsed_files.end();
102     while (it != end) {
103         // Note: this is empty for virtual files from other VFSes.
104         QString fn = vfs->fileNameForId(it.key());
105         if (fn.startsWith(prefix)) {
106 #ifdef PROPARSER_THREAD_SAFE
107             if (it->locker) {
108                 if (!it->locker->done) {
109                     ++it->locker->waiters;
110                     it->locker->cond.wait(&mutex);
111                     if (!--it->locker->waiters) {
112                         delete it->locker;
113                         it->locker = 0;
114                     }
115                 }
116             }
117 #endif
118             if (it->pro)
119                 it->pro->deref();
120             it = parsed_files.erase(it);
121         } else {
122             ++it;
123         }
124     }
125 }
126 
127 ////////// Parser ///////////
128 
129 #define fL1S(s) QString::fromLatin1(s)
130 
131 namespace { // MSVC2010 doesn't seem to know the semantics of "static" ...
132 
133 static struct {
134     QString strelse;
135     QString strfor;
136     QString strdefineTest;
137     QString strdefineReplace;
138     QString strbypassNesting;
139     QString stroption;
140     QString strreturn;
141     QString strnext;
142     QString strbreak;
143     QString strhost_build;
144     QString strLINE;
145     QString strFILE;
146     QString strLITERAL_HASH;
147     QString strLITERAL_DOLLAR;
148     QString strLITERAL_WHITESPACE;
149 } statics;
150 
151 }
152 
initialize()153 void QMakeParser::initialize()
154 {
155     if (!statics.strelse.isNull())
156         return;
157 
158     statics.strelse = QLatin1String("else");
159     statics.strfor = QLatin1String("for");
160     statics.strdefineTest = QLatin1String("defineTest");
161     statics.strdefineReplace = QLatin1String("defineReplace");
162     statics.strbypassNesting = QLatin1String("bypassNesting");
163     statics.stroption = QLatin1String("option");
164     statics.strreturn = QLatin1String("return");
165     statics.strnext = QLatin1String("next");
166     statics.strbreak = QLatin1String("break");
167     statics.strhost_build = QLatin1String("host_build");
168     statics.strLINE = QLatin1String("_LINE_");
169     statics.strFILE = QLatin1String("_FILE_");
170     statics.strLITERAL_HASH = QLatin1String("LITERAL_HASH");
171     statics.strLITERAL_DOLLAR = QLatin1String("LITERAL_DOLLAR");
172     statics.strLITERAL_WHITESPACE = QLatin1String("LITERAL_WHITESPACE");
173 }
174 
QMakeParser(ProFileCache * cache,QMakeVfs * vfs,QMakeParserHandler * handler)175 QMakeParser::QMakeParser(ProFileCache *cache, QMakeVfs *vfs, QMakeParserHandler *handler)
176     : m_cache(cache)
177     , m_handler(handler)
178     , m_vfs(vfs)
179 {
180     // So that single-threaded apps don't have to call initialize() for now.
181     initialize();
182 }
183 
parsedProFile(const QString & fileName,ParseFlags flags)184 ProFile *QMakeParser::parsedProFile(const QString &fileName, ParseFlags flags)
185 {
186     ProFile *pro;
187     QMakeVfs::VfsFlags vfsFlags = ((flags & ParseCumulative) ? QMakeVfs::VfsCumulative
188                                                              : QMakeVfs::VfsExact);
189     int id = m_vfs->idForFileName(fileName, vfsFlags);
190     if ((flags & ParseUseCache) && m_cache) {
191         ProFileCache::Entry *ent;
192 #ifdef PROPARSER_THREAD_SAFE
193         QMutexLocker locker(&m_cache->mutex);
194 #endif
195         auto it = m_cache->parsed_files.find(id);
196         if (it != m_cache->parsed_files.end()) {
197             ent = &*it;
198 #ifdef PROPARSER_THREAD_SAFE
199             if (ent->locker && !ent->locker->done) {
200                 ++ent->locker->waiters;
201                 QThreadPool::globalInstance()->releaseThread();
202                 ent->locker->cond.wait(locker.mutex());
203                 QThreadPool::globalInstance()->reserveThread();
204                 if (!--ent->locker->waiters) {
205                     delete ent->locker;
206                     ent->locker = 0;
207                 }
208             }
209 #endif
210             if ((pro = ent->pro))
211                 pro->ref();
212         } else {
213             ent = &m_cache->parsed_files[id];
214 #ifdef PROPARSER_THREAD_SAFE
215             ent->locker = new ProFileCache::Entry::Locker;
216             locker.unlock();
217 #endif
218             QString contents;
219             if (readFile(id, flags, &contents)) {
220                 pro = parsedProBlock(QStringRef(&contents), id, fileName, 1, FullGrammar);
221                 pro->itemsRef()->squeeze();
222                 pro->ref();
223             } else {
224                 pro = nullptr;
225             }
226             ent->pro = pro;
227 #ifdef PROPARSER_THREAD_SAFE
228             locker.relock();
229             if (ent->locker->waiters) {
230                 ent->locker->done = true;
231                 ent->locker->cond.wakeAll();
232             } else {
233                 delete ent->locker;
234                 ent->locker = 0;
235             }
236 #endif
237         }
238     } else {
239         QString contents;
240         if (readFile(id, flags, &contents))
241             pro = parsedProBlock(QStringRef(&contents), id, fileName, 1, FullGrammar);
242         else
243             pro = nullptr;
244     }
245     return pro;
246 }
247 
parsedProBlock(const QStringRef & contents,int id,const QString & name,int line,SubGrammar grammar)248 ProFile *QMakeParser::parsedProBlock(
249         const QStringRef &contents, int id, const QString &name, int line, SubGrammar grammar)
250 {
251     ProFile *pro = new ProFile(id, name);
252     read(pro, contents, line, grammar);
253     return pro;
254 }
255 
discardFileFromCache(int id)256 void QMakeParser::discardFileFromCache(int id)
257 {
258     if (m_cache)
259         m_cache->discardFile(id);
260 }
261 
readFile(int id,ParseFlags flags,QString * contents)262 bool QMakeParser::readFile(int id, ParseFlags flags, QString *contents)
263 {
264     QString errStr;
265     QMakeVfs::ReadResult result = m_vfs->readFile(id, contents, &errStr);
266     if (result != QMakeVfs::ReadOk) {
267         if (m_handler && ((flags & ParseReportMissing) || result != QMakeVfs::ReadNotFound))
268             m_handler->message(QMakeParserHandler::ParserIoError,
269                                fL1S("Cannot read %1: %2").arg(m_vfs->fileNameForId(id), errStr));
270         return false;
271     }
272     return true;
273 }
274 
putTok(ushort * & tokPtr,ushort tok)275 void QMakeParser::putTok(ushort *&tokPtr, ushort tok)
276 {
277     *tokPtr++ = tok;
278 }
279 
putBlockLen(ushort * & tokPtr,uint len)280 void QMakeParser::putBlockLen(ushort *&tokPtr, uint len)
281 {
282     *tokPtr++ = (ushort)len;
283     *tokPtr++ = (ushort)(len >> 16);
284 }
285 
putBlock(ushort * & tokPtr,const ushort * buf,uint len)286 void QMakeParser::putBlock(ushort *&tokPtr, const ushort *buf, uint len)
287 {
288     memcpy(tokPtr, buf, len * 2);
289     tokPtr += len;
290 }
291 
putHashStr(ushort * & pTokPtr,const ushort * buf,uint len)292 void QMakeParser::putHashStr(ushort *&pTokPtr, const ushort *buf, uint len)
293 {
294     uint hash = ProString::hash((const QChar *)buf, len);
295     ushort *tokPtr = pTokPtr;
296     *tokPtr++ = (ushort)hash;
297     *tokPtr++ = (ushort)(hash >> 16);
298     *tokPtr++ = (ushort)len;
299     if (len) // buf may be nullptr; don't pass that to memcpy (-> undefined behavior)
300         memcpy(tokPtr, buf, len * 2);
301     pTokPtr = tokPtr + len;
302 }
303 
finalizeHashStr(ushort * buf,uint len)304 void QMakeParser::finalizeHashStr(ushort *buf, uint len)
305 {
306     buf[-4] = TokHashLiteral;
307     buf[-1] = len;
308     uint hash = ProString::hash((const QChar *)buf, len);
309     buf[-3] = (ushort)hash;
310     buf[-2] = (ushort)(hash >> 16);
311 }
312 
read(ProFile * pro,const QStringRef & in,int line,SubGrammar grammar)313 void QMakeParser::read(ProFile *pro, const QStringRef &in, int line, SubGrammar grammar)
314 {
315     m_proFile = pro;
316     m_lineNo = line;
317 
318     // Final precompiled token stream buffer
319     QString tokBuff;
320     // Worst-case size calculations:
321     // - line marker adds 1 (2-nl) to 1st token of each line
322     // - empty assignment "A=":2 =>
323     //   TokHashLiteral(1) + hash(2) + len(1) + "A"(1) + TokAssign(1) + size_hint(1) +
324     //   TokValueTerminator(1) == 8 (9)
325     // - non-empty assignment "A=B C":5 =>
326     //   TokHashLiteral(1) + hash(2) + len(1) + "A"(1) + TokAssign(1) + size_hint(1) +
327     //   TokLiteral(1) + len(1) + "B"(1) +
328     //   TokLiteral(1) + len(1) + "C"(1) + TokValueTerminator(1) == 14 (15)
329     // - variable expansion: "$$f":3 =>
330     //   TokVariable(1) + hash(2) + len(1) + "f"(1) = 5
331     // - function expansion: "$$f()":5 =>
332     //   TokFuncName(1) + hash(2) + len(1) + "f"(1) + TokFuncTerminator(1) = 6
333     // - test literal: "X":1 =>
334     //   TokHashLiteral(1) + hash(2) + len(1) + "A"(1) + TokCondition(1) = 6 (7)
335     // - scope: "X:":2 =>
336     //   TokHashLiteral(1) + hash(2) + len(1) + "A"(1) + TokCondition(1) +
337     //   TokBranch(1) + len(2) + ... + len(2) + ... == 11 (12)
338     // - test call: "X():":4 =>
339     //   TokHashLiteral(1) + hash(2) + len(1) + "A"(1) + TokTestCall(1) + TokFuncTerminator(1) +
340     //   TokBranch(1) + len(2) + ... + len(2) + ... == 12 (13)
341     // - "for(A,B):":9 =>
342     //   TokForLoop(1) + hash(2) + len(1) + "A"(1) +
343     //   len(2) + TokLiteral(1) + len(1) + "B"(1) + TokValueTerminator(1) +
344     //   len(2) + ... + TokTerminator(1) == 14 (15)
345     // One extra for possibly missing trailing newline.
346     tokBuff.reserve((in.size() + 1) * 7);
347     ushort *tokPtr = (ushort *)tokBuff.constData(); // Current writing position
348 
349     // Expression precompiler buffer.
350     QString xprBuff;
351     xprBuff.reserve(tokBuff.capacity()); // Excessive, but simple
352     ushort *buf = (ushort *)xprBuff.constData();
353 
354     // Parser state
355     m_blockstack.clear();
356     m_blockstack.resize(1);
357 
358     QStack<ParseCtx> xprStack;
359     xprStack.reserve(10);
360 
361     const ushort *cur = (const ushort *)in.unicode();
362     const ushort *inend = cur + in.length();
363     m_canElse = false;
364   freshLine:
365     m_state = StNew;
366     m_invert = 0;
367     m_operator = NoOperator;
368     m_markLine = m_lineNo;
369     m_inError = false;
370     int parens = 0; // Braces in value context
371     int argc = 0;
372     int wordCount = 0; // Number of words in currently accumulated expression
373     int lastIndent = 0; // Previous line's indentation, to detect accidental continuation abuse
374     bool lineMarked = true; // For in-expression markers
375     ushort needSep = TokNewStr; // Met unquoted whitespace
376     ushort quote = 0;
377     ushort term = 0;
378 
379     Context context;
380     ushort *ptr;
381     if (grammar == ValueGrammar) {
382         context = CtxPureValue;
383         ptr = tokPtr + 2;
384     } else {
385         context = CtxTest;
386         ptr = buf + 4;
387     }
388     ushort *xprPtr = ptr;
389 
390 #define FLUSH_LHS_LITERAL() \
391     do { \
392         if ((tlen = ptr - xprPtr)) { \
393             finalizeHashStr(xprPtr, tlen); \
394             if (needSep) { \
395                 wordCount++; \
396                 needSep = 0; \
397             } \
398         } else { \
399             ptr -= 4; \
400         } \
401     } while (0)
402 
403 #define FLUSH_RHS_LITERAL() \
404     do { \
405         if ((tlen = ptr - xprPtr)) { \
406             xprPtr[-2] = TokLiteral | needSep; \
407             xprPtr[-1] = tlen; \
408             if (needSep) { \
409                 wordCount++; \
410                 needSep = 0; \
411             } \
412         } else { \
413             ptr -= 2; \
414         } \
415     } while (0)
416 
417 #define FLUSH_LITERAL() \
418     do { \
419         if (context == CtxTest) \
420             FLUSH_LHS_LITERAL(); \
421         else \
422             FLUSH_RHS_LITERAL(); \
423     } while (0)
424 
425 #define FLUSH_VALUE_LIST() \
426     do { \
427         if (wordCount > 1) { \
428             xprPtr = tokPtr; \
429             if (*xprPtr == TokLine) \
430                 xprPtr += 2; \
431             tokPtr[-1] = ((*xprPtr & TokMask) == TokLiteral) ? wordCount : 0; \
432         } else { \
433             tokPtr[-1] = 0; \
434         } \
435         tokPtr = ptr; \
436         putTok(tokPtr, TokValueTerminator); \
437     } while (0)
438 
439     const ushort *end; // End of this line
440     const ushort *cptr; // Start of next line
441     bool lineCont;
442     int indent;
443 
444     if (context == CtxPureValue) {
445         end = inend;
446         cptr = nullptr;
447         lineCont = false;
448         indent = 0; // just gcc being stupid
449         goto nextChr;
450     }
451 
452     forever {
453         ushort c;
454 
455         // First, skip leading whitespace
456         for (indent = 0; ; ++cur, ++indent) {
457             if (cur == inend) {
458                 cur = nullptr;
459                 goto flushLine;
460             }
461             c = *cur;
462             if (c == '\n') {
463                 ++cur;
464                 goto flushLine;
465             }
466             if (c != ' ' && c != '\t' && c != '\r')
467                 break;
468         }
469 
470         // Then strip comments. Yep - no escaping is possible.
471         for (cptr = cur;; ++cptr) {
472             if (cptr == inend) {
473                 end = cptr;
474                 break;
475             }
476             c = *cptr;
477             if (c == '#') {
478                 end = cptr;
479                 while (++cptr < inend) {
480                     if (*cptr == '\n') {
481                         ++cptr;
482                         break;
483                     }
484                 }
485                 if (end == cur) { // Line with only a comment (sans whitespace)
486                     if (m_markLine == m_lineNo)
487                         m_markLine++;
488                     // Qmake bizarreness: such lines do not affect line continuations
489                     goto ignore;
490                 }
491                 break;
492             }
493             if (c == '\n') {
494                 end = cptr++;
495                 break;
496             }
497         }
498 
499         // Then look for line continuations. Yep - no escaping here as well.
500         forever {
501             // We don't have to check for underrun here, as we already determined
502             // that the line is non-empty.
503             ushort ec = *(end - 1);
504             if (ec == '\\') {
505                 --end;
506                 lineCont = true;
507                 break;
508             }
509             if (ec != ' ' && ec != '\t' && ec != '\r') {
510                 lineCont = false;
511                 break;
512             }
513             --end;
514         }
515 
516             // Finally, do the tokenization
517             ushort tok, rtok;
518             int tlen;
519           newWord:
520             do {
521                 if (cur == end)
522                     goto lineEnd;
523                 c = *cur++;
524             } while (c == ' ' || c == '\t');
525             forever {
526                 if (c == '$') {
527                     if (*cur == '$') { // may be EOF, EOL, WS, '#' or '\\' if past end
528                         cur++;
529                         FLUSH_LITERAL();
530                         if (!lineMarked) {
531                             lineMarked = true;
532                             *ptr++ = TokLine;
533                             *ptr++ = (ushort)m_lineNo;
534                         }
535                         term = 0;
536                         tok = TokVariable;
537                         c = *cur;
538                         if (c == '[') {
539                             ptr += 4;
540                             tok = TokProperty;
541                             term = ']';
542                             c = *++cur;
543                         } else if (c == '{') {
544                             ptr += 4;
545                             term = '}';
546                             c = *++cur;
547                         } else if (c == '(') {
548                             ptr += 2;
549                             tok = TokEnvVar;
550                             term = ')';
551                             c = *++cur;
552                         } else {
553                             ptr += 4;
554                         }
555                         xprPtr = ptr;
556                         rtok = tok;
557                         while ((c & 0xFF00) || c == '.' || c == '_' ||
558                                (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
559                                (c >= '0' && c <= '9') || (c == '/' && term)) {
560                             *ptr++ = c;
561                             if (++cur == end) {
562                                 c = 0;
563                                 goto notfunc;
564                             }
565                             c = *cur;
566                         }
567                         if (tok == TokVariable && c == '(')
568                             tok = TokFuncName;
569                       notfunc:
570                         if (ptr == xprPtr)
571                             languageWarning(fL1S("Missing name in expansion"));
572                         if (quote)
573                             tok |= TokQuoted;
574                         if (needSep) {
575                             tok |= needSep;
576                             wordCount++;
577                         }
578                         tlen = ptr - xprPtr;
579                         if (rtok != TokVariable
580                             || !resolveVariable(xprPtr, tlen, needSep, &ptr,
581                                                 &buf, &xprBuff, &tokPtr, &tokBuff, cur, in)) {
582                             if (rtok == TokVariable || rtok == TokProperty) {
583                                 xprPtr[-4] = tok;
584                                 uint hash = ProString::hash((const QChar *)xprPtr, tlen);
585                                 xprPtr[-3] = (ushort)hash;
586                                 xprPtr[-2] = (ushort)(hash >> 16);
587                                 xprPtr[-1] = tlen;
588                             } else {
589                                 xprPtr[-2] = tok;
590                                 xprPtr[-1] = tlen;
591                             }
592                         }
593                         if ((tok & TokMask) == TokFuncName) {
594                             cur++;
595                           funcCall:
596                             {
597                                 xprStack.resize(xprStack.size() + 1);
598                                 ParseCtx &top = xprStack.top();
599                                 top.parens = parens;
600                                 top.quote = quote;
601                                 top.terminator = term;
602                                 top.context = context;
603                                 top.argc = argc;
604                                 top.wordCount = wordCount;
605                             }
606                             parens = 0;
607                             quote = 0;
608                             term = 0;
609                             argc = 1;
610                             context = CtxArgs;
611                           nextToken:
612                             wordCount = 0;
613                           nextWord:
614                             ptr += (context == CtxTest) ? 4 : 2;
615                             xprPtr = ptr;
616                             needSep = TokNewStr;
617                             goto newWord;
618                         }
619                         if (term) {
620                           checkTerm:
621                             if (c != term) {
622                                 parseError(fL1S("Missing %1 terminator [found %2]")
623                                     .arg(QChar(term))
624                                     .arg(c ? QString(c) : QString::fromLatin1("end-of-line")));
625                                 m_inError = true;
626                                 // Just parse on, as if there was a terminator ...
627                             } else {
628                                 cur++;
629                             }
630                         }
631                       joinToken:
632                         ptr += (context == CtxTest) ? 4 : 2;
633                         xprPtr = ptr;
634                         needSep = 0;
635                         goto nextChr;
636                     }
637                 } else if (c == '\\') {
638                     static const char symbols[] = "[]{}()$\\'\"";
639                     ushort c2;
640                     if (cur != end && !((c2 = *cur) & 0xff00) && strchr(symbols, c2)) {
641                         c = c2;
642                         cur++;
643                     } else {
644                         deprecationWarning(fL1S("Unescaped backslashes are deprecated"));
645                     }
646                 } else if (quote) {
647                     if (c == quote) {
648                         quote = 0;
649                         goto nextChr;
650                     } else if (c == '!' && ptr == xprPtr && context == CtxTest) {
651                         m_invert++;
652                         goto nextChr;
653                     }
654                 } else if (c == '\'' || c == '"') {
655                     quote = c;
656                     goto nextChr;
657                 } else if (context == CtxArgs) {
658                     // Function arg context
659                     if (c == ' ' || c == '\t') {
660                         FLUSH_RHS_LITERAL();
661                         goto nextWord;
662                     } else if (c == '(') {
663                         ++parens;
664                     } else if (c == ')') {
665                         if (--parens < 0) {
666                             FLUSH_RHS_LITERAL();
667                             *ptr++ = TokFuncTerminator;
668                             int theargc = argc;
669                             {
670                                 ParseCtx &top = xprStack.top();
671                                 parens = top.parens;
672                                 quote = top.quote;
673                                 term = top.terminator;
674                                 context = top.context;
675                                 argc = top.argc;
676                                 wordCount = top.wordCount;
677                                 xprStack.resize(xprStack.size() - 1);
678                             }
679                             if (term == ':') {
680                                 finalizeCall(tokPtr, buf, ptr, theargc);
681                                 goto nextItem;
682                             } else if (term == '}') {
683                                 c = (cur == end) ? 0 : *cur;
684                                 goto checkTerm;
685                             } else {
686                                 Q_ASSERT(!term);
687                                 goto joinToken;
688                             }
689                         }
690                     } else if (!parens && c == ',') {
691                         FLUSH_RHS_LITERAL();
692                         *ptr++ = TokArgSeparator;
693                         argc++;
694                         goto nextToken;
695                     }
696                 } else if (context == CtxTest) {
697                     // Test or LHS context
698                     if (c == ' ' || c == '\t') {
699                         FLUSH_LHS_LITERAL();
700                         goto nextWord;
701                     } else if (c == '(') {
702                         FLUSH_LHS_LITERAL();
703                         if (wordCount != 1) {
704                             if (wordCount)
705                                 parseError(fL1S("Extra characters after test expression."));
706                             else
707                                 parseError(fL1S("Opening parenthesis without prior test name."));
708                             ptr = buf; // Put empty function name
709                         }
710                         *ptr++ = TokTestCall;
711                         term = ':';
712                         goto funcCall;
713                     } else if (c == '!' && ptr == xprPtr) {
714                         m_invert++;
715                         goto nextChr;
716                     } else if (c == ':') {
717                         FLUSH_LHS_LITERAL();
718                         finalizeCond(tokPtr, buf, ptr, wordCount);
719                         warnOperator("in front of AND operator");
720                         if (m_state == StNew)
721                             parseError(fL1S("AND operator without prior condition."));
722                         else
723                             m_operator = AndOperator;
724                       nextItem:
725                         ptr = buf;
726                         goto nextToken;
727                     } else if (c == '|') {
728                         FLUSH_LHS_LITERAL();
729                         finalizeCond(tokPtr, buf, ptr, wordCount);
730                         warnOperator("in front of OR operator");
731                         if (m_state != StCond)
732                             parseError(fL1S("OR operator without prior condition."));
733                         else
734                             m_operator = OrOperator;
735                         goto nextItem;
736                     } else if (c == '{') {
737                         FLUSH_LHS_LITERAL();
738                         finalizeCond(tokPtr, buf, ptr, wordCount);
739                         if (m_operator == AndOperator) {
740                             languageWarning(fL1S("Excess colon in front of opening brace."));
741                             m_operator = NoOperator;
742                         }
743                         failOperator("in front of opening brace");
744                         flushCond(tokPtr);
745                         m_state = StNew; // Reset possible StCtrl, so colons get rejected.
746                         ++m_blockstack.top().braceLevel;
747                         if (grammar == TestGrammar)
748                             parseError(fL1S("Opening scope not permitted in this context."));
749                         goto nextItem;
750                     } else if (c == '}') {
751                         FLUSH_LHS_LITERAL();
752                         finalizeCond(tokPtr, buf, ptr, wordCount);
753                         m_state = StNew; // De-facto newline
754                       closeScope:
755                         flushScopes(tokPtr);
756                         failOperator("in front of closing brace");
757                         if (!m_blockstack.top().braceLevel) {
758                             parseError(fL1S("Excess closing brace."));
759                         } else if (!--m_blockstack.top().braceLevel
760                                    && m_blockstack.count() != 1) {
761                             leaveScope(tokPtr);
762                             m_state = StNew;
763                             m_canElse = false;
764                             m_markLine = m_lineNo;
765                         }
766                         goto nextItem;
767                     } else if (c == '+') {
768                         tok = TokAppend;
769                         goto do2Op;
770                     } else if (c == '-') {
771                         tok = TokRemove;
772                         goto do2Op;
773                     } else if (c == '*') {
774                         tok = TokAppendUnique;
775                         goto do2Op;
776                     } else if (c == '~') {
777                         tok = TokReplace;
778                       do2Op:
779                         if (*cur == '=') {
780                             cur++;
781                             goto doOp;
782                         }
783                     } else if (c == '=') {
784                         tok = TokAssign;
785                       doOp:
786                         FLUSH_LHS_LITERAL();
787                         flushCond(tokPtr);
788                         acceptColon("in front of assignment");
789                         putLineMarker(tokPtr);
790                         if (grammar == TestGrammar) {
791                             parseError(fL1S("Assignment not permitted in this context."));
792                         } else if (wordCount != 1) {
793                             parseError(fL1S("Assignment needs exactly one word on the left hand side."));
794                             // Put empty variable name.
795                         } else {
796                             putBlock(tokPtr, buf, ptr - buf);
797                         }
798                         putTok(tokPtr, tok);
799                         context = CtxValue;
800                         ptr = ++tokPtr;
801                         goto nextToken;
802                     }
803                 } else if (context == CtxValue) {
804                     if (c == ' ' || c == '\t') {
805                         FLUSH_RHS_LITERAL();
806                         goto nextWord;
807                     } else if (c == '{') {
808                         ++parens;
809                     } else if (c == '}') {
810                         if (!parens) {
811                             FLUSH_RHS_LITERAL();
812                             FLUSH_VALUE_LIST();
813                             context = CtxTest;
814                             goto closeScope;
815                         }
816                         --parens;
817                     } else if (c == '=') {
818                         if (indent < lastIndent)
819                             languageWarning(fL1S("Possible accidental line continuation"));
820                     }
821                 }
822                 *ptr++ = c;
823               nextChr:
824                 if (cur == end)
825                     goto lineEnd;
826                 c = *cur++;
827             }
828 
829           lineEnd:
830             if (lineCont) {
831                 if (quote) {
832                     *ptr++ = ' ';
833                 } else {
834                     FLUSH_LITERAL();
835                     needSep = TokNewStr;
836                     ptr += (context == CtxTest) ? 4 : 2;
837                     xprPtr = ptr;
838                 }
839             } else {
840                 cur = cptr;
841               flushLine:
842                 FLUSH_LITERAL();
843                 if (quote) {
844                     parseError(fL1S("Missing closing %1 quote").arg(QChar(quote)));
845                     if (!xprStack.isEmpty()) {
846                         context = xprStack.at(0).context;
847                         xprStack.clear();
848                     }
849                     goto flErr;
850                 } else if (!xprStack.isEmpty()) {
851                     parseError(fL1S("Missing closing parenthesis in function call"));
852                     context = xprStack.at(0).context;
853                     xprStack.clear();
854                   flErr:
855                     pro->setOk(false);
856                     if (context == CtxValue) {
857                         tokPtr[-1] = 0; // sizehint
858                         putTok(tokPtr, TokValueTerminator);
859                     } else if (context == CtxPureValue) {
860                         putTok(tokPtr, TokValueTerminator);
861                     } else {
862                         bogusTest(tokPtr, QString());
863                     }
864                 } else if (context == CtxValue) {
865                     FLUSH_VALUE_LIST();
866                     if (parens)
867                         languageWarning(fL1S("Possible braces mismatch"));
868                 } else if (context == CtxPureValue) {
869                     tokPtr = ptr;
870                     putTok(tokPtr, TokValueTerminator);
871                 } else {
872                     finalizeCond(tokPtr, buf, ptr, wordCount);
873                     warnOperator("at end of line");
874                 }
875                 if (!cur)
876                     break;
877                 ++m_lineNo;
878                 goto freshLine;
879             }
880 
881         lastIndent = indent;
882         lineMarked = false;
883       ignore:
884         cur = cptr;
885         ++m_lineNo;
886     }
887 
888     flushScopes(tokPtr);
889     if (m_blockstack.size() > 1 || m_blockstack.top().braceLevel)
890         parseError(fL1S("Missing closing brace(s)."));
891     while (m_blockstack.size())
892         leaveScope(tokPtr);
893     tokBuff.resize(tokPtr - (ushort *)tokBuff.constData()); // Reserved capacity stays
894     *pro->itemsRef() = tokBuff;
895 
896 #undef FLUSH_VALUE_LIST
897 #undef FLUSH_LITERAL
898 #undef FLUSH_LHS_LITERAL
899 #undef FLUSH_RHS_LITERAL
900 }
901 
putLineMarker(ushort * & tokPtr)902 void QMakeParser::putLineMarker(ushort *&tokPtr)
903 {
904     if (m_markLine) {
905         *tokPtr++ = TokLine;
906         *tokPtr++ = (ushort)m_markLine;
907         m_markLine = 0;
908     }
909 }
910 
enterScope(ushort * & tokPtr,bool special,ScopeState state)911 void QMakeParser::enterScope(ushort *&tokPtr, bool special, ScopeState state)
912 {
913     uchar nest = m_blockstack.top().nest;
914     m_blockstack.resize(m_blockstack.size() + 1);
915     m_blockstack.top().special = special;
916     m_blockstack.top().start = tokPtr;
917     m_blockstack.top().nest = nest;
918     tokPtr += 2;
919     m_state = state;
920     m_canElse = false;
921     if (special)
922         m_markLine = m_lineNo;
923 }
924 
leaveScope(ushort * & tokPtr)925 void QMakeParser::leaveScope(ushort *&tokPtr)
926 {
927     if (m_blockstack.top().inBranch) {
928         // Put empty else block
929         putBlockLen(tokPtr, 0);
930     }
931     if (ushort *start = m_blockstack.top().start) {
932         putTok(tokPtr, TokTerminator);
933         uint len = tokPtr - start - 2;
934         start[0] = (ushort)len;
935         start[1] = (ushort)(len >> 16);
936     }
937     m_blockstack.resize(m_blockstack.size() - 1);
938 }
939 
940 // If we are on a fresh line, close all open one-line scopes.
flushScopes(ushort * & tokPtr)941 void QMakeParser::flushScopes(ushort *&tokPtr)
942 {
943     if (m_state == StNew) {
944         while (!m_blockstack.top().braceLevel && m_blockstack.size() > 1)
945             leaveScope(tokPtr);
946         if (m_blockstack.top().inBranch) {
947             m_blockstack.top().inBranch = false;
948             // Put empty else block
949             putBlockLen(tokPtr, 0);
950         }
951         m_canElse = false;
952     }
953 }
954 
955 // If there is a pending conditional, enter a new scope, otherwise flush scopes.
flushCond(ushort * & tokPtr)956 void QMakeParser::flushCond(ushort *&tokPtr)
957 {
958     if (m_state == StCond) {
959         putTok(tokPtr, TokBranch);
960         m_blockstack.top().inBranch = true;
961         enterScope(tokPtr, false, StNew);
962     } else {
963         flushScopes(tokPtr);
964     }
965 }
966 
warnOperator(const char * msg)967 void QMakeParser::warnOperator(const char *msg)
968 {
969     if (m_invert) {
970         languageWarning(fL1S("Stray NOT operator %1.").arg(fL1S(msg)));
971         m_invert = 0;
972     }
973     if (m_operator == AndOperator) {
974         languageWarning(fL1S("Stray AND operator %1.").arg(fL1S(msg)));
975         m_operator = NoOperator;
976     } else if (m_operator == OrOperator) {
977         languageWarning(fL1S("Stray OR operator %1.").arg(fL1S(msg)));
978         m_operator = NoOperator;
979     }
980 }
981 
failOperator(const char * msg)982 bool QMakeParser::failOperator(const char *msg)
983 {
984     bool fail = false;
985     if (m_invert) {
986         parseError(fL1S("Unexpected NOT operator %1.").arg(fL1S(msg)));
987         m_invert = 0;
988         fail = true;
989     }
990     if (m_operator == AndOperator) {
991         parseError(fL1S("Unexpected AND operator %1.").arg(fL1S(msg)));
992         m_operator = NoOperator;
993         fail = true;
994     } else if (m_operator == OrOperator) {
995         parseError(fL1S("Unexpected OR operator %1.").arg(fL1S(msg)));
996         m_operator = NoOperator;
997         fail = true;
998     }
999     return fail;
1000 }
1001 
acceptColon(const char * msg)1002 bool QMakeParser::acceptColon(const char *msg)
1003 {
1004     if (m_operator == AndOperator)
1005         m_operator = NoOperator;
1006     return !failOperator(msg);
1007 }
1008 
putOperator(ushort * & tokPtr)1009 void QMakeParser::putOperator(ushort *&tokPtr)
1010 {
1011     if (m_operator== AndOperator) {
1012         // A colon must be used after else and for() if no brace is used,
1013         // but in this case it is obviously not a binary operator.
1014         if (m_state == StCond)
1015             putTok(tokPtr, TokAnd);
1016         m_operator = NoOperator;
1017     } else if (m_operator == OrOperator) {
1018         putTok(tokPtr, TokOr);
1019         m_operator = NoOperator;
1020     }
1021 }
1022 
finalizeTest(ushort * & tokPtr)1023 void QMakeParser::finalizeTest(ushort *&tokPtr)
1024 {
1025     flushScopes(tokPtr);
1026     putLineMarker(tokPtr);
1027     putOperator(tokPtr);
1028     if (m_invert & 1)
1029         putTok(tokPtr, TokNot);
1030     m_invert = 0;
1031     m_state = StCond;
1032     m_canElse = true;
1033 }
1034 
bogusTest(ushort * & tokPtr,const QString & msg)1035 void QMakeParser::bogusTest(ushort *&tokPtr, const QString &msg)
1036 {
1037     if (!msg.isEmpty())
1038         parseError(msg);
1039     flushScopes(tokPtr);
1040     m_operator = NoOperator;
1041     m_invert = 0;
1042     m_state = StCond;
1043     m_canElse = true;
1044 }
1045 
finalizeCond(ushort * & tokPtr,ushort * uc,ushort * ptr,int wordCount)1046 void QMakeParser::finalizeCond(ushort *&tokPtr, ushort *uc, ushort *ptr, int wordCount)
1047 {
1048     if (wordCount != 1) {
1049         if (wordCount)
1050             bogusTest(tokPtr, fL1S("Extra characters after test expression."));
1051         return;
1052     }
1053 
1054     // Check for magic tokens
1055     if (*uc == TokHashLiteral) {
1056         uint nlen = uc[3];
1057         ushort *uce = uc + 4 + nlen;
1058         if (uce == ptr) {
1059             m_tmp.setRawData((QChar *)uc + 4, nlen);
1060             if (!m_tmp.compare(statics.strelse, Qt::CaseInsensitive)) {
1061                 if (failOperator("in front of else"))
1062                     return;
1063                 BlockScope &top = m_blockstack.top();
1064                 if (m_canElse && (!top.special || top.braceLevel)) {
1065                     // A list of tests (the last one likely with side effects),
1066                     // but no assignment, scope, etc.
1067                     putTok(tokPtr, TokBranch);
1068                     // Put empty then block
1069                     putBlockLen(tokPtr, 0);
1070                     enterScope(tokPtr, false, StCtrl);
1071                     return;
1072                 }
1073                 forever {
1074                     BlockScope &top = m_blockstack.top();
1075                     if (top.inBranch && (!top.special || top.braceLevel)) {
1076                         top.inBranch = false;
1077                         enterScope(tokPtr, false, StCtrl);
1078                         return;
1079                     }
1080                     if (top.braceLevel || m_blockstack.size() == 1)
1081                         break;
1082                     leaveScope(tokPtr);
1083                 }
1084                 parseError(fL1S("Unexpected 'else'."));
1085                 return;
1086             }
1087         }
1088     }
1089 
1090     finalizeTest(tokPtr);
1091     putBlock(tokPtr, uc, ptr - uc);
1092     putTok(tokPtr, TokCondition);
1093 }
1094 
finalizeCall(ushort * & tokPtr,ushort * uc,ushort * ptr,int argc)1095 void QMakeParser::finalizeCall(ushort *&tokPtr, ushort *uc, ushort *ptr, int argc)
1096 {
1097     // Check for magic tokens
1098     if (*uc == TokHashLiteral) {
1099         uint nlen = uc[3];
1100         ushort *uce = uc + 4 + nlen;
1101         if (*uce == TokTestCall) {
1102             uce++;
1103             m_tmp.setRawData((QChar *)uc + 4, nlen);
1104             const QString *defName;
1105             ushort defType;
1106             if (m_tmp == statics.strfor) {
1107                 if (!acceptColon("in front of for()")) {
1108                     bogusTest(tokPtr, QString());
1109                     return;
1110                 }
1111                 flushCond(tokPtr);
1112                 putLineMarker(tokPtr);
1113                 --ptr;
1114                 Q_ASSERT(*ptr == TokFuncTerminator);
1115                 if (*uce == (TokLiteral|TokNewStr)) {
1116                     nlen = uce[1];
1117                     uc = uce + 2 + nlen;
1118                     if (uc == ptr) {
1119                         // for(literal) (only "ever" would be legal if qmake was sane)
1120                         putTok(tokPtr, TokForLoop);
1121                         putHashStr(tokPtr, nullptr, (uint)0);
1122                         putBlockLen(tokPtr, 1 + 3 + nlen + 1);
1123                         putTok(tokPtr, TokHashLiteral);
1124                         putHashStr(tokPtr, uce + 2, nlen);
1125                       didFor:
1126                         putTok(tokPtr, TokValueTerminator);
1127                         enterScope(tokPtr, true, StCtrl);
1128                         m_blockstack.top().nest |= NestLoop;
1129                         return;
1130                     } else if (*uc == TokArgSeparator && argc == 2) {
1131                         // for(var, something)
1132                         uc++;
1133                         putTok(tokPtr, TokForLoop);
1134                         putHashStr(tokPtr, uce + 2, nlen);
1135                       doFor:
1136                         nlen = ptr - uc;
1137                         putBlockLen(tokPtr, nlen + 1);
1138                         putBlock(tokPtr, uc, nlen);
1139                         goto didFor;
1140                     }
1141                 } else if (argc == 1) {
1142                     // for(non-literal) (this wouldn't be here if qmake was sane)
1143                     putTok(tokPtr, TokForLoop);
1144                     putHashStr(tokPtr, nullptr, (uint)0);
1145                     uc = uce;
1146                     goto doFor;
1147                 }
1148                 parseError(fL1S("Syntax is for(var, list), for(var, forever) or for(ever)."));
1149                 return;
1150             } else if (m_tmp == statics.strdefineReplace) {
1151                 defName = &statics.strdefineReplace;
1152                 defType = TokReplaceDef;
1153                 goto deffunc;
1154             } else if (m_tmp == statics.strdefineTest) {
1155                 defName = &statics.strdefineTest;
1156                 defType = TokTestDef;
1157               deffunc:
1158                 if (m_invert) {
1159                     bogusTest(tokPtr, fL1S("Unexpected NOT operator in front of function definition."));
1160                     return;
1161                 }
1162                 flushScopes(tokPtr);
1163                 putLineMarker(tokPtr);
1164                 if (*uce == (TokLiteral|TokNewStr)) {
1165                     uint nlen = uce[1];
1166                     if (uce[nlen + 2] == TokFuncTerminator) {
1167                         putOperator(tokPtr);
1168                         putTok(tokPtr, defType);
1169                         putHashStr(tokPtr, uce + 2, nlen);
1170                         enterScope(tokPtr, true, StCtrl);
1171                         m_blockstack.top().nest = NestFunction;
1172                         return;
1173                     }
1174                 }
1175                 parseError(fL1S("%1(function) requires one literal argument.").arg(*defName));
1176                 return;
1177             } else if (m_tmp == statics.strbypassNesting) {
1178                 if (*uce != TokFuncTerminator) {
1179                     bogusTest(tokPtr, fL1S("%1() requires zero arguments.").arg(m_tmp));
1180                     return;
1181                 }
1182                 if (!(m_blockstack.top().nest & NestFunction)) {
1183                     bogusTest(tokPtr, fL1S("Unexpected %1().").arg(m_tmp));
1184                     return;
1185                 }
1186                 if (m_invert) {
1187                     bogusTest(tokPtr, fL1S("Unexpected NOT operator in front of %1().").arg(m_tmp));
1188                     return;
1189                 }
1190                 flushScopes(tokPtr);
1191                 putLineMarker(tokPtr);
1192                 putOperator(tokPtr);
1193                 putTok(tokPtr, TokBypassNesting);
1194                 enterScope(tokPtr, true, StCtrl);
1195                 return;
1196             } else if (m_tmp == statics.strreturn) {
1197                 if (m_blockstack.top().nest & NestFunction) {
1198                     if (argc > 1) {
1199                         bogusTest(tokPtr, fL1S("return() requires zero or one argument."));
1200                         return;
1201                     }
1202                 } else {
1203                     if (*uce != TokFuncTerminator) {
1204                         bogusTest(tokPtr, fL1S("Top-level return() requires zero arguments."));
1205                         return;
1206                     }
1207                 }
1208                 defType = TokReturn;
1209                 goto ctrlstm2;
1210             } else if (m_tmp == statics.strnext) {
1211                 defType = TokNext;
1212                 goto ctrlstm;
1213             } else if (m_tmp == statics.strbreak) {
1214                 defType = TokBreak;
1215               ctrlstm:
1216                 if (*uce != TokFuncTerminator) {
1217                     bogusTest(tokPtr, fL1S("%1() requires zero arguments.").arg(m_tmp));
1218                     return;
1219                 }
1220                 if (!(m_blockstack.top().nest & NestLoop)) {
1221                     bogusTest(tokPtr, fL1S("Unexpected %1().").arg(m_tmp));
1222                     return;
1223                 }
1224               ctrlstm2:
1225                 if (m_invert) {
1226                     bogusTest(tokPtr, fL1S("Unexpected NOT operator in front of %1().").arg(m_tmp));
1227                     return;
1228                 }
1229                 finalizeTest(tokPtr);
1230                 putBlock(tokPtr, uce, ptr - uce - 1); // Only for TokReturn
1231                 putTok(tokPtr, defType);
1232                 return;
1233             } else if (m_tmp == statics.stroption) {
1234                 if (m_state != StNew || m_blockstack.top().braceLevel || m_blockstack.size() > 1
1235                         || m_invert || m_operator != NoOperator) {
1236                     bogusTest(tokPtr, fL1S("option() must appear outside any control structures."));
1237                     return;
1238                 }
1239                 if (*uce == (TokLiteral|TokNewStr)) {
1240                     uint nlen = uce[1];
1241                     if (uce[nlen + 2] == TokFuncTerminator) {
1242                         m_tmp.setRawData((QChar *)uce + 2, nlen);
1243                         if (m_tmp == statics.strhost_build)
1244                             m_proFile->setHostBuild(true);
1245                         else
1246                             parseError(fL1S("Unknown option() %1.").arg(m_tmp));
1247                         return;
1248                     }
1249                 }
1250                 parseError(fL1S("option() requires one literal argument."));
1251                 return;
1252             }
1253         }
1254     }
1255 
1256     finalizeTest(tokPtr);
1257     putBlock(tokPtr, uc, ptr - uc);
1258 }
1259 
resolveVariable(ushort * xprPtr,int tlen,int needSep,ushort ** ptr,ushort ** buf,QString * xprBuff,ushort ** tokPtr,QString * tokBuff,const ushort * cur,const QStringRef & in)1260 bool QMakeParser::resolveVariable(ushort *xprPtr, int tlen, int needSep, ushort **ptr,
1261                                   ushort **buf, QString *xprBuff,
1262                                   ushort **tokPtr, QString *tokBuff,
1263                                   const ushort *cur, const QStringRef &in)
1264 {
1265     QString out;
1266     m_tmp.setRawData((const QChar *)xprPtr, tlen);
1267     if (m_tmp == statics.strLINE) {
1268         out.setNum(m_lineNo);
1269     } else if (m_tmp == statics.strFILE) {
1270         out = m_proFile->fileName();
1271         // The string is typically longer than the variable reference, so we need
1272         // to ensure that there is enough space in the output buffer - as unlikely
1273         // as an overflow is to actually happen in practice.
1274         int need = (in.length() - (cur - (const ushort *)in.constData()) + 2) * 5 + out.length();
1275         int tused = *tokPtr - (ushort *)tokBuff->constData();
1276         int xused;
1277         int total;
1278         bool ptrFinal = xprPtr >= (ushort *)tokBuff->constData()
1279                 && xprPtr < (ushort *)tokBuff->constData() + tokBuff->capacity();
1280         if (ptrFinal) {
1281             xused = xprPtr - (ushort *)tokBuff->constData();
1282             total = xused + need;
1283         } else {
1284             xused = xprPtr - *buf;
1285             total = tused + xused + need;
1286         }
1287         if (tokBuff->capacity() < total) {
1288             tokBuff->reserve(total);
1289             *tokPtr = (ushort *)tokBuff->constData() + tused;
1290             xprBuff->reserve(total);
1291             *buf = (ushort *)xprBuff->constData();
1292             xprPtr = (ptrFinal ? (ushort *)tokBuff->constData() : *buf) + xused;
1293         }
1294     } else if (m_tmp == statics.strLITERAL_HASH) {
1295         out = QLatin1String("#");
1296     } else if (m_tmp == statics.strLITERAL_DOLLAR) {
1297         out = QLatin1String("$");
1298     } else if (m_tmp == statics.strLITERAL_WHITESPACE) {
1299         out = QLatin1String("\t");
1300     } else {
1301         return false;
1302     }
1303     xprPtr -= 2; // Was set up for variable reference
1304     xprPtr[-2] = TokLiteral | needSep;
1305     xprPtr[-1] = out.length();
1306     memcpy(xprPtr, out.constData(), out.length() * 2);
1307     *ptr = xprPtr + out.length();
1308     return true;
1309 }
1310 
message(int type,const QString & msg) const1311 void QMakeParser::message(int type, const QString &msg) const
1312 {
1313     if (!m_inError && m_handler)
1314         m_handler->message(type, msg, m_proFile->fileName(), m_lineNo);
1315 }
1316 
1317 #ifdef PROPARSER_DEBUG
1318 
1319 #define BOUNDS_CHECK(need) \
1320     do { \
1321         int have = limit - offset; \
1322         if (have < (int)need) { \
1323             *outStr += fL1S("<out of bounds (need %1, got %2)>").arg(need).arg(have); \
1324             return false; \
1325         } \
1326     } while (0)
1327 
getRawUshort(const ushort * tokens,int limit,int & offset,ushort * outVal,QString * outStr)1328 static bool getRawUshort(const ushort *tokens, int limit, int &offset, ushort *outVal, QString *outStr)
1329 {
1330     BOUNDS_CHECK(1);
1331     uint val = tokens[offset++];
1332     *outVal = val;
1333     return true;
1334 }
1335 
getUshort(const ushort * tokens,int limit,int & offset,ushort * outVal,QString * outStr)1336 static bool getUshort(const ushort *tokens, int limit, int &offset, ushort *outVal, QString *outStr)
1337 {
1338     *outStr += fL1S(" << H(");
1339     if (!getRawUshort(tokens, limit, offset, outVal, outStr))
1340         return false;
1341     *outStr += QString::number(*outVal) + QLatin1Char(')');
1342     return true;
1343 }
1344 
getRawUint(const ushort * tokens,int limit,int & offset,uint * outVal,QString * outStr)1345 static bool getRawUint(const ushort *tokens, int limit, int &offset, uint *outVal, QString *outStr)
1346 {
1347     BOUNDS_CHECK(2);
1348     uint val = tokens[offset++];
1349     val |= (uint)tokens[offset++] << 16;
1350     *outVal = val;
1351     return true;
1352 }
1353 
getUint(const ushort * tokens,int limit,int & offset,uint * outVal,QString * outStr)1354 static bool getUint(const ushort *tokens, int limit, int &offset, uint *outVal, QString *outStr)
1355 {
1356     *outStr += fL1S(" << I(");
1357     if (!getRawUint(tokens, limit, offset, outVal, outStr))
1358         return false;
1359     *outStr += QString::number(*outVal) + QLatin1Char(')');
1360     return true;
1361 }
1362 
getRawStr(const ushort * tokens,int limit,int & offset,int strLen,QString * outStr)1363 static bool getRawStr(const ushort *tokens, int limit, int &offset, int strLen, QString *outStr)
1364 {
1365     BOUNDS_CHECK(strLen);
1366     *outStr += fL1S("L\"");
1367     bool attn = false;
1368     for (int i = 0; i < strLen; i++) {
1369         ushort val = tokens[offset++];
1370         switch (val) {
1371         case '"': *outStr += fL1S("\\\""); break;
1372         case '\n': *outStr += fL1S("\\n"); break;
1373         case '\r': *outStr += fL1S("\\r"); break;
1374         case '\t': *outStr += fL1S("\\t"); break;
1375         case '\\': *outStr += fL1S("\\\\"); break;
1376         default:
1377             if (val < 32 || val > 126) {
1378                 *outStr += (val > 255 ? fL1S("\\u") : fL1S("\\x")) + QString::number(val, 16);
1379                 attn = true;
1380                 continue;
1381             }
1382             if (attn && isxdigit(val))
1383                 *outStr += fL1S("\"\"");
1384             *outStr += QChar(val);
1385             break;
1386         }
1387         attn = false;
1388     }
1389     *outStr += QLatin1Char('"');
1390     return true;
1391 }
1392 
getStr(const ushort * tokens,int limit,int & offset,QString * outStr)1393 static bool getStr(const ushort *tokens, int limit, int &offset, QString *outStr)
1394 {
1395     *outStr += fL1S(" << S(");
1396     ushort len;
1397     if (!getRawUshort(tokens, limit, offset, &len, outStr))
1398         return false;
1399     if (!getRawStr(tokens, limit, offset, len, outStr))
1400         return false;
1401     *outStr += QLatin1Char(')');
1402     return true;
1403 }
1404 
getHashStr(const ushort * tokens,int limit,int & offset,QString * outStr)1405 static bool getHashStr(const ushort *tokens, int limit, int &offset, QString *outStr)
1406 {
1407     *outStr += fL1S(" << HS(");
1408     uint hash;
1409     if (!getRawUint(tokens, limit, offset, &hash, outStr))
1410         return false;
1411     ushort len;
1412     if (!getRawUshort(tokens, limit, offset, &len, outStr))
1413         return false;
1414     const QChar *chars = (const QChar *)tokens + offset;
1415     if (!getRawStr(tokens, limit, offset, len, outStr))
1416         return false;
1417     uint realhash = ProString::hash(chars, len);
1418     if (realhash != hash)
1419         *outStr += fL1S(" /* Bad hash ") + QString::number(hash) + fL1S(" */");
1420     *outStr += QLatin1Char(')');
1421     return true;
1422 }
1423 
1424 static bool getBlock(const ushort *tokens, int limit, int &offset, QString *outStr, int indent);
1425 
getSubBlock(const ushort * tokens,int limit,int & offset,QString * outStr,int indent,const char * scope)1426 static bool getSubBlock(const ushort *tokens, int limit, int &offset, QString *outStr, int indent,
1427                         const char *scope)
1428 {
1429     *outStr += fL1S("\n    /* %1 */ ").arg(offset, 5)
1430                + QString(indent * 4, QLatin1Char(' '))
1431                + fL1S("/* ") + fL1S(scope) + fL1S(" */");
1432     uint len;
1433     if (!getUint(tokens, limit, offset, &len, outStr))
1434         return false;
1435     if (len) {
1436         BOUNDS_CHECK(len);
1437         int tmpOff = offset;
1438         offset += len;
1439         forever {
1440             if (!getBlock(tokens, offset, tmpOff, outStr, indent + 1))
1441                 break;  // Error was already reported, try to continue
1442             if (tmpOff == offset)
1443                 break;
1444             *outStr += QLatin1Char('\n') + QString(20 + indent * 4, QLatin1Char(' '))
1445                        + fL1S("/* Warning: Excess tokens follow. */");
1446         }
1447     }
1448     return true;
1449 }
1450 
getBlock(const ushort * tokens,int limit,int & offset,QString * outStr,int indent)1451 static bool getBlock(const ushort *tokens, int limit, int &offset, QString *outStr, int indent)
1452 {
1453     static const char * const tokNames[] = {
1454         "TokTerminator",
1455         "TokLine",
1456         "TokAssign", "TokAppend", "TokAppendUnique", "TokRemove", "TokReplace",
1457         "TokValueTerminator",
1458         "TokLiteral", "TokHashLiteral", "TokVariable", "TokProperty", "TokEnvVar",
1459         "TokFuncName", "TokArgSeparator", "TokFuncTerminator",
1460         "TokCondition", "TokTestCall",
1461         "TokReturn", "TokBreak", "TokNext",
1462         "TokNot", "TokAnd", "TokOr",
1463         "TokBranch", "TokForLoop",
1464         "TokTestDef", "TokReplaceDef", "TokBypassNesting"
1465     };
1466 
1467     while (offset != limit) {
1468         *outStr += fL1S("\n    /* %1 */").arg(offset, 5)
1469                    + QString(indent * 4, QLatin1Char(' '));
1470         BOUNDS_CHECK(1);
1471         ushort tok = tokens[offset++];
1472         ushort maskedTok = tok & TokMask;
1473         if (maskedTok >= sizeof(tokNames)/sizeof(tokNames[0])
1474             || (tok & ~(TokNewStr | TokQuoted | TokMask))) {
1475             *outStr += fL1S(" << {invalid token %1}").arg(tok);
1476             return false;
1477         }
1478         *outStr += fL1S(" << H(") + fL1S(tokNames[maskedTok]);
1479         if (tok & TokNewStr)
1480             *outStr += fL1S(" | TokNewStr");
1481         if (tok & TokQuoted)
1482             *outStr += fL1S(" | TokQuoted");
1483         *outStr += QLatin1Char(')');
1484         bool ok;
1485         switch (maskedTok) {
1486         case TokFuncTerminator:   // Recursion, but not a sub-block
1487             return true;
1488         case TokArgSeparator:
1489         case TokValueTerminator:  // Not recursion
1490         case TokTerminator:       // Recursion, and limited by (sub-)block length
1491         case TokCondition:
1492         case TokReturn:
1493         case TokBreak:
1494         case TokNext:
1495         case TokNot:
1496         case TokAnd:
1497         case TokOr:
1498             ok = true;
1499             break;
1500         case TokTestCall:
1501             ok = getBlock(tokens, limit, offset, outStr, indent + 1);
1502             break;
1503         case TokBranch:
1504             ok = getSubBlock(tokens, limit, offset, outStr, indent, "then branch");
1505             if (ok)
1506                 ok = getSubBlock(tokens, limit, offset, outStr, indent, "else branch");
1507             break;
1508         default:
1509             switch (maskedTok) {
1510             case TokAssign:
1511             case TokAppend:
1512             case TokAppendUnique:
1513             case TokRemove:
1514             case TokReplace:
1515                 // The parameter is the sizehint for the output.
1516                 // fallthrough
1517             case TokLine: {
1518                 ushort dummy;
1519                 ok = getUshort(tokens, limit, offset, &dummy, outStr);
1520                 break; }
1521             case TokLiteral:
1522             case TokEnvVar:
1523                 ok = getStr(tokens, limit, offset, outStr);
1524                 break;
1525             case TokHashLiteral:
1526             case TokVariable:
1527             case TokProperty:
1528                 ok = getHashStr(tokens, limit, offset, outStr);
1529                 break;
1530             case TokFuncName:
1531                 ok = getHashStr(tokens, limit, offset, outStr);
1532                 if (ok)
1533                     ok = getBlock(tokens, limit, offset, outStr, indent + 1);
1534                 break;
1535             case TokForLoop:
1536                 ok = getHashStr(tokens, limit, offset, outStr);
1537                 if (ok)
1538                     ok = getSubBlock(tokens, limit, offset, outStr, indent, "iterator");
1539                 if (ok)
1540                     ok = getSubBlock(tokens, limit, offset, outStr, indent, "body");
1541                 break;
1542             case TokTestDef:
1543             case TokReplaceDef:
1544                 ok = getHashStr(tokens, limit, offset, outStr);
1545                 if (ok)
1546                     ok = getSubBlock(tokens, limit, offset, outStr, indent, "body");
1547                 break;
1548             case TokBypassNesting:
1549                 ok = getSubBlock(tokens, limit, offset, outStr, indent, "block");
1550                 break;
1551             default:
1552                 Q_ASSERT(!"unhandled token");
1553             }
1554         }
1555         if (!ok)
1556             return false;
1557     }
1558     return true;
1559 }
1560 
formatProBlock(const QString & block)1561 QString QMakeParser::formatProBlock(const QString &block)
1562 {
1563     QString outStr;
1564     outStr += fL1S("\n            << TS(");
1565     int offset = 0;
1566     getBlock(reinterpret_cast<const ushort *>(block.constData()), block.length(),
1567              offset, &outStr, 0);
1568     outStr += QLatin1Char(')');
1569     return outStr;
1570 }
1571 
1572 #endif // PROPARSER_DEBUG
1573 
1574 QT_END_NAMESPACE
1575