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

source code of qtbase/qmake/library/qmakeparser.cpp