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