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