Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
cpp.cpp
Go to the documentation of this file.
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 "cpp.h"
5
6#include <translator.h>
7#include "metastrings.h"
8#include "trparser.h"
9
10#include <QtCore/QBitArray>
11#include <QtCore/QTextStream>
12#include <QtCore/QRegularExpression>
13
14#include <iostream>
15#include <optional>
16
17QT_BEGIN_NAMESPACE
18
19
20/* qmake ignore Q_OBJECT */
21
22using namespace Qt::StringLiterals;
23
25{
26 if (str.m_hash & 0x80000000)
27 str.m_hash = qHash(str.m_str) & 0x7fffffff;
28 return str.m_hash;
29}
30
31QDebug operator<<(QDebug debug, const HashString &s)
32{
33 return debug << s.value();
34}
35
37{
38 if (list.m_hash & 0x80000000) {
39 uint hash = 0;
40 for (const HashString &qs : list.m_list) {
41 hash ^= qHash(qs) ^ 0x6ad9f526;
42 hash = ((hash << 13) & 0x7fffffff) | (hash >> 18);
43 }
44 list.m_hash = hash;
45 }
46 return list.m_hash;
47}
48
49QDebug operator<<(QDebug debug, const HashStringList &lst)
50{
51 return debug << lst.m_list;
52}
53
54static int nextFileId;
55
57public:
59 {
60 m_ba.resize(nextFileId);
61 }
62 bool tryVisit(int fileId)
63 {
64 if (m_ba.at(fileId))
65 return false;
66 m_ba[fileId] = true;
67 return true;
68 }
69private:
70 QBitArray m_ba;
71};
72
73class CppParser : private CppParserState {
74
75public:
76 CppParser(ParseResults *results = 0);
77 void setInput(const QString &in);
78 void setInput(QTextStream &ts, const QString &fileName);
79 void setTranslator(Translator *_tor) { tor = _tor; }
80 void parse(ConversionData &cd, const QStringList &includeStack, QSet<QString> &inclusions);
81 bool parseTranslate(QString &prefix);
82 void parseInternal(ConversionData &cd, const QStringList &includeStack,
83 QSet<QString> &inclusions);
84 const ParseResults *recordResults(bool isHeader);
85 void deleteResults() { delete results; }
86
87private:
88 struct IfdefState {
89 IfdefState() {}
90 IfdefState(int _bracketDepth, int _braceDepth, int _parenDepth) :
91 bracketDepth(_bracketDepth),
92 braceDepth(_braceDepth),
93 parenDepth(_parenDepth),
94 elseLine(-1)
95 {}
96
97 CppParserState state;
98 int bracketDepth, bracketDepth1st;
99 int braceDepth, braceDepth1st;
100 int parenDepth, parenDepth1st;
101 int elseLine;
102 };
103
104 enum TokenType {
105 Tok_Eof,
106 Tok_class,
107 Tok_enum,
108 Tok_friend,
109 Tok_namespace,
110 Tok_using,
111 Tok_return,
112 Tok_decltype,
113 Tok_Q_OBJECT,
114 Tok_Access,
115 Tok_Cancel,
116 Tok_Ident,
117 Tok_String,
118 Tok_RawString,
119 Tok_Arrow,
120 Tok_Colon,
121 Tok_ColonColon,
122 Tok_Equals,
123 Tok_LeftBracket,
124 Tok_RightBracket,
125 Tok_Attribute,
126 Tok_LeftAngleBracket,
127 Tok_RightAngleBracket,
128 Tok_LeftShift,
129 Tok_QuestionMark,
130 Tok_LeftBrace,
131 Tok_RightBrace,
132 Tok_LeftParen,
133 Tok_RightParen,
134 Tok_Comma,
135 Tok_Semicolon,
136 Tok_Null,
137 Tok_Integer,
138 Tok_QuotedInclude,
139 Tok_AngledInclude
140 };
141
142 std::ostream &yyMsg(int line = 0);
143
144 int getChar();
145 TokenType lookAheadToSemicolonOrLeftBrace();
146 int lookBackFunctionCallStart(int prefixSize);
147 int lookAheadFunctionCallEnd(int functionStart);
148 TokenType getToken();
149
150 void processComment();
151
152 bool match(TokenType t);
153 bool matchString(QString *s);
154 bool matchEncoding();
155 bool matchStringOrNull(QString *s);
156 bool skipExpression();
157
158 void recordMessage(int line, const QString &context, const QString &text,
159 const QString &comment, const QString &extracomment, const QString &msgid,
160 const QString &label, const TranslatorMessage::ExtraData &extra,
161 bool plural, int startOffset, int endOffset);
162
163 void handleTr(QString &prefix, bool plural);
164 void handleTranslate(int prefixSize, bool plural);
165 void handleTrId(int prefixSize, bool plural);
166 void handleDeclareTrFunctions();
167
168 void processInclude(const QString &file, ConversionData &cd,
169 const QStringList &includeStack, QSet<QString> &inclusions);
170
171 void saveState(CppParserState *state);
172 void loadState(const CppParserState &state);
173
174 static QString stringifyNamespace(int start, const NamespaceList &namespaces);
175 static QString stringifyNamespace(const NamespaceList &namespaces)
176 { return stringifyNamespace(1, namespaces); }
177 static QString joinNamespaces(const QString &one, const QString &two);
178 typedef bool (CppParser::*VisitNamespaceCallback)(const Namespace *ns, void *context) const;
179 bool visitNamespace(const NamespaceList &namespaces, int nsCount,
180 VisitNamespaceCallback callback, void *context,
181 VisitRecorder &vr, const ParseResults *rslt) const;
182 bool visitNamespace(const NamespaceList &namespaces, int nsCount,
183 VisitNamespaceCallback callback, void *context) const;
184 bool qualifyOneCallbackOwn(const Namespace *ns, void *context) const;
185 bool qualifyOneCallbackUsing(const Namespace *ns, void *context) const;
186 bool qualifyOne(const NamespaceList &namespaces, int nsCnt, const HashString &segment,
187 NamespaceList *resolved, Namespace const **resolvedNamespace,
188 QSet<HashStringList> *visitedUsings, bool *foundViaUsing = nullptr) const;
189 bool qualifyOne(const NamespaceList &namespaces, int nsCnt, const HashString &segment,
190 NamespaceList *resolved, Namespace const **resolvedNamespace) const;
191 bool fullyQualify(const NamespaceList &namespaces, int nsCnt,
192 const NamespaceList &segments, bool isDeclaration,
193 NamespaceList *resolved, NamespaceList *unresolved) const;
194 bool fullyQualify(const NamespaceList &namespaces,
195 const NamespaceList &segments, bool isDeclaration,
196 NamespaceList *resolved, NamespaceList *unresolved) const;
197 bool fullyQualify(const NamespaceList &namespaces,
198 const QString &segments, bool isDeclaration,
199 NamespaceList *resolved, NamespaceList *unresolved) const;
200 bool findNamespaceCallback(const Namespace *ns, void *context) const;
201 Namespace *findNamespace(const NamespaceList &namespaces, int nsCount = -1) const;
202 void enterNamespace(NamespaceList *namespaces, const HashString &name);
203 void truncateNamespaces(NamespaceList *namespaces, int lenght);
204 Namespace *modifyNamespace(NamespaceList *namespaces, bool haveLast = true);
205
206 // Tokenizer state
207 QString yyFileName;
208 int yyCh;
209 bool yyAtNewline;
210 bool yyTrailingSpace;
211 QString yyWord;
212 qsizetype yyWordInitialCapacity = 0;
213 QStack<IfdefState> yyIfdefStack;
214 int yyBracketDepth;
215 int yyBraceDepth;
216 int yyParenDepth;
217 int yyLineNo;
218 int yyCurLineNo;
219 int yyBracketLineNo;
220 int yyBraceLineNo;
221 int yyParenLineNo;
222
223 // the string to read from and current position in the string
224 QStringConverter::Encoding yySourceEncoding = QStringConverter::Utf8;
225 QString yyInStr;
226 const ushort *yyInPtr;
227
228 // Parser state
229 TokenType yyTok;
230
231 bool metaExpected;
232 QString context;
233 MetaStrings m_metaStrings;
234
235 QString prospectiveContext;
236 ParseResults *results;
237 Translator *tor;
238 bool directInclude;
239
240 CppParserState savedState;
241 int yyMinBraceDepth;
242 bool inDefine;
243};
244
246{
247 tor = 0;
248 if (_results) {
249 results = _results;
250 directInclude = true;
251 } else {
252 results = new ParseResults;
253 directInclude = false;
254 }
255 yyBracketDepth = 0;
256 yyBraceDepth = 0;
257 yyParenDepth = 0;
258 yyCurLineNo = 1;
259 yyBracketLineNo = 1;
260 yyBraceLineNo = 1;
261 yyParenLineNo = 1;
262 yyAtNewline = true;
263 yyMinBraceDepth = 0;
264 inDefine = false;
265}
266
267
268std::ostream &CppParser::yyMsg(int line)
269{
270 return std::cerr << qPrintable(yyFileName) << ':' << (line ? line : yyLineNo) << ": ";
271}
272
273void CppParser::setInput(const QString &in)
274{
275 yyInStr = in;
276 yyFileName = QString();
277 yySourceEncoding = QStringConverter::Utf8;
278}
279
280void CppParser::setInput(QTextStream &ts, const QString &fileName)
281{
282 yyInStr = ts.readAll();
283 yyFileName = fileName;
284 yySourceEncoding = ts.encoding();
285}
286
287/*
288 The first part of this source file is the C++ tokenizer. We skip
289 most of C++; the only tokens that interest us are defined here.
290 Thus, the code fragment
291
292 int main()
293 {
294 printf("Hello, world!\n");
295 return 0;
296 }
297
298 is broken down into the following tokens (Tok_ omitted):
299
300 Ident Ident LeftParen RightParen
301 LeftBrace
302 Ident LeftParen String RightParen Semicolon
303 return Semicolon
304 RightBrace.
305
306 The 0 doesn't produce any token.
307*/
308
309int CppParser::getChar()
310{
311 const ushort *uc = yyInPtr;
312 forever {
313 ushort c = *uc;
314 if (!c) {
315 yyInPtr = uc;
316 return EOF;
317 }
318 ++uc;
319 if (c == '\\') {
320 ushort cc = *uc;
321 if (cc == '\n') {
322 ++yyCurLineNo;
323 ++uc;
324 continue;
325 }
326 if (cc == '\r') {
327 ++yyCurLineNo;
328 ++uc;
329 if (*uc == '\n')
330 ++uc;
331 continue;
332 }
333 }
334 if (c == '\r') {
335 if (*uc == '\n')
336 ++uc;
337 c = '\n';
338 ++yyCurLineNo;
339 yyAtNewline = true;
340 } else if (c == '\n') {
341 ++yyCurLineNo;
342 yyAtNewline = true;
343 } else if (c != ' ' && c != '\t' && c != '#') {
344 yyAtNewline = false;
345 }
346 yyInPtr = uc;
347 return int(c);
348 }
349}
350
351CppParser::TokenType CppParser::lookAheadToSemicolonOrLeftBrace()
352{
353 if (*yyInPtr == 0)
354 return Tok_Eof;
355 const ushort *uc = yyInPtr + 1;
356 forever {
357 ushort c = *uc;
358 if (!c)
359 return Tok_Eof;
360 if (c == ';')
361 return Tok_Semicolon;
362 if (c == '{')
363 return Tok_LeftBrace;
364 ++uc;
365 }
366}
367
368static bool isStringLiteralPrefix(const QStringView s)
369{
370 return s == u"L"_s
371 || s == u"U"_s
372 || s == u"u"_s
373 || s == u"u8"_s;
374}
375
376static bool isRawStringLiteralPrefix(QStringView s)
377{
378 if (s.endsWith(u'R')) {
379 s.chop(1);
380 return s.isEmpty() || isStringLiteralPrefix(s);
381 }
382 return false;
383}
384
385static const QString strQ_OBJECT = u"Q_OBJECT"_s;
386static const QString strclass = u"class"_s;
387static const QString strdecltype = u"decltype"_s;
388static const QString strenum = u"enum"_s;
389static const QString strfinal = u"final"_s;
390static const QString strfriend = u"friend"_s;
391static const QString strnamespace = u"namespace"_s;
392static const QString strnullptr = u"nullptr"_s;
393static const QString strQ_NULLPTR = u"Q_NULLPTR"_s;
394static const QString strNULL = u"NULL"_s;
395static const QString stroperator = u"operator"_s;
396static const QString strreturn = u"return"_s;
397static const QString strstruct = u"struct"_s;
398static const QString strusing = u"using"_s;
399static const QString strprivate = u"private"_s;
400static const QString strprotected = u"protected"_s;
401static const QString strpublic = u"public"_s;
402static const QString strslots = u"slots"_s;
403static const QString strsignals = u"signals"_s;
404static const QString strQ_SLOTS = u"Q_SLOTS"_s;
405static const QString strQ_SIGNALS = u"Q_SIGNALS"_s;
406
407CppParser::TokenType CppParser::getToken()
408{
409 restart:
410 // Failing this assertion would mean losing the preallocated buffer.
411 Q_ASSERT(yyWord.capacity() == yyWordInitialCapacity);
412
413 while (yyCh != EOF) {
414 yyLineNo = yyCurLineNo;
415
416 if (yyCh == '#' && yyAtNewline) {
417 /*
418 Early versions of lupdate complained about
419 unbalanced braces in the following code:
420
421 #ifdef ALPHA
422 while (beta) {
423 #else
424 while (gamma) {
425 #endif
426 delta;
427 }
428
429 The code contains, indeed, two opening braces for
430 one closing brace; yet there's no reason to panic.
431
432 The solution is to remember yyBraceDepth as it was
433 when #if, #ifdef or #ifndef was met, and to set
434 yyBraceDepth to that value when meeting #elif or
435 #else.
436 */
437 do {
438 yyCh = getChar();
439 } while (isspace(yyCh) && yyCh != '\n');
440
441 switch (yyCh) {
442 case 'd': // define
443 // Skip over the name of the define to avoid it being interpreted as c++ code
444 do { // Rest of "define"
445 yyCh = getChar();
446 if (yyCh == EOF)
447 return Tok_Eof;
448 if (yyCh == '\n')
449 goto restart;
450 } while (!isspace(yyCh));
451 do { // Space beween "define" and macro name
452 yyCh = getChar();
453 if (yyCh == EOF)
454 return Tok_Eof;
455 if (yyCh == '\n')
456 goto restart;
457 } while (isspace(yyCh));
458 do { // Macro name
459 if (yyCh == '(') {
460 // Argument list. Follows the name without a space, and no
461 // paren nesting is possible.
462 do {
463 yyCh = getChar();
464 if (yyCh == EOF)
465 return Tok_Eof;
466 if (yyCh == '\n')
467 goto restart;
468 } while (yyCh != ')');
469 break;
470 }
471 yyCh = getChar();
472 if (yyCh == EOF)
473 return Tok_Eof;
474 if (yyCh == '\n')
475 goto restart;
476 } while (!isspace(yyCh));
477 do { // Shortcut the immediate newline case if no comments follow.
478 yyCh = getChar();
479 if (yyCh == EOF)
480 return Tok_Eof;
481 if (yyCh == '\n')
482 goto restart;
483 } while (isspace(yyCh));
484
485 saveState(&savedState);
486 yyMinBraceDepth = yyBraceDepth;
487 inDefine = true;
488 goto restart;
489 case 'i':
490 yyCh = getChar();
491 if (yyCh == 'f') {
492 // if, ifdef, ifndef
493 yyIfdefStack.push(IfdefState(yyBracketDepth, yyBraceDepth, yyParenDepth));
494 yyCh = getChar();
495 } else if (yyCh == 'n') {
496 // include
497 do {
498 yyCh = getChar();
499 } while (yyCh != EOF && !isspace(yyCh) && yyCh != '"' && yyCh != '<' );
500 while (isspace(yyCh))
501 yyCh = getChar();
502 int tChar;
503 if (yyCh == '"')
504 tChar = '"';
505 else if (yyCh == '<')
506 tChar = '>';
507 else
508 break;
509 ushort *ptr = (ushort *)yyWord.unicode();
510 forever {
511 yyCh = getChar();
512 if (yyCh == EOF || yyCh == '\n')
513 break;
514 if (yyCh == tChar) {
515 yyCh = getChar();
516 break;
517 }
518 *ptr++ = yyCh;
519 }
520 yyWord.resize(ptr - (ushort *)yyWord.unicode());
521 return (tChar == '"') ? Tok_QuotedInclude : Tok_AngledInclude;
522 }
523 break;
524 case 'e':
525 yyCh = getChar();
526 if (yyCh == 'l') {
527 // elif, else
528 if (!yyIfdefStack.isEmpty()) {
529 IfdefState &is = yyIfdefStack.top();
530 if (is.elseLine != -1) {
531 if (yyBracketDepth != is.bracketDepth1st
532 || yyBraceDepth != is.braceDepth1st
533 || yyParenDepth != is.parenDepth1st)
534 yyMsg(is.elseLine)
535 << "Parenthesis/bracket/brace mismatch between "
536 "#if and #else branches; using #if branch\n";
537 } else {
538 is.bracketDepth1st = yyBracketDepth;
539 is.braceDepth1st = yyBraceDepth;
540 is.parenDepth1st = yyParenDepth;
541 saveState(&is.state);
542 }
543 is.elseLine = yyLineNo;
544 yyBracketDepth = is.bracketDepth;
545 yyBraceDepth = is.braceDepth;
546 yyParenDepth = is.parenDepth;
547 }
548 yyCh = getChar();
549 } else if (yyCh == 'n') {
550 // endif
551 if (!yyIfdefStack.isEmpty()) {
552 IfdefState is = yyIfdefStack.pop();
553 if (is.elseLine != -1) {
554 if (yyBracketDepth != is.bracketDepth1st
555 || yyBraceDepth != is.braceDepth1st
556 || yyParenDepth != is.parenDepth1st)
557 yyMsg(is.elseLine)
558 << "Parenthesis/brace mismatch between "
559 "#if and #else branches; using #if branch\n";
560 yyBracketDepth = is.bracketDepth1st;
561 yyBraceDepth = is.braceDepth1st;
562 yyParenDepth = is.parenDepth1st;
563 loadState(is.state);
564 }
565 }
566 yyCh = getChar();
567 }
568 break;
569 }
570 // Optimization: skip over rest of preprocessor directive
571 do {
572 if (yyCh == '/') {
573 yyCh = getChar();
574 if (yyCh == '/') {
575 do {
576 yyCh = getChar();
577 } while (yyCh != EOF && yyCh != '\n');
578 break;
579 } else if (yyCh == '*') {
580 bool metAster = false;
581
582 forever {
583 yyCh = getChar();
584 if (yyCh == EOF) {
585 yyMsg() << "Unterminated C++ comment\n";
586 break;
587 }
588
589 if (yyCh == '*') {
590 metAster = true;
591 } else if (metAster && yyCh == '/') {
592 yyCh = getChar();
593 break;
594 } else {
595 metAster = false;
596 }
597 }
598 }
599 } else {
600 yyCh = getChar();
601 }
602 } while (yyCh != '\n' && yyCh != EOF);
603 yyCh = getChar();
604 } else if ((yyCh >= 'A' && yyCh <= 'Z') || (yyCh >= 'a' && yyCh <= 'z') || yyCh == '_') {
605 ushort *ptr = (ushort *)yyWord.unicode();
606 do {
607 *ptr++ = yyCh;
608 yyCh = getChar();
609 } while ((yyCh >= 'A' && yyCh <= 'Z') || (yyCh >= 'a' && yyCh <= 'z')
610 || (yyCh >= '0' && yyCh <= '9') || yyCh == '_');
611 yyWord.resize(ptr - (ushort *)yyWord.unicode());
612 yyTrailingSpace = isspace(yyCh);
613
614 //qDebug() << "IDENT: " << yyWord;
615
616 if (yyCh == '"' && isStringLiteralPrefix(yyWord)) {
617 // Handle prefixed string literals as ordinary string literals.
618 continue;
619 }
620
621 switch (yyWord.unicode()[0].unicode()) {
622 case 'N':
623 if (yyWord == strNULL)
624 return Tok_Null;
625 break;
626 case 'Q':
627 if (yyWord == strQ_NULLPTR)
628 return Tok_Null;
629 if (yyWord == strQ_OBJECT)
630 return Tok_Q_OBJECT;
631 if (yyWord == strQ_SLOTS || yyWord == strQ_SIGNALS)
632 return Tok_Access;
633 break;
634 case 'c':
635 if (yyWord == strclass)
636 return Tok_class;
637 break;
638 case 'd':
639 if (yyWord == strdecltype)
640 return Tok_decltype;
641 break;
642 case 'e':
643 if (yyWord == strenum)
644 return Tok_enum;
645 break;
646 case 'f':
647 if (yyWord == strfriend)
648 return Tok_friend;
649 break;
650 case 'n':
651 if (yyWord == strnamespace)
652 return Tok_namespace;
653 if (yyWord == strnullptr)
654 return Tok_Null;
655 break;
656 case 'o':
657 if (yyWord == stroperator) {
658 // Operator overload declaration/definition.
659 // We need to prevent those characters from confusing the followup
660 // parsing. Actually using them does not add value, so just eat them.
661 while (isspace(yyCh))
662 yyCh = getChar();
663 while (yyCh == '+' || yyCh == '-' || yyCh == '*' || yyCh == '/' || yyCh == '%'
664 || yyCh == '=' || yyCh == '<' || yyCh == '>' || yyCh == '!'
665 || yyCh == '&' || yyCh == '|' || yyCh == '~' || yyCh == '^'
666 || yyCh == '[' || yyCh == ']')
667 yyCh = getChar();
668 }
669 break;
670 case 'p':
671 if (yyWord == strpublic || yyWord == strprotected || yyWord == strprivate)
672 return Tok_Access;
673 break;
674 case 'r':
675 if (yyWord == strreturn)
676 return Tok_return;
677 break;
678 case 's':
679 if (yyWord == strstruct)
680 return Tok_class;
681 if (yyWord == strslots || yyWord == strsignals)
682 return Tok_Access;
683 break;
684 case 'u':
685 if (yyWord == strusing)
686 return Tok_using;
687 break;
688 }
689
690 // a C++11 raw string literal?
691 if (yyCh == '"' && isRawStringLiteralPrefix(yyWord)) {
692 ptr = reinterpret_cast<ushort *>(const_cast<QChar *>(yyWord.unicode()));
693 //get delimiter
694 QString delimiter;
695 for (yyCh = getChar(); yyCh != EOF && yyCh != '('; yyCh = getChar())
696 delimiter += QLatin1Char(yyCh);
697 if (yyCh != EOF)
698 yyCh = getChar(); // throw away the opening parentheses
699 bool is_end = false;
700 ushort *ptr_past_end = nullptr;
701 while (yyCh != EOF && !is_end) {
702 *ptr++ = yyCh;
703 if (ptr_past_end != nullptr) {
704 if (delimiter.size() == ptr - ptr_past_end
705 && memcmp(delimiter.unicode(), ptr_past_end, (ptr - ptr_past_end) * sizeof (ushort)) == 0
706 ) {
707 // we've got the delimiter, check if " follows
708 yyCh = getChar();
709 if (yyCh == '"')
710 is_end = true;
711 else
712 ptr_past_end = nullptr;
713 continue;
714 }
715 }
716 if (yyCh == ')') {
717 ptr_past_end = ptr;
718 if (delimiter.isEmpty()) {
719 // no delimiter, check if " follows
720 yyCh = getChar();
721 if (yyCh == '"')
722 is_end = true;
723 else
724 ptr_past_end = nullptr;
725 continue;
726 }
727 }
728 yyCh = getChar();
729 }
730 if (is_end)
731 yyWord.resize(ptr_past_end - 1 - reinterpret_cast<const ushort *>(yyWord.unicode()));
732 else
733 yyWord.resize(ptr - reinterpret_cast<const ushort *>(yyWord.unicode()));
734 if (yyCh != '"')
735 yyMsg() << "Unterminated/mismatched C++ Raw string\n";
736 else
737 yyCh = getChar();
738 return Tok_RawString;
739 }
740
741 return Tok_Ident;
742 } else {
743 switch (yyCh) {
744 case '\n':
745 if (inDefine) {
746 loadState(savedState);
747 prospectiveContext.clear();
748 yyBraceDepth = yyMinBraceDepth;
749 yyMinBraceDepth = 0;
750 inDefine = false;
751 metaExpected = true;
752 yyCh = getChar();
753 return Tok_Cancel; // Break out of any multi-token constructs
754 }
755 yyCh = getChar();
756 break;
757 case '/':
758 yyCh = getChar();
759 if (yyCh == '/') {
760 ushort *ptr = (ushort *)yyWord.unicode();
761 do {
762 yyCh = getChar();
763 if (yyCh == EOF)
764 break;
765 *ptr++ = yyCh;
766 } while (yyCh != '\n');
767 yyWord.resize(ptr - (ushort *)yyWord.unicode());
768 processComment();
769 } else if (yyCh == '*') {
770 bool metAster = false;
771 ushort *ptr = (ushort *)yyWord.unicode();
772
773 forever {
774 yyCh = getChar();
775 if (yyCh == EOF) {
776 yyMsg() << "Unterminated C++ comment\n";
777 break;
778 }
779 *ptr++ = yyCh;
780
781 if (yyCh == '*')
782 metAster = true;
783 else if (metAster && yyCh == '/')
784 break;
785 else
786 metAster = false;
787 }
788 yyWord.resize(ptr - (ushort *)yyWord.unicode() - 2);
789 processComment();
790
791 yyCh = getChar();
792 }
793 break;
794 case '"': {
795 ushort *ptr = (ushort *)yyWord.unicode();
796 yyCh = getChar();
797 while (yyCh != EOF && yyCh != '\n' && yyCh != '"') {
798 if (yyCh == '\\') {
799 yyCh = getChar();
800 if (yyCh == EOF || yyCh == '\n')
801 break;
802 *ptr++ = '\\';
803 }
804 *ptr++ = yyCh;
805 yyCh = getChar();
806 }
807 yyWord.resize(ptr - (ushort *)yyWord.unicode());
808
809 if (yyCh != '"')
810 yyMsg() << "Unterminated C++ string\n";
811 else
812 yyCh = getChar();
813 return Tok_String;
814 }
815 case '-':
816 yyCh = getChar();
817 if (yyCh == '>') {
818 yyCh = getChar();
819 return Tok_Arrow;
820 }
821 break;
822 case ':':
823 yyCh = getChar();
824 if (yyCh == ':') {
825 yyCh = getChar();
826 return Tok_ColonColon;
827 }
828 return Tok_Colon;
829 // Incomplete: '<' might be part of '<=' or of template syntax.
830 // The main intent of not completely ignoring it is to break
831 // parsing of things like std::cout << QObject::tr() as
832 // context std::cout::QObject (see Task 161106)
833 case '=':
834 yyCh = getChar();
835 return Tok_Equals;
836 case '>':
837 yyCh = getChar();
838 return Tok_RightAngleBracket;
839 case '<':
840 yyCh = getChar();
841 if (yyCh == '<') {
842 yyCh = getChar();
843 return Tok_LeftShift;
844 }
845 return Tok_LeftAngleBracket;
846 case '\'':
847 yyCh = getChar();
848 if (yyCh == '\\')
849 yyCh = getChar();
850
851 forever {
852 if (yyCh == EOF || yyCh == '\n') {
853 yyMsg() << "Unterminated C++ character\n";
854 break;
855 }
856 yyCh = getChar();
857 if (yyCh == '\'') {
858 yyCh = getChar();
859 break;
860 }
861 }
862 break;
863 case '{':
864 if (yyBraceDepth == 0)
865 yyBraceLineNo = yyCurLineNo;
866 yyBraceDepth++;
867 yyCh = getChar();
868 return Tok_LeftBrace;
869 case '}':
870 if (yyBraceDepth == yyMinBraceDepth) {
871 if (!inDefine)
872 yyMsg(yyCurLineNo)
873 << "Excess closing brace in C++ code"
874 " (or abuse of the C++ preprocessor)\n";
875 // Avoid things getting messed up even more
876 yyCh = getChar();
877 return Tok_Semicolon;
878 }
879 yyBraceDepth--;
880 yyCh = getChar();
881 return Tok_RightBrace;
882 case '(':
883 if (yyParenDepth == 0)
884 yyParenLineNo = yyCurLineNo;
885 yyParenDepth++;
886 yyCh = getChar();
887 return Tok_LeftParen;
888 case ')':
889 if (yyParenDepth == 0)
890 yyMsg(yyCurLineNo)
891 << "Excess closing parenthesis in C++ code"
892 " (or abuse of the C++ preprocessor)\n";
893 else
894 yyParenDepth--;
895 yyCh = getChar();
896 return Tok_RightParen;
897 case '[':
898 yyCh = getChar();
899 if (yyCh == '[') {
900 // C++11 attribute: [[...]]
901 yyCh = getChar();
902 int depth = 2;
903 bool inString = false;
904 bool inChar = false;
905 bool escaped = false;
906
907 // Need to handle nested brackets, strings, and character literals
908 while (depth > 0 && yyCh != EOF) {
909 if (escaped) {
910 escaped = false;
911 } else if (yyCh == '\\') {
912 escaped = true;
913 } else if (yyCh == '"' && !inChar) {
914 inString = !inString;
915 } else if (yyCh == '\'' && !inString) {
916 inChar = !inChar;
917 } else if (!inString && !inChar) {
918 if (yyCh == '[')
919 depth++;
920 else if (yyCh == ']')
921 depth--;
922 }
923 yyCh = getChar();
924 }
925
926 if (yyCh == EOF) {
927 yyMsg(yyCurLineNo) << "Unterminated C++ attribute (missing ']]')\n";
928 return Tok_LeftBracket;
929 }
930
931 yyCh = getChar();
932 return Tok_Attribute;
933 }
934 if (yyBracketDepth == 0)
935 yyBracketLineNo = yyCurLineNo;
936 yyBracketDepth++;
937 return Tok_LeftBracket;
938 case ']':
939 if (yyBracketDepth == 0)
940 yyMsg(yyCurLineNo)
941 << "Excess closing bracket in C++ code"
942 " (or abuse of the C++ preprocessor)\n";
943 else
944 yyBracketDepth--;
945 yyCh = getChar();
946 return Tok_RightBracket;
947 case ',':
948 yyCh = getChar();
949 return Tok_Comma;
950 case ';':
951 yyCh = getChar();
952 return Tok_Semicolon;
953 case '?':
954 yyCh = getChar();
955 return Tok_QuestionMark;
956 case '0':
957 yyCh = getChar();
958 if (yyCh == 'x' || yyCh == 'X') {
959 do {
960 yyCh = getChar();
961 } while ((yyCh >= '0' && yyCh <= '9') || yyCh == '\''
962 || (yyCh >= 'a' && yyCh <= 'f') || (yyCh >= 'A' && yyCh <= 'F'));
963 return Tok_Integer;
964 }
965 if (yyCh < '0' || yyCh > '9')
966 return Tok_Null;
967 Q_FALLTHROUGH();
968 case '1':
969 case '2':
970 case '3':
971 case '4':
972 case '5':
973 case '6':
974 case '7':
975 case '8':
976 case '9':
977 do {
978 yyCh = getChar();
979 } while ((yyCh >= '0' && yyCh <= '9') || yyCh == '\'');
980 return Tok_Integer;
981 default:
982 yyCh = getChar();
983 break;
984 }
985 }
986 }
987 return Tok_Eof;
988}
989
990/*
991 The second part of this source file are namespace/class related
992 utilities for the third part.
993*/
994
995void CppParser::saveState(CppParserState *state)
996{
997 *state = *this;
998}
999
1000void CppParser::loadState(const CppParserState &state)
1001{
1002 *static_cast<CppParserState *>(this) = state;
1003}
1004
1005Namespace *CppParser::modifyNamespace(NamespaceList *namespaces, bool haveLast)
1006{
1007 Namespace *pns, *ns = &results->rootNamespace;
1008 for (int i = 1; i < namespaces->size(); ++i) {
1009 pns = ns;
1010 if (!(ns = pns->children.value(namespaces->at(i)))) {
1011 do {
1012 ns = new Namespace;
1013 if (haveLast || i < namespaces->size() - 1)
1014 if (const Namespace *ons = findNamespace(*namespaces, i + 1))
1015 ns->classDef = ons->classDef;
1016 pns->children.insert(namespaces->at(i), ns);
1017 ns->parent = pns;
1018 pns = ns;
1019 } while (++i < namespaces->size());
1020 break;
1021 }
1022 }
1023 return ns;
1024}
1025
1026QString CppParser::stringifyNamespace(int start, const NamespaceList &namespaces)
1027{
1028 QString ret;
1029 int l = 0;
1030 for (int j = start; j < namespaces.size(); ++j)
1031 l += namespaces.at(j).value().size();
1032 ret.reserve(l + qMax(0, (namespaces.size() - start - 1)) * 2);
1033 for (int i = start; i < namespaces.size(); ++i) {
1034 if (i > start)
1035 ret += "::"_L1;
1036 ret += namespaces.at(i).value();
1037 }
1038 return ret;
1039}
1040
1041QString CppParser::joinNamespaces(const QString &one, const QString &two)
1042{
1043 return two.isEmpty() ? one : one.isEmpty() ? two : one + QStringLiteral("::") + two;
1044}
1045
1046bool CppParser::visitNamespace(const NamespaceList &namespaces, int nsCount,
1047 VisitNamespaceCallback callback, void *context,
1048 VisitRecorder &vr, const ParseResults *rslt) const
1049{
1050 const Namespace *ns = &rslt->rootNamespace;
1051 for (int i = 1; i < nsCount; ++i)
1052 if (!(ns = ns->children.value(namespaces.at(i))))
1053 goto supers;
1054 if ((this->*callback)(ns, context))
1055 return true;
1056supers:
1057 for (const ParseResults *sup : rslt->includes)
1058 if (vr.tryVisit(sup->fileId)
1059 && visitNamespace(namespaces, nsCount, callback, context, vr, sup))
1060 return true;
1061 return false;
1062}
1063
1064bool CppParser::visitNamespace(const NamespaceList &namespaces, int nsCount,
1065 VisitNamespaceCallback callback, void *context) const
1066{
1067 VisitRecorder vr;
1068 return visitNamespace(namespaces, nsCount, callback, context, vr, results);
1069}
1070
1072 QualifyOneData(const NamespaceList &ns, int nsc, const HashString &seg, NamespaceList *rslvd,
1073 QSet<HashStringList> *visited, Namespace const **resolvedNs)
1074 : namespaces(ns),
1075 nsCount(nsc),
1076 segment(seg),
1077 resolved(rslvd),
1079 resolvedNamespace(resolvedNs)
1080 {}
1081
1088};
1089
1090bool CppParser::qualifyOneCallbackOwn(const Namespace *ns, void *context) const
1091{
1092 QualifyOneData *data = (QualifyOneData *)context;
1093 if (auto rns = ns->children.constFind(data->segment); rns != ns->children.cend()) {
1094 *data->resolved = data->namespaces.mid(0, data->nsCount);
1095 *data->resolved << data->segment;
1096 *data->resolvedNamespace = *rns;
1097 return true;
1098 }
1099 auto nsai = ns->aliases.constFind(data->segment);
1100 if (nsai != ns->aliases.constEnd()) {
1101 const NamespaceList &nsl = *nsai;
1102 if (nsl.last().value().isEmpty()) { // Delayed alias resolution
1103 NamespaceList &nslIn = *const_cast<NamespaceList *>(&nsl);
1104 nslIn.removeLast();
1105 NamespaceList nslOut;
1106 if (!fullyQualify(data->namespaces, data->nsCount, nslIn, false, &nslOut, 0)) {
1107 const_cast<Namespace *>(ns)->aliases.remove(data->segment);
1108 return false;
1109 }
1110 nslIn = nslOut;
1111 }
1112 *data->resolved = nsl;
1113 *data->resolvedNamespace = ns;
1114 return true;
1115 }
1116 return false;
1117}
1118
1119bool CppParser::qualifyOneCallbackUsing(const Namespace *ns, void *context) const
1120{
1121 QualifyOneData *data = (QualifyOneData *)context;
1122 for (const HashStringList &use : ns->usings)
1123 if (!data->visitedUsings->contains(use)) {
1124 data->visitedUsings->insert(use);
1125 if (qualifyOne(use.value(), use.value().size(), data->segment, data->resolved,
1126 data->resolvedNamespace, data->visitedUsings))
1127 return true;
1128 }
1129 return false;
1130}
1131
1132bool CppParser::qualifyOne(const NamespaceList &namespaces, int nsCnt, const HashString &segment,
1133 NamespaceList *resolved, Namespace const **resolvedNamespace,
1134 QSet<HashStringList> *visitedUsings, bool *foundViaUsing) const
1135{
1136 QualifyOneData data(namespaces, nsCnt, segment, resolved, visitedUsings, resolvedNamespace);
1137
1138 if (visitNamespace(namespaces, nsCnt, &CppParser::qualifyOneCallbackOwn, &data)) {
1139 if (foundViaUsing)
1140 *foundViaUsing = false;
1141 return true;
1142 }
1143
1144 if (visitNamespace(namespaces, nsCnt, &CppParser::qualifyOneCallbackUsing, &data)) {
1145 if (foundViaUsing)
1146 *foundViaUsing = true;
1147 return true;
1148 }
1149
1150 return false;
1151}
1152
1153bool CppParser::qualifyOne(const NamespaceList &namespaces, int nsCnt, const HashString &segment,
1154 NamespaceList *resolved, Namespace const **resolvedNamespace) const
1155{
1156 QSet<HashStringList> visitedUsings;
1157
1158 return qualifyOne(namespaces, nsCnt, segment, resolved, resolvedNamespace, &visitedUsings);
1159}
1160
1161bool CppParser::fullyQualify(const NamespaceList &namespaces, int nsCnt,
1162 const NamespaceList &segments, bool isDeclaration,
1163 NamespaceList *resolved, NamespaceList *unresolved) const
1164{
1165 int nsIdx;
1166 int initSegIdx;
1167
1168 if (segments.first().value().isEmpty()) {
1169 // fully qualified
1170 if (segments.size() == 1) {
1171 resolved->clear();
1172 *resolved << HashString(QString());
1173 return true;
1174 }
1175 initSegIdx = 1;
1176 nsIdx = 0;
1177 } else {
1178 initSegIdx = 0;
1179 nsIdx = nsCnt - 1;
1180 }
1181
1182 // Check if the identifier to resolve shares a common suffix
1183 // with the current namespace path. This handles cases like:
1184 // - Current scope: ["", "Outer", "Test"]
1185 // - Trying to resolve: ["Outer", "Test"]
1186
1187 auto matchSeg = segments.crbegin();
1188 auto matchNs = namespaces.crbegin();
1189
1190 if (matchSeg->value() != matchNs->value())
1191 matchSeg++;
1192
1193 int matchingSuffixLength = 0;
1194 while (matchSeg != segments.crend()
1195 && matchNs != namespaces.crend()
1196 && matchSeg->value() == matchNs->value()) {
1197
1198 matchSeg++;
1199 matchNs++;
1200 matchingSuffixLength++;
1201 }
1202
1203 // The main loop to resolve the segments in the current namespace.
1204 // There are three possible outcomes:
1205 // 1. resolution of the segments into a class that has tr
1206 // functions (hasTrFunction) if available (trCandidate).
1207 // 2. resolution of the segments into a namespace/class without a
1208 // tr function -> in this case, if we're in a handleTr (or
1209 // similar) function, the caller issues a warning (noTrCandidate).
1210 // 3. the segments cannot be resolved in the current namespace -> in
1211 // this case, we calculate the non-resolved part (unresolvedCandidate)
1212
1213 bool viaUsing = false;
1214 std::optional<NamespaceList> trCandidate;
1215 std::optional<NamespaceList> noTrCandidate;
1216 std::optional<NamespaceList> unresolvedCandidate;
1217 do {
1218 Namespace const *resolvedNs = nullptr;
1219 int segIdx = initSegIdx;
1220 int resolveSize = nsIdx + 1;
1221 *resolved = namespaces;
1222 do {
1223 QSet<HashStringList> visitedUsings;
1224 if (!qualifyOne(*resolved, resolveSize, segments[segIdx], resolved, &resolvedNs,
1225 &visitedUsings, &viaUsing))
1226 break; // could not resolve
1227
1228 segIdx++;
1229 resolveSize = resolved->size();
1230 } while (segIdx < segments.size());
1231
1232 if (segIdx == segments.size()) { // resolved
1233 if (resolvedNs && resolvedNs->classDef && resolvedNs->classDef->hasTrFunctions) {
1234 if (!viaUsing)
1235 return true;
1236 else if (!trCandidate)
1237 trCandidate.emplace(*resolved);
1238 } else if (resolvedNs) {
1239 if (!viaUsing)
1240 noTrCandidate.emplace(*resolved);
1241 else if (!noTrCandidate)
1242 noTrCandidate.emplace(*resolved);
1243 }
1244 } else if (!unresolvedCandidate) { // unresolved
1245 unresolvedCandidate.emplace(segments.mid(segIdx));
1246 }
1247
1248 // For declarations: limit search to suffix-matched depths
1249 // For usages: search all parent levels (standard C++ lookup)
1250 } while ((!isDeclaration || matchingSuffixLength-- > 0) && --nsIdx >= 0);
1251
1252 // Depending on the case, after the loop, we might have only one of the
1253 // outcome candidates, or 2 or 3 (all possbile outcome candidates).
1254 // We prioritize the candidates as follows:
1255 // 1. if a class with tr commands is found (trCandidate), we take it
1256 // as the result.
1257 // 2. otherwise, if any class/namespace without tr commands is
1258 // found (noTrCandidate), take it as the result.
1259 // 3. Finally, if none of the above candidates are found, we return the
1260 // unresolved part (unresolvedCandidate).
1261
1262 if (trCandidate) { // resolved class with tr functions gets priority
1263 *resolved = *trCandidate;
1264 return true;
1265 } else if (noTrCandidate) { // the non-tr resolved namespace/class
1266 *resolved = *noTrCandidate;
1267 return true;
1268 } else if (unresolvedCandidate && unresolved) {
1269 *unresolved = *unresolvedCandidate;
1270 }
1271
1272 resolved->clear();
1273 *resolved << HashString(QString());
1274 if (unresolved)
1275 *unresolved = segments.mid(initSegIdx);
1276 return false;
1277}
1278
1279bool CppParser::fullyQualify(const NamespaceList &namespaces,
1280 const NamespaceList &segments, bool isDeclaration,
1281 NamespaceList *resolved, NamespaceList *unresolved) const
1282{
1283 return fullyQualify(namespaces, namespaces.size(),
1284 segments, isDeclaration, resolved, unresolved);
1285}
1286
1287bool CppParser::fullyQualify(const NamespaceList &namespaces,
1288 const QString &quali, bool isDeclaration,
1289 NamespaceList *resolved, NamespaceList *unresolved) const
1290{
1291 NamespaceList segments;
1292 for (const QString &str : quali.split("::"_L1)) // XXX slow, but needs to be fast(?)
1293 segments << HashString(str);
1294 return fullyQualify(namespaces, segments, isDeclaration, resolved, unresolved);
1295}
1296
1297bool CppParser::findNamespaceCallback(const Namespace *ns, void *context) const
1298{
1299 *((const Namespace **)context) = ns;
1300 return true;
1301}
1302
1303Namespace *CppParser::findNamespace(const NamespaceList &namespaces, int nsCount) const
1304{
1305 Namespace *ns = 0;
1306 if (nsCount == -1)
1307 nsCount = namespaces.size();
1308 visitNamespace(namespaces, nsCount, &CppParser::findNamespaceCallback, &ns);
1309 return ns;
1310}
1311
1312void CppParser::enterNamespace(NamespaceList *namespaces, const HashString &name)
1313{
1314 *namespaces << name;
1315 Namespace *ns;
1316 if (!(ns = findNamespace(*namespaces)))
1317 ns = modifyNamespace(namespaces, false);
1318
1319 const Namespace *cns = &results->rootNamespace;
1320 for (int i = 1; i < namespaces->size(); ++i) {
1321 ns->usings << cns->usings;
1322 if (!(cns = cns->children.value(namespaces->at(i))))
1323 break;
1324 }
1325}
1326
1327void CppParser::truncateNamespaces(NamespaceList *namespaces, int length)
1328{
1329 if (namespaces->size() > length)
1330 namespaces->erase(namespaces->begin() + length, namespaces->end());
1331}
1332
1333
1334/*
1335 Functions for processing include files.
1336*/
1337
1338size_t qHash(const CppParserState &s, size_t seed)
1339{
1340 seed = qHash(s.namespaces, seed);
1341 seed = qHash(s.namespaceDepths, seed);
1342 seed = qHash(s.functionContext, seed);
1343 seed = qHash(s.functionContextUnresolved, seed);
1344 seed = qHash(s.pendingContext, seed);
1345 return seed;
1346}
1347
1348size_t qHash(const ResultsCacheKey &key, size_t seed)
1349{
1350 seed = qHash(key.cleanFile, seed);
1351 seed = qHash(key.parserState, seed);
1352 return seed;
1353}
1354
1355IncludeCycleHash &CppFiles::includeCycles()
1356{
1357 static IncludeCycleHash cycles;
1358
1359 return cycles;
1360}
1361
1362TranslatorHash &CppFiles::translatedFiles()
1363{
1364 static TranslatorHash tors;
1365
1366 return tors;
1367}
1368
1369QSet<QString> &CppFiles::blacklistedFiles()
1370{
1371 static QSet<QString> blacklisted;
1372
1373 return blacklisted;
1374}
1375
1376QSet<const ParseResults *> CppFiles::getResults(const ResultsCacheKey &key)
1377{
1378 IncludeCycle * const cycle = includeCycles().value(key);
1379
1380 if (cycle)
1381 return cycle->results;
1382 else
1383 return QSet<const ParseResults *>();
1384}
1385
1386void CppFiles::setResults(const ResultsCacheKey &key, const ParseResults *results)
1387{
1388 IncludeCycle *cycle = includeCycles().value(key);
1389
1390 if (!cycle) {
1391 cycle = new IncludeCycle;
1392 includeCycles().insert(key, cycle);
1393 }
1394
1395 cycle->fileNames.insert(key.cleanFile);
1396 cycle->results.insert(results);
1397}
1398
1399const Translator *CppFiles::getTranslator(const QString &cleanFile)
1400{
1401 return translatedFiles().value(cleanFile);
1402}
1403
1404void CppFiles::setTranslator(const QString &cleanFile, const Translator *tor)
1405{
1406 translatedFiles().insert(cleanFile, tor);
1407}
1408
1409bool CppFiles::isBlacklisted(const QString &cleanFile)
1410{
1411 return blacklistedFiles().contains(cleanFile);
1412}
1413
1414void CppFiles::setBlacklisted(const QString &cleanFile)
1415{
1416 blacklistedFiles().insert(cleanFile);
1417}
1418
1419void CppFiles::addIncludeCycle(const QSet<QString> &fileNames, const CppParserState &parserState)
1420{
1421 IncludeCycle * const cycle = new IncludeCycle;
1422 cycle->fileNames = fileNames;
1423
1424 QSet<IncludeCycle *> intersectingCycles;
1425 for (const QString &fileName : fileNames) {
1426 const ResultsCacheKey key = { fileName, parserState };
1427 IncludeCycle *intersectingCycle = includeCycles().value(key);
1428
1429 if (intersectingCycle && !intersectingCycles.contains(intersectingCycle)) {
1430 intersectingCycles.insert(intersectingCycle);
1431
1432 cycle->fileNames.unite(intersectingCycle->fileNames);
1433 cycle->results.unite(intersectingCycle->results);
1434 }
1435 }
1436 qDeleteAll(intersectingCycles);
1437
1438 for (const QString &fileName : std::as_const(cycle->fileNames))
1439 includeCycles().insert({ fileName, parserState }, cycle);
1440}
1441
1442static bool isHeader(const QString &name)
1443{
1444 QString fileExt = QFileInfo(name).suffix();
1445 return fileExt.isEmpty() || fileExt.startsWith(u'h', Qt::CaseInsensitive);
1446}
1447
1448void CppParser::processInclude(const QString &file, ConversionData &cd, const QStringList &includeStack,
1449 QSet<QString> &inclusions)
1450{
1451 QString cleanFile = QDir::cleanPath(file);
1452
1453 for (const QRegularExpression &rx : std::as_const(cd.m_excludes)) {
1454 if (rx.match(cleanFile).hasMatch())
1455 return;
1456 }
1457
1458 const int index = includeStack.indexOf(cleanFile);
1459 if (index != -1) {
1460 CppFiles::addIncludeCycle(QSet<QString>(includeStack.cbegin() + index, includeStack.cend()),
1461 *this);
1462 return;
1463 }
1464
1465 // If the #include has been blacklisted previously,
1466 // or is not a header file (stdc++ extensionless or *.h*), then really include
1467 // it. Otherwise it is safe to process it stand-alone and re-use the parsed
1468 // namespace data for inclusion into other files.
1469 bool isIndirect = false;
1470 if (!CppFiles::isBlacklisted(cleanFile)
1471 && isHeader(cleanFile)) {
1472
1473 QSet<const ParseResults *> res = CppFiles::getResults(ResultsCacheKey(cleanFile, *this));
1474 if (!res.isEmpty()) {
1475 results->includes.unite(res);
1476 return;
1477 }
1478
1479 isIndirect = true;
1480 }
1481
1482 QFile f(cleanFile);
1483 if (!f.open(QIODevice::ReadOnly)) {
1484 yyMsg() << qPrintable(
1485 QStringLiteral("Cannot open %1: %2\n").arg(cleanFile, f.errorString()));
1486 return;
1487 }
1488
1489 QTextStream ts(&f);
1490 ts.setEncoding(yySourceEncoding);
1491 ts.setAutoDetectUnicode(true);
1492
1493 inclusions.insert(cleanFile);
1494 if (isIndirect) {
1495 CppParser parser;
1496 for (const QString &projectRoot : std::as_const(cd.m_projectRoots))
1497 if (cleanFile.startsWith(projectRoot)) {
1498 parser.setTranslator(new Translator);
1499 break;
1500 }
1501 parser.setInput(ts, cleanFile);
1502 QStringList stack = includeStack;
1503 stack << cleanFile;
1504 parser.parse(cd, stack, inclusions);
1505 results->includes.insert(parser.recordResults(true));
1506 } else {
1507 CppParser parser(results);
1508 parser.namespaces = namespaces;
1509 parser.functionContext = functionContext;
1510 parser.functionContextUnresolved = functionContextUnresolved;
1511 parser.setInput(ts, cleanFile);
1512 parser.setTranslator(tor);
1513 QStringList stack = includeStack;
1514 stack << cleanFile;
1515 parser.parseInternal(cd, stack, inclusions);
1516 // Avoid that messages obtained by direct scanning are used
1517 CppFiles::setBlacklisted(cleanFile);
1518 }
1519 inclusions.remove(cleanFile);
1520
1521 prospectiveContext.clear();
1522 pendingContext.clear();
1523}
1524
1525/*
1526 The third part of this source file is the parser. It accomplishes
1527 a very easy task: It finds all strings inside a tr() or translate()
1528 call, and possibly finds out the context of the call. It supports
1529 three cases: (1) the context is specified, as in
1530 FunnyDialog::tr("Hello") or translate("FunnyDialog", "Hello");
1531 (2) the call appears within an inlined function; (3) the call
1532 appears within a function defined outside the class definition.
1533*/
1534
1535bool CppParser::match(TokenType t)
1536{
1537 bool matches = (yyTok == t);
1538 if (matches)
1539 yyTok = getToken();
1540 return matches;
1541}
1542
1543bool CppParser::matchString(QString *s)
1544{
1545 bool matches = false;
1546 s->clear();
1547 forever {
1548 if (yyTok != Tok_String && yyTok != Tok_RawString)
1549 return matches;
1550 matches = true;
1551 if (yyTok == Tok_String)
1552 *s += transcode(yyWord);
1553 else
1554 *s += yyWord;
1555 s->detach();
1556 yyTok = getToken();
1557 }
1558}
1559
1560static const QString strQApplication = u"QApplication"_s;
1561static const QString strQCoreApplication = u"QCoreApplication"_s;
1562static const QString strUnicodeUTF8 = u"UnicodeUTF8"_s;
1563static const QString strDefaultCodec = u"DefaultCodec"_s;
1564static const QString strCodecForTr = u"CodecForTr"_s;
1565static const QString strLatin1 = u"Latin1"_s;
1566
1567bool CppParser::matchEncoding()
1568{
1569 if (yyTok != Tok_Ident)
1570 return false;
1571 if (yyWord == strQApplication || yyWord == strQCoreApplication) {
1572 yyTok = getToken();
1573 if (yyTok == Tok_ColonColon)
1574 yyTok = getToken();
1575 }
1576 if (yyWord == strUnicodeUTF8) {
1577 yyTok = getToken();
1578 return true;
1579 }
1580 if (yyWord == strLatin1 || yyWord == strDefaultCodec || yyWord == strCodecForTr)
1581 yyMsg() << "Unsupported encoding Latin1/DefaultCodec/CodecForTr\n";
1582 return false;
1583}
1584
1585bool CppParser::matchStringOrNull(QString *s)
1586{
1587 return matchString(s) || match(Tok_Null);
1588}
1589
1590/*
1591* Skip over a C++ expression by consuming tokens until reaching an unmatched
1592* closing parenthesis. This is used to skip expression arguments in translate
1593* calls without needing to parse or validate the expression structure.
1594*
1595* The function handles any valid C++ expression, including complex ones with
1596* nested parentheses, operators, casts, templates, and other constructs.
1597*
1598* Returns true if the expression was successfully skipped, false if EOF or
1599* a cancellation token was encountered before finding the end.
1600*/
1601bool CppParser::skipExpression()
1602{
1603 if (match(Tok_Null) || match(Tok_Integer))
1604 return true;
1605
1606 int parenlevel = 0;
1607 while (parenlevel >= 0) {
1608 yyTok = getToken();
1609 if (yyTok == Tok_RightParen)
1610 --parenlevel;
1611 else if (yyTok == Tok_LeftParen)
1612 ++parenlevel;
1613 else if (yyTok == Tok_Cancel || yyTok == Tok_Eof)
1614 return false;
1615 }
1616 return true;
1617}
1618
1619void CppParser::recordMessage(int line, const QString &context, const QString &text,
1620 const QString &comment, const QString &extracomment,
1621 const QString &msgid, const QString &label,
1622 const TranslatorMessage::ExtraData &extra,
1623 bool plural, int startOffset, int endOffset)
1624{
1625 TranslatorMessage msg(transcode(context), text, transcode(comment), QString(), yyFileName, line,
1626 QStringList(), TranslatorMessage::Unfinished, plural);
1627 msg.setExtraComment(transcode(extracomment.simplified()));
1628 msg.setId(msgid);
1629 msg.setExtras(extra);
1630 if (!msgid.isEmpty())
1631 msg.setLabel(label);
1632 msg.setStartOffset(startOffset);
1633 msg.setEndOffset(endOffset);
1634 tor->append(msg);
1635}
1636
1637void CppParser::handleTr(QString &prefix, bool plural)
1638{
1639 if (!m_metaStrings.sourcetext().isEmpty())
1640 yyMsg() << "//% cannot be used with tr() / QT_TR_NOOP(). Ignoring\n";
1641 if (!m_metaStrings.label().isEmpty())
1642 yyMsg() << "labels cannot be used with text-based translation. Ignoring\n";
1643
1644 int line = yyLineNo;
1645 const int startOffset = lookBackFunctionCallStart(prefix.size());
1646 yyTok = getToken();
1647 QString text;
1648 if (matchString(&text)) {
1649 QString comment;
1650 if (yyTok == Tok_RightParen) {
1651 // no comment
1652 } else if (match(Tok_Comma) && matchStringOrNull(&comment)) { //comment
1653 if (yyTok == Tok_RightParen) {
1654 // ok,
1655 } else if (match(Tok_Comma)) {
1656 plural = true;
1657 skipExpression();
1658 }
1659 }
1660 if (!pendingContext.isEmpty() && !prefix.startsWith("::"_L1)) {
1661 NamespaceList unresolved;
1662 if (!fullyQualify(namespaces, pendingContext, true, &functionContext, &unresolved)) {
1663 functionContextUnresolved = stringifyNamespace(0, unresolved);
1664 yyMsg() << qPrintable(
1665 QStringLiteral("Qualifying with unknown namespace/class %1::%2\n")
1666 .arg(stringifyNamespace(functionContext)).arg(unresolved.first().value()));
1667 }
1668 pendingContext.clear();
1669 }
1670 if (prefix.isEmpty()) {
1671 if (functionContextUnresolved.isEmpty()) {
1672 int idx = functionContext.size();
1673 if (idx < 2) {
1674 yyMsg() << "tr() cannot be called without context\n";
1675 return;
1676 }
1677 Namespace *fctx;
1678 while (!(fctx = findNamespace(functionContext, idx)->classDef)->hasTrFunctions) {
1679 if (idx == 1) {
1680 context = stringifyNamespace(functionContext);
1681 fctx = findNamespace(functionContext)->classDef;
1682 if (!fctx->complained) {
1683 yyMsg() << qPrintable(
1684 QStringLiteral("Class '%1' lacks Q_OBJECT macro\n").arg(context));
1685 fctx->complained = true;
1686 }
1687 goto gotctx;
1688 }
1689 --idx;
1690 }
1691 if (fctx->trQualification.isEmpty()) {
1692 context.clear();
1693 for (int i = 1;;) {
1694 context += functionContext.at(i).value();
1695 if (++i == idx)
1696 break;
1697 context += "::"_L1;
1698 }
1699 fctx->trQualification = context;
1700 } else {
1701 context = fctx->trQualification;
1702 }
1703 } else {
1704 context = joinNamespaces(stringifyNamespace(functionContext), functionContextUnresolved);
1705 }
1706 } else {
1707 prefix.chop(2);
1708 NamespaceList nsl;
1709 NamespaceList unresolved;
1710 if (fullyQualify(functionContext, prefix, false, &nsl, &unresolved)) {
1711 Namespace *fctx = findNamespace(nsl)->classDef;
1712 if (fctx->trQualification.isEmpty()) {
1713 context = stringifyNamespace(nsl);
1714 fctx->trQualification = context;
1715 } else {
1716 context = fctx->trQualification;
1717 }
1718 if (!fctx->hasTrFunctions && !fctx->complained) {
1719 yyMsg() << qPrintable(QStringLiteral("Class '%1' lacks Q_OBJECT macro\n")
1720 .arg(context));
1721 fctx->complained = true;
1722 }
1723 } else {
1724 context = joinNamespaces(stringifyNamespace(nsl), stringifyNamespace(0, unresolved));
1725 }
1726 prefix.clear();
1727 }
1728
1729 gotctx:
1730 const int endOffset = yyInPtr - (const ushort *)yyInStr.unicode() - 1;
1731 recordMessage(line, context, text, comment, m_metaStrings.extracomment(),
1732 m_metaStrings.msgid(), QString(), m_metaStrings.extra(),
1733 plural, startOffset, endOffset);
1734 }
1735 m_metaStrings.clear();
1736}
1737
1738void CppParser::handleTranslate(int prefixSize, bool plural)
1739{
1740 if (!m_metaStrings.sourcetext().isEmpty())
1741 yyMsg() << "//% cannot be used with translate() / QT_TRANSLATE_NOOP(). Ignoring\n";
1742 if (!m_metaStrings.label().isEmpty())
1743 yyMsg() << "labels cannot be used with text-based translation. Ignoring\n";
1744 const int startOffset = lookBackFunctionCallStart(prefixSize);
1745 int line = yyLineNo;
1746 yyTok = getToken();
1747 QString text;
1748 if (matchString(&context)
1749 && match(Tok_Comma)
1750 && matchString(&text) && !text.isEmpty())
1751 {
1752 QString comment;
1753 if (yyTok != Tok_RightParen) {
1754 // look for comment
1755 if (match(Tok_Comma) && matchStringOrNull(&comment)) {
1756 if (yyTok != Tok_RightParen) {
1757 // look for encoding
1758 if (match(Tok_Comma)) {
1759 if (matchEncoding()) {
1760 if (yyTok != Tok_RightParen) {
1761 // look for the plural quantifier,
1762 // this can be a number, an identifier or
1763 // a function call,
1764 // so for simplicity we mark it as plural if
1765 // we know we have a comma instead of an
1766 // right parentheses.
1767 plural |= match(Tok_Comma);
1768 }
1769 } else {
1770 // This can be a QTranslator::translate("context",
1771 // "source", "comment", n) plural translation
1772 if (skipExpression() && yyTok == Tok_RightParen) {
1773 plural = true;
1774 } else {
1775 return;
1776 }
1777 }
1778 } else {
1779 return;
1780 }
1781 }
1782 } else {
1783 return;
1784 }
1785 }
1786 const int endOffset = yyInPtr - (const ushort *)yyInStr.unicode() - 1;
1787
1788 recordMessage(line, context, text, comment, m_metaStrings.extracomment(),
1789 m_metaStrings.msgid(), QString(), m_metaStrings.extra(),
1790 plural, startOffset, endOffset);
1791 }
1792 m_metaStrings.clear();
1793}
1794
1795void CppParser::handleTrId(int prefixSize, bool plural)
1796{
1797 if (!m_metaStrings.msgid().isEmpty())
1798 yyMsg() << "//= cannot be used with qtTrId() / QT_TRID_NOOP(). Ignoring\n";
1799 int line = yyLineNo;
1800 const int startOffset = lookBackFunctionCallStart(prefixSize);
1801 yyTok = getToken();
1802 QString msgid;
1803 if (matchString(&msgid) && !msgid.isEmpty()) {
1804 plural |= match(Tok_Comma);
1805 const int endOffset = yyInPtr - (const ushort *)yyInStr.unicode() - 1;
1806
1807 recordMessage(line, QString(), transcode(m_metaStrings.sourcetext()), QString(),
1808 m_metaStrings.extracomment(), msgid, m_metaStrings.label(),
1809 m_metaStrings.extra(), plural, startOffset, endOffset);
1810 }
1811 m_metaStrings.clear();
1812}
1813
1814void CppParser::handleDeclareTrFunctions()
1815{
1816 QString name;
1817 forever {
1818 yyTok = getToken();
1819 if (yyTok != Tok_Ident)
1820 return;
1821 name += yyWord;
1822 name.detach();
1823 yyTok = getToken();
1824 if (yyTok == Tok_RightParen)
1825 break;
1826 if (yyTok != Tok_ColonColon)
1827 return;
1828 name += "::"_L1;
1829 }
1830 Namespace *ns = modifyNamespace(&namespaces);
1831 ns->hasTrFunctions = true;
1832 ns->trQualification = name;
1833 ns->trQualification.detach();
1834}
1835
1836void CppParser::parse(ConversionData &cd, const QStringList &includeStack,
1837 QSet<QString> &inclusions)
1838{
1839 namespaces << HashString();
1840 functionContext = namespaces;
1841 functionContextUnresolved.clear();
1842
1843 parseInternal(cd, includeStack, inclusions);
1844}
1845
1846int CppParser::lookBackFunctionCallStart(int prefixSize)
1847{
1848 const int funcNameOffset = yyInPtr - (const ushort *)yyInStr.unicode();
1849 QStringView prefix = QStringView(yyInStr).sliced(0, funcNameOffset);
1850 do
1851 prefix.chop(1);
1852 while (prefix.back() != '('_L1);
1853 prefix.chop(prefixSize + yyWord.size() + 1);
1854
1855 auto chopMatch = [](auto& str, const auto& match) {
1856 if (str.endsWith(match)) {
1857 str.chop(match.size());
1858 return true;
1859 }
1860 return false;
1861 };
1862 while (chopMatch(prefix, "."_L1) || chopMatch(prefix, "->"_L1)) {
1863 int pos = prefix.size() - 1;
1864 if (prefix.at(pos) == ')'_L1) {
1865 int count = 1;
1866 pos--;
1867 while (count != 0 && pos >= 0) {
1868 if (QChar c = prefix.at(pos--); c == ')'_L1)
1869 count++;
1870 else if (c == '('_L1)
1871 count--;
1872 else if (c == "\""_L1 || c == "'"_L1) {
1873 const QChar quotation = c;
1874 while (pos >= 0) {
1875 c = prefix[pos--];
1876 if (c == '\\'_L1)
1877 pos--;
1878 else if (c == quotation)
1879 break;
1880 }
1881 }
1882 }
1883 }
1884 while (pos >= 0 && (prefix.at(pos).isLetterOrNumber() || prefix.at(pos) == '_'_L1))
1885 pos--;
1886 prefix.slice(0, pos + 1);
1887 }
1888 return prefix.size();
1889}
1890
1891bool CppParser::parseTranslate(QString &prefix)
1892{
1893 bool forcePlural = false;
1894 switch (trFunctionAliasManager.trFunctionByName(yyWord)) {
1895 case TrFunctionAliasManager::Function_Q_DECLARE_TR_FUNCTIONS:
1896 handleDeclareTrFunctions();
1897 break;
1898 case TrFunctionAliasManager::Function_QT_TR_N_NOOP:
1899 forcePlural = true;
1900 Q_FALLTHROUGH();
1901 case TrFunctionAliasManager::Function_tr:
1902 case TrFunctionAliasManager::Function_trUtf8:
1903 case TrFunctionAliasManager::Function_QT_TR_NOOP:
1904 case TrFunctionAliasManager::Function_QT_TR_NOOP_UTF8:
1905 if (tor)
1906 handleTr(prefix, forcePlural);
1907 break;
1908 case TrFunctionAliasManager::Function_QT_TRANSLATE_N_NOOP:
1909 case TrFunctionAliasManager::Function_QT_TRANSLATE_N_NOOP3:
1910 forcePlural = true;
1911 Q_FALLTHROUGH();
1912 case TrFunctionAliasManager::Function_translate:
1913 case TrFunctionAliasManager::Function_findMessage:
1914 case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP:
1915 case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP_UTF8:
1916 case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP3:
1917 case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP3_UTF8:
1918 if (tor)
1919 handleTranslate(prefix.size(), forcePlural);
1920 break;
1921 case TrFunctionAliasManager::Function_QT_TRID_N_NOOP:
1922 forcePlural = true;
1923 Q_FALLTHROUGH();
1924 case TrFunctionAliasManager::Function_qtTrId:
1925 case TrFunctionAliasManager::Function_QT_TRID_NOOP:
1926 if (tor)
1927 handleTrId(prefix.size(), forcePlural);
1928 break;
1929 default:
1930 return false;
1931 }
1932 return true;
1933}
1934
1935void CppParser::parseInternal(ConversionData &cd, const QStringList &includeStack,
1936 QSet<QString> &inclusions)
1937{
1938 static constexpr auto strColons("::"_L1);
1939
1940 QString prefix;
1941 bool yyTokColonSeen = false; // Start of c'tor's initializer list
1942 bool yyTokIdentSeen = false; // Start of initializer (member or base class)
1943 bool maybeInTrailingReturnType = false;
1944
1945 const QList<TokenType> allowedTokensInFnTemplate{
1946 Tok_Ident, Tok_decltype, Tok_ColonColon, Tok_Comma,
1947 Tok_Integer, Tok_LeftAngleBracket, Tok_RightAngleBracket
1948 };
1949 metaExpected = true;
1950
1951 prospectiveContext.clear();
1952 pendingContext.clear();
1953
1954 yyWord.reserve(yyInStr.size()); // Rather insane. That's because we do no length checking.
1955 yyWordInitialCapacity = yyWord.capacity();
1956 yyInPtr = (const ushort *)yyInStr.unicode();
1957 yyCh = getChar();
1958 yyTok = getToken();
1959 while (yyTok != Tok_Eof) {
1960 // these are array indexing operations. we ignore them entirely
1961 // so they don't confuse our scoping of static initializers.
1962 // we enter the loop by either reading a left bracket or by an
1963 // #else popping the state.
1964 if (yyBracketDepth && yyBraceDepth == namespaceDepths.size()) {
1965 yyTok = getToken();
1966 continue;
1967 }
1968 //qDebug() << "TOKEN: " << yyTok;
1969 switch (yyTok) {
1970 case Tok_QuotedInclude: {
1971 QString text = QDir(QFileInfo(yyFileName).absolutePath()).absoluteFilePath(yyWord);
1972 text.detach();
1973 if (QFileInfo(text).isFile()) {
1974 processInclude(text, cd, includeStack, inclusions);
1975 yyTok = getToken();
1976 break;
1977 }
1978 }
1979 Q_FALLTHROUGH();
1980 case Tok_AngledInclude: {
1981 const QStringList cSources = cd.m_allCSources.values(yyWord);
1982 if (!cSources.isEmpty()) {
1983 for (const QString &cSource : cSources)
1984 processInclude(cSource, cd, includeStack, inclusions);
1985 goto incOk;
1986 }
1987 for (const QString &incPath : std::as_const(cd.m_includePath)) {
1988 QString text = QDir(incPath).absoluteFilePath(yyWord);
1989 text.detach();
1990 if (QFileInfo(text).isFile()) {
1991 processInclude(text, cd, includeStack, inclusions);
1992 goto incOk;
1993 }
1994 }
1995 incOk:
1996 yyTok = getToken();
1997 break;
1998 }
1999 case Tok_friend:
2000 yyTok = getToken();
2001 // These are forward declarations, so ignore them.
2002 if (yyTok == Tok_class)
2003 yyTok = getToken();
2004 break;
2005 case Tok_class:
2006 /*
2007 Partial support for inlined functions.
2008 */
2009
2010 case_class:
2011 yyTok = getToken();
2012 if (yyTok == Tok_Equals) { // we're in a template entity
2013 yyTok = getToken();
2014 break;
2015 } else if (yyBraceDepth == namespaceDepths.size() && yyParenDepth == 0) {
2016 NamespaceList quali;
2017 HashString fct;
2018
2019 // Find class name including qualification
2020 forever {
2021 QString text = yyWord;
2022 text.detach();
2023 fct.setValue(text);
2024 yyTok = getToken();
2025
2026 if (yyTok == Tok_ColonColon) {
2027 quali << fct;
2028 yyTok = getToken();
2029 } else if (yyTok == Tok_Ident) {
2030 if (yyWord == strfinal) {
2031 // C++11: final may appear immediately after the name of the class
2032 yyTok = getToken();
2033 break;
2034 }
2035
2036 // Handle impure definitions such as 'class Q_EXPORT QMessageBox', in
2037 // which case 'QMessageBox' is the class name, not 'Q_EXPORT', by
2038 // abandoning any qualification collected so far.
2039 quali.clear();
2040 } else if (yyTok == Tok_Attribute) {
2041 yyTok = getToken();
2042 } else {
2043 break;
2044 }
2045 }
2046
2047 if (yyTok == Tok_Colon || yyTok == Tok_LeftAngleBracket) {
2048 // Skip any token until '{' or ';' since we might do things wrong if we find
2049 // a '::' or ':' token here.
2050 do {
2051 yyTok = getToken();
2052 tokenInTemplate:
2053 if (yyTok == Tok_Eof)
2054 goto goteof;
2055 if (yyTok == Tok_Cancel)
2056 goto case_default;
2057 if (yyTok == Tok_class)
2058 goto case_class;
2059 if (yyTok == Tok_Ident) {
2060 yyTok = getToken();
2061 if (yyTok == Tok_LeftParen)
2062 parseTranslate(prefix);
2063 else
2064 goto tokenInTemplate;
2065 }
2066 } while (yyTok != Tok_LeftBrace && yyTok != Tok_Semicolon);
2067 if (yyTok == Tok_Semicolon)
2068 break;
2069 } else {
2070 if (yyTok != Tok_LeftBrace) {
2071 // Obviously a forward declaration. We skip those, as they
2072 // don't create actually usable namespaces.
2073 break;
2074 }
2075 }
2076
2077 if (!quali.isEmpty()) {
2078 // Forward-declared class definitions can be namespaced.
2079 NamespaceList nsl;
2080 if (!fullyQualify(namespaces, quali, true, &nsl, 0)) {
2081 yyMsg() << "Ignoring definition of undeclared qualified class\n";
2082 break;
2083 }
2084 namespaceDepths.push(namespaces.size());
2085 namespaces = nsl;
2086 } else {
2087 namespaceDepths.push(namespaces.size());
2088 }
2089 enterNamespace(&namespaces, fct);
2090
2091 // Mark this namespace as a class
2092 Namespace *ns = modifyNamespace(&namespaces);
2093 ns->isClass = true;
2094
2095 functionContext = namespaces;
2096 functionContextUnresolved.clear(); // Pointless
2097 prospectiveContext.clear();
2098 pendingContext.clear();
2099
2100 metaExpected = true;
2101 yyTok = getToken();
2102 }
2103 break;
2104 case Tok_namespace:
2105 yyTok = getToken();
2106 // Skip C++11 attributes on namespace
2107 while (yyTok == Tok_Attribute)
2108 yyTok = getToken();
2109 if (yyTok == Tok_Ident) {
2110 QString text = yyWord;
2111 text.detach();
2112 HashString ns = HashString(text);
2113 NamespaceList nestedNamespaces;
2114 forever {
2115 yyTok = getToken();
2116 if (yyTok != Tok_ColonColon)
2117 break;
2118 yyTok = getToken();
2119 if (yyTok != Tok_Ident)
2120 break; // whoops
2121 nestedNamespaces.append(ns);
2122 text = yyWord;
2123 text.detach();
2124 ns = HashString(text);
2125 }
2126 if (yyTok == Tok_LeftBrace) {
2127 namespaceDepths.push(namespaces.size());
2128 for (const auto &nns : nestedNamespaces)
2129 enterNamespace(&namespaces, nns);
2130 enterNamespace(&namespaces, ns);
2131
2132 functionContext = namespaces;
2133 functionContextUnresolved.clear();
2134 prospectiveContext.clear();
2135 pendingContext.clear();
2136 metaExpected = true;
2137 yyTok = getToken();
2138 } else if (yyTok == Tok_Equals) {
2139 // e.g. namespace Is = OuterSpace::InnerSpace;
2140 // Note: 'Is' being qualified is invalid per C++17.
2141 NamespaceList fullName;
2142 yyTok = getToken();
2143 if (yyTok == Tok_ColonColon)
2144 fullName.append(HashString(QString()));
2145 while (yyTok == Tok_ColonColon || yyTok == Tok_Ident) {
2146 if (yyTok == Tok_Ident) {
2147 text = yyWord;
2148 text.detach();
2149 fullName.append(HashString(text));
2150 }
2151 yyTok = getToken();
2152 }
2153 if (fullName.isEmpty())
2154 break;
2155 fullName.append(HashString(QString())); // Mark as unresolved
2156 modifyNamespace(&namespaces)->aliases[ns] = fullName;
2157 }
2158 } else if (yyTok == Tok_LeftBrace) {
2159 // Anonymous namespace
2160 namespaceDepths.push(namespaces.size());
2161 metaExpected = true;
2162 yyTok = getToken();
2163 }
2164 break;
2165 case Tok_using:
2166 yyTok = getToken();
2167 // XXX this should affect only the current scope, not the entire current namespace
2168 if (yyTok == Tok_namespace) {
2169 NamespaceList fullName;
2170 yyTok = getToken();
2171 if (yyTok == Tok_ColonColon)
2172 fullName.append(HashString(QString()));
2173 while (yyTok == Tok_ColonColon || yyTok == Tok_Ident) {
2174 if (yyTok == Tok_Ident) {
2175 QString text = yyWord;
2176 text.detach();
2177 fullName.append(HashString(text));
2178 }
2179 yyTok = getToken();
2180 }
2181 NamespaceList nsl;
2182 if (fullyQualify(namespaces, fullName, false, &nsl, 0))
2183 modifyNamespace(&namespaces)->usings << HashStringList(nsl);
2184 } else {
2185 NamespaceList fullName;
2186 if (yyTok == Tok_ColonColon)
2187 fullName.append(HashString(QString()));
2188 while (yyTok == Tok_ColonColon || yyTok == Tok_Ident) {
2189 if (yyTok == Tok_Ident) {
2190 QString text = yyWord;
2191 text.detach();
2192 fullName.append(HashString(text));
2193 }
2194 yyTok = getToken();
2195 }
2196 if (fullName.isEmpty())
2197 break;
2198 // using-declarations cannot rename classes, so the last element of
2199 // fullName is already the resolved name we actually want.
2200 // As we do no resolution here, we'll collect useless usings of data
2201 // members and methods as well. This is no big deal.
2202 fullName.append(HashString(QString())); // Mark as unresolved
2203 const HashString &ns = *(fullName.constEnd() - 2);
2204 modifyNamespace(&namespaces)->aliases[ns] = fullName;
2205 }
2206 break;
2207 case Tok_Q_OBJECT:
2208 modifyNamespace(&namespaces)->hasTrFunctions = true;
2209 yyTok = getToken();
2210 break;
2211 case Tok_Ident: {
2212 if (yyTokColonSeen &&
2213 yyBraceDepth == namespaceDepths.size() && yyParenDepth == 0) {
2214 // member or base class identifier
2215 yyTokIdentSeen = true;
2216 }
2217 yyTok = getToken();
2218 QString className;
2219 bool methodSpecialization = false;
2220 if (yyTok == Tok_LeftAngleBracket) { // maybe a method specialization
2221 int count = 1;
2222 className = yyWord;
2223 className.detach();
2224 yyTok = getToken();
2225 while (count && allowedTokensInFnTemplate.contains(yyTok)) {
2226 if (yyTok == Tok_LeftAngleBracket)
2227 count++;
2228 else if (yyTok == Tok_RightAngleBracket)
2229 count--;
2230 yyTok = getToken();
2231 }
2232 methodSpecialization = count == 0;
2233 }
2234 if (yyTok == Tok_LeftParen) {
2235 if (parseTranslate(prefix)) {
2236 yyTok = getToken();
2237 break;
2238 } else {
2239 prefix.clear();
2240 }
2241 }
2242 if (yyTok == Tok_ColonColon && !maybeInTrailingReturnType && !yyTrailingSpace) {
2243 prefix += methodSpecialization ? className : yyWord;
2244 prefix.detach();
2245 } else {
2246 // Breaking the :: chain in case of a return/member type
2247 if (!prefix.isEmpty() && yyTok != Tok_LeftParen && yyTok != Tok_RightParen
2248 && yyTok != Tok_LeftBracket && yyTok != Tok_Equals) {
2249 // We're breaking the chain NOT at a function call or member initialization
2250 // This means prospectiveContext was from a return/member type
2251 prospectiveContext.clear();
2252 }
2253 prefix.clear();
2254 }
2255 metaExpected = false;
2256 break;
2257 }
2258 case Tok_Arrow:
2259 if (yyParenDepth == 0 && yyBraceDepth == namespaceDepths.size())
2260 maybeInTrailingReturnType = true;
2261 yyTok = getToken();
2262 if (yyTok == Tok_Ident) {
2263 yyTok = getToken();
2264 if (yyTok == Tok_LeftParen) {
2265 switch (trFunctionAliasManager.trFunctionByName(yyWord)) {
2266 case TrFunctionAliasManager::Function_tr:
2267 case TrFunctionAliasManager::Function_trUtf8:
2268 yyMsg() << "Cannot invoke tr() like this\n";
2269 break;
2270 }
2271 }
2272 }
2273 break;
2274 case Tok_ColonColon:
2275 if (yyTokIdentSeen || maybeInTrailingReturnType) {
2276 // member or base class identifier
2277 yyTok = getToken();
2278 break;
2279 }
2280 if (yyBraceDepth == namespaceDepths.size() && yyParenDepth == 0 && !yyTokColonSeen)
2281 prospectiveContext = prefix;
2282 if (!prefix.isEmpty())
2283 prefix += strColons;
2284 yyTok = getToken();
2285 break;
2286 case Tok_RightBrace:
2287 if (!yyTokColonSeen) {
2288 if (yyBraceDepth + 1 == namespaceDepths.size()) {
2289 // class or namespace
2290 truncateNamespaces(&namespaces, namespaceDepths.pop());
2291 }
2292 if (yyBraceDepth == namespaceDepths.size()) {
2293 // function, class or namespace
2294 if (!yyBraceDepth && !directInclude)
2295 truncateNamespaces(&functionContext, 1);
2296 else
2297 functionContext = namespaces;
2298 functionContextUnresolved.clear();
2299 pendingContext.clear();
2300 }
2301 }
2302 Q_FALLTHROUGH();
2303 case Tok_Semicolon:
2304 maybeInTrailingReturnType = false;
2305 prospectiveContext.clear();
2306 prefix.clear();
2307 if (m_metaStrings.hasData()) {
2308 yyMsg() << "Discarding unconsumed meta data\n";
2309 m_metaStrings.clear();
2310 }
2311 metaExpected = true;
2312 yyTok = getToken();
2313 break;
2314 case Tok_Access:
2315 // Eat access specifiers, so their colons are not mistaken for c'tor initializer list starts
2316 do {
2317 yyTok = getToken();
2318 } while (yyTok == Tok_Access); // Multiple specifiers are possible, e.g. "public slots"
2319 metaExpected = true;
2320 if (yyTok == Tok_Colon)
2321 goto case_default;
2322 break;
2323 case Tok_Colon:
2324 case Tok_Equals:
2325 if (yyBraceDepth == namespaceDepths.size() && yyParenDepth == 0) {
2326 if (!prospectiveContext.isEmpty()) {
2327 pendingContext = prospectiveContext;
2328 prospectiveContext.clear();
2329 }
2330 //ignore colons for bitfields (are usually followed by a semicolon)
2331 if (yyTok == Tok_Colon) {
2332 if (lookAheadToSemicolonOrLeftBrace() != Tok_Semicolon)
2333 yyTokColonSeen = true;
2334 }
2335 }
2336 metaExpected = true;
2337 yyTok = getToken();
2338 break;
2339 case Tok_LeftBrace:
2340 if (yyBraceDepth == namespaceDepths.size() + 1 && yyParenDepth == 0) {
2341 if (!prospectiveContext.isEmpty()) {
2342 pendingContext = prospectiveContext;
2343 prospectiveContext.clear();
2344 }
2345 if (!yyTokIdentSeen) {
2346 // Function body
2347 yyTokColonSeen = false;
2348 }
2349 }
2350 maybeInTrailingReturnType = false;
2351 yyTokIdentSeen = false;
2352 metaExpected = true;
2353 yyTok = getToken();
2354 break;
2355 case Tok_LeftParen:
2356 if (!yyTokColonSeen && yyBraceDepth == namespaceDepths.size() && yyParenDepth == 1
2357 && !prospectiveContext.isEmpty()) {
2358 pendingContext = prospectiveContext;
2359 prospectiveContext.clear();
2360 }
2361 yyTokIdentSeen = false;
2362 metaExpected = true;
2363 yyTok = getToken();
2364 break;
2365 case Tok_Comma:
2366 case Tok_QuestionMark:
2367 metaExpected = true;
2368 yyTok = getToken();
2369 break;
2370 case Tok_RightParen:
2371 if (yyParenDepth == 0 && !yyTokColonSeen && !pendingContext.isEmpty()
2372 && yyBraceDepth == namespaceDepths.size()) {
2373 // Demote the pendingContext to prospectiveContext.
2374 prospectiveContext = pendingContext;
2375 pendingContext.clear();
2376 }
2377 metaExpected = true;
2378 yyTok = getToken();
2379 break;
2380 case Tok_decltype:
2381 {
2382 // Save the parentheses depth outside the 'decltype' specifier.
2383 auto initialParenDepth = yyParenDepth;
2384
2385 // Eat the opening parenthesis that follows 'decltype'.
2386 yyTok = getToken();
2387
2388 // Skip over everything within the parentheses that follow 'decltype'.
2389 while (yyParenDepth != initialParenDepth && yyTok != Tok_Eof)
2390 yyTok = getToken();
2391 }
2392 break;
2393 case Tok_enum:
2394 yyTok = getToken();
2395 // If it is an enum class then ignore
2396 if (yyTok == Tok_class)
2397 yyTok = getToken();
2398
2399 // Allow the parser to flexibly detect and ignore
2400 // colons in front of the typed enums.
2401 yyTok = getToken();
2402 if (yyTok == Tok_Colon) // ignore any colons in front of a typed enum
2403 yyTok = getToken();
2404 break;
2405 default:
2406 if (!yyParenDepth && !maybeInTrailingReturnType)
2407 prospectiveContext.clear();
2408 Q_FALLTHROUGH();
2409 case Tok_RightBracket: // ignoring indexing; for static initializers
2410 case_default:
2411 yyTok = getToken();
2412 break;
2413 }
2414 }
2415
2416 goteof:
2417 if (yyBraceDepth != 0)
2418 yyMsg(yyBraceLineNo)
2419 << "Unbalanced opening brace in C++ code (or abuse of the C++ preprocessor)\n";
2420 else if (yyParenDepth != 0)
2421 yyMsg(yyParenLineNo)
2422 << "Unbalanced opening parenthesis in C++ code"
2423 " (or abuse of the C++ preprocessor)\n";
2424 else if (yyBracketDepth != 0)
2425 yyMsg(yyBracketLineNo)
2426 << "Unbalanced opening bracket in C++ code"
2427 " (or abuse of the C++ preprocessor)\n";
2428}
2429
2430void CppParser::processComment()
2431{
2432 if (!tor || !metaExpected)
2433 return;
2434
2435 if (!m_metaStrings.parse(yyWord)) {
2436 yyMsg() << m_metaStrings.popError().toStdString();
2437 return;
2438 }
2439
2440 if (!m_metaStrings.label().isEmpty()) {
2441 // If pendingContext is set, it may contain aliases that need resolution
2442 NamespaceList contextForLabels = functionContext;
2443 if (!pendingContext.isEmpty())
2444 fullyQualify(namespaces, pendingContext, true, &contextForLabels, nullptr);
2445
2446 QString context = stringifyNamespace(contextForLabels);
2447 QString className;
2448
2449 // Extract class name if the current context is a class
2450 if (const Namespace *ns = findNamespace(contextForLabels); ns && ns->isClass) {
2451 int idx = context.lastIndexOf("::"_L1);
2452 className = idx >= 0 ? context.mid(idx + 2) : context;
2453 }
2454
2455 if (!m_metaStrings.resolveLabel(yyFileName, context, className))
2456 yyMsg() << m_metaStrings.popError().toStdString();
2457 }
2458
2459 if (m_metaStrings.magicComment()) {
2460 auto [context, comment] = *m_metaStrings.magicComment();
2461 TranslatorMessage msg(transcode(context), QString(), transcode(comment), QString(),
2462 yyFileName, yyLineNo, QStringList(), TranslatorMessage::Finished,
2463 false);
2464 msg.setExtraComment(transcode(m_metaStrings.extracomment().simplified()));
2465 tor->append(msg);
2466 tor->setExtras(m_metaStrings.extra());
2467 m_metaStrings.clear();
2468 }
2469}
2470
2472{
2473 if (tor) {
2474 if (tor->messageCount()) {
2475 CppFiles::setTranslator(yyFileName, tor);
2476 } else {
2477 delete tor;
2478 tor = 0;
2479 }
2480 }
2481 if (isHeader) {
2482 const ParseResults *pr;
2483 if (!tor && results->includes.size() == 1
2484 && results->rootNamespace.children.isEmpty()
2485 && results->rootNamespace.aliases.isEmpty()
2486 && results->rootNamespace.usings.isEmpty()) {
2487 // This is a forwarding header. Slash it.
2488 pr = *results->includes.cbegin();
2489 delete results;
2490 } else {
2491 results->fileId = nextFileId++;
2492 pr = results;
2493 }
2494 CppFiles::setResults(ResultsCacheKey(yyFileName, *this), pr);
2495 return pr;
2496 } else {
2497 delete results;
2498 return 0;
2499 }
2500}
2501
2502void loadCPP(Translator &translator, const QStringList &filenames, ConversionData &cd)
2503{
2504 QStringConverter::Encoding e = cd.m_sourceIsUtf16 ? QStringConverter::Utf16 : QStringConverter::Utf8;
2505
2506 for (const QString &filename : filenames) {
2507 if (!CppFiles::getResults(ResultsCacheKey(filename)).isEmpty() || CppFiles::isBlacklisted(filename))
2508 continue;
2509
2510 QFile file(filename);
2511 if (!file.open(QIODevice::ReadOnly)) {
2512 cd.appendError(QStringLiteral("Cannot open %1: %2").arg(filename,
2513 file.errorString()));
2514 continue;
2515 }
2516
2517 CppParser parser;
2518 QTextStream ts(&file);
2519 ts.setEncoding(e);
2520 ts.setAutoDetectUnicode(true);
2521 parser.setInput(ts, filename);
2522 Translator *tor = new Translator;
2523 parser.setTranslator(tor);
2524 QSet<QString> inclusions;
2525 parser.parse(cd, QStringList(), inclusions);
2526 parser.recordResults(isHeader(filename));
2527 }
2528
2529 for (const QString &filename : filenames) {
2530 if (!CppFiles::isBlacklisted(filename)) {
2531 if (const Translator *tor = CppFiles::getTranslator(filename)) {
2532 for (const TranslatorMessage &msg : tor->messages())
2533 translator.extend(msg, cd);
2534 }
2535 }
2536 }
2537}
2538
2539QT_END_NAMESPACE
static void setResults(const ResultsCacheKey &key, const ParseResults *results)
Definition cpp.cpp:1386
bool parseTranslate(QString &prefix)
Definition cpp.cpp:1891
void setInput(QTextStream &ts, const QString &fileName)
Definition cpp.cpp:280
void setTranslator(Translator *_tor)
Definition cpp.cpp:79
void setInput(const QString &in)
Definition cpp.cpp:273
void deleteResults()
Definition cpp.cpp:85
void parseInternal(ConversionData &cd, const QStringList &includeStack, QSet< QString > &inclusions)
Definition cpp.cpp:1935
CppParser(ParseResults *results=0)
Definition cpp.cpp:245
void parse(ConversionData &cd, const QStringList &includeStack, QSet< QString > &inclusions)
Definition cpp.cpp:1836
const ParseResults * recordResults(bool isHeader)
Definition cpp.cpp:2471
void setEndOffset(int endOffset)
QHash< QString, QString > ExtraData
void setExtras(const ExtraData &extras)
void setStartOffset(int startOffset)
void setExtras(const ExtraData &extras)
Definition translator.h:156
void append(const TranslatorMessage &msg)
int messageCount() const
Definition translator.h:137
bool tryVisit(int fileId)
Definition cpp.cpp:62
VisitRecorder()
Definition cpp.cpp:58
static bool isStringLiteralPrefix(const QStringView s)
Definition cpp.cpp:368
static const QString strclass
Definition cpp.cpp:386
static const QString strreturn
Definition cpp.cpp:396
QDebug operator<<(QDebug debug, const HashStringList &lst)
Definition cpp.cpp:49
static const QString strQCoreApplication
Definition cpp.cpp:1561
static const QString strQ_SIGNALS
Definition cpp.cpp:405
static const QString strnamespace
Definition cpp.cpp:391
static const QString strfinal
Definition cpp.cpp:389
static const QString strenum
Definition cpp.cpp:388
size_t qHash(const ResultsCacheKey &key, size_t seed)
Definition cpp.cpp:1348
static const QString strnullptr
Definition cpp.cpp:392
static const QString strsignals
Definition cpp.cpp:403
size_t qHash(const HashStringList &list)
Definition cpp.cpp:36
static const QString strCodecForTr
Definition cpp.cpp:1564
static const QString strpublic
Definition cpp.cpp:401
static const QString strQ_SLOTS
Definition cpp.cpp:404
static const QString strUnicodeUTF8
Definition cpp.cpp:1562
static const QString strprotected
Definition cpp.cpp:400
static const QString strLatin1
Definition cpp.cpp:1565
static int nextFileId
Definition cpp.cpp:54
static bool isHeader(const QString &name)
Definition cpp.cpp:1442
static const QString strNULL
Definition cpp.cpp:394
static const QString strstruct
Definition cpp.cpp:397
static bool isRawStringLiteralPrefix(QStringView s)
Definition cpp.cpp:376
static const QString stroperator
Definition cpp.cpp:395
static const QString strusing
Definition cpp.cpp:398
static const QString strQ_OBJECT
Definition cpp.cpp:385
static const QString strQ_NULLPTR
Definition cpp.cpp:393
size_t qHash(const HashString &str)
Definition cpp.cpp:24
static const QString strQApplication
Definition cpp.cpp:1560
size_t qHash(const CppParserState &s, size_t seed)
Definition cpp.cpp:1338
static const QString strDefaultCodec
Definition cpp.cpp:1563
static const QString strprivate
Definition cpp.cpp:399
static const QString strfriend
Definition cpp.cpp:390
void loadCPP(Translator &translator, const QStringList &filenames, ConversionData &cd)
Definition cpp.cpp:2502
QDebug operator<<(QDebug debug, const HashString &s)
Definition cpp.cpp:31
static const QString strslots
Definition cpp.cpp:402
static const QString strdecltype
Definition cpp.cpp:387
QHash< QString, const Translator * > TranslatorHash
Definition cpp.h:130
QList< HashString > NamespaceList
Definition cpp.h:38
QHash< ResultsCacheKey, IncludeCycle * > IncludeCycleHash
Definition cpp.h:129
Namespace * classDef
Definition cpp.h:64
Namespace * parent
Definition cpp.h:48
bool complained
Definition cpp.h:69
bool isClass
Definition cpp.h:70
bool hasTrFunctions
Definition cpp.h:68
int fileId
Definition cpp.h:74
NamespaceList * resolved
Definition cpp.cpp:1085
const HashString & segment
Definition cpp.cpp:1084
Namespace const ** resolvedNamespace
Definition cpp.cpp:1087
const NamespaceList & namespaces
Definition cpp.cpp:1082
QSet< HashStringList > * visitedUsings
Definition cpp.cpp:1086
QualifyOneData(const NamespaceList &ns, int nsc, const HashString &seg, NamespaceList *rslvd, QSet< HashStringList > *visited, Namespace const **resolvedNs)
Definition cpp.cpp:1072
TrFunctionAliasManager trFunctionAliasManager
Definition trparser.cpp:153