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 qmake application 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"
33using namespace QMakeInternal;
34
35#include <qfile.h>
36#ifdef PROPARSER_THREAD_SAFE
37# include <qthreadpool.h>
38#endif
39
40QT_BEGIN_NAMESPACE
41
42///////////////////////////////////////////////////////////////////////
43//
44// ProFileCache
45//
46///////////////////////////////////////////////////////////////////////
47
48ProFileCache::ProFileCache()
49{
50 QMakeVfs::ref();
51}
52
53ProFileCache::~ProFileCache()
54{
55 for (const Entry &ent : qAsConst(parsed_files))
56 if (ent.pro)
57 ent.pro->deref();
58 QMakeVfs::deref();
59}
60
61void 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
71void 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
96void 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
131namespace { // MSVC2010 doesn't seem to know the semantics of "static" ...
132
133static 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
153void 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
175QMakeParser::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
184ProFile *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(QStringView(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(QStringView(contents), id, fileName, 1, FullGrammar);
242 else
243 pro = nullptr;
244 }
245 return pro;
246}
247
248ProFile *QMakeParser::parsedProBlock(
249 QStringView 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
256void QMakeParser::discardFileFromCache(int id)
257{
258 if (m_cache)
259 m_cache->discardFile(id);
260}
261
262bool 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
275void QMakeParser::putTok(ushort *&tokPtr, ushort tok)
276{
277 *tokPtr++ = tok;
278}
279
280void QMakeParser::putBlockLen(ushort *&tokPtr, uint len)
281{
282 *tokPtr++ = (ushort)len;
283 *tokPtr++ = (ushort)(len >> 16);
284}
285
286void QMakeParser::putBlock(ushort *&tokPtr, const ushort *buf, uint len)
287{
288 memcpy(tokPtr, buf, len * 2);
289 tokPtr += len;
290}
291
292void QMakeParser::putHashStr(ushort *&pTokPtr, const ushort *buf, uint len)
293{
294 const size_t 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
304void QMakeParser::finalizeHashStr(ushort *buf, uint len)
305{
306 buf[-4] = TokHashLiteral;
307 buf[-1] = len;
308 const size_t hash = ProString::hash((const QChar *)buf, len);
309 buf[-3] = (ushort)hash;
310 buf[-2] = (ushort)(hash >> 16);
311}
312
313void QMakeParser::read(ProFile *pro, QStringView 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.data();
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 char16_t needSep = TokNewStr; // Met unquoted whitespace
376 char16_t quote = 0;
377 char16_t 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 char16_t 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 const size_t 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(QChar(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 char16_t 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
902void QMakeParser::putLineMarker(ushort *&tokPtr)
903{
904 if (m_markLine) {
905 *tokPtr++ = TokLine;
906 *tokPtr++ = (ushort)m_markLine;
907 m_markLine = 0;
908 }
909}
910
911void 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
925void 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.
941void 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.
956void 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
967void 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
982bool 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
1002bool QMakeParser::acceptColon(const char *msg)
1003{
1004 if (m_operator == AndOperator)
1005 m_operator = NoOperator;
1006 return !failOperator(msg);
1007}
1008
1009void 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
1023void 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
1035void 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
1046void 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
1095void 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
1260bool QMakeParser::resolveVariable(ushort *xprPtr, int tlen, int needSep, ushort **ptr,
1261 ushort **buf, QString *xprBuff,
1262 ushort **tokPtr, QString *tokBuff,
1263 const ushort *cur, QStringView 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
1311void 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
1328static 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
1336static 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
1345static 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
1354static 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
1363static 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
1393static 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
1405static 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
1424static bool getBlock(const ushort *tokens, int limit, int &offset, QString *outStr, int indent);
1425
1426static 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
1451static 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
1561QString 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
1574QT_END_NAMESPACE
1575