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 = 1;
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 || yyCh != ']') {
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 return false;
1274}
1275
1276bool CppParser::fullyQualify(const NamespaceList &namespaces,
1277 const NamespaceList &segments, bool isDeclaration,
1278 NamespaceList *resolved, NamespaceList *unresolved) const
1279{
1280 return fullyQualify(namespaces, namespaces.size(),
1281 segments, isDeclaration, resolved, unresolved);
1282}
1283
1284bool CppParser::fullyQualify(const NamespaceList &namespaces,
1285 const QString &quali, bool isDeclaration,
1286 NamespaceList *resolved, NamespaceList *unresolved) const
1287{
1288 NamespaceList segments;
1289 for (const QString &str : quali.split("::"_L1)) // XXX slow, but needs to be fast(?)
1290 segments << HashString(str);
1291 return fullyQualify(namespaces, segments, isDeclaration, resolved, unresolved);
1292}
1293
1294bool CppParser::findNamespaceCallback(const Namespace *ns, void *context) const
1295{
1296 *((const Namespace **)context) = ns;
1297 return true;
1298}
1299
1300Namespace *CppParser::findNamespace(const NamespaceList &namespaces, int nsCount) const
1301{
1302 Namespace *ns = 0;
1303 if (nsCount == -1)
1304 nsCount = namespaces.size();
1305 visitNamespace(namespaces, nsCount, &CppParser::findNamespaceCallback, &ns);
1306 return ns;
1307}
1308
1309void CppParser::enterNamespace(NamespaceList *namespaces, const HashString &name)
1310{
1311 *namespaces << name;
1312 Namespace *ns;
1313 if (!(ns = findNamespace(*namespaces)))
1314 ns = modifyNamespace(namespaces, false);
1315
1316 const Namespace *cns = &results->rootNamespace;
1317 for (int i = 1; i < namespaces->size(); ++i) {
1318 ns->usings << cns->usings;
1319 if (!(cns = cns->children.value(namespaces->at(i))))
1320 break;
1321 }
1322}
1323
1324void CppParser::truncateNamespaces(NamespaceList *namespaces, int length)
1325{
1326 if (namespaces->size() > length)
1327 namespaces->erase(namespaces->begin() + length, namespaces->end());
1328}
1329
1330
1331/*
1332 Functions for processing include files.
1333*/
1334
1335size_t qHash(const CppParserState &s, size_t seed)
1336{
1337 seed = qHash(s.namespaces, seed);
1338 seed = qHash(s.namespaceDepths, seed);
1339 seed = qHash(s.functionContext, seed);
1340 seed = qHash(s.functionContextUnresolved, seed);
1341 seed = qHash(s.pendingContext, seed);
1342 return seed;
1343}
1344
1345size_t qHash(const ResultsCacheKey &key, size_t seed)
1346{
1347 seed = qHash(key.cleanFile, seed);
1348 seed = qHash(key.parserState, seed);
1349 return seed;
1350}
1351
1352IncludeCycleHash &CppFiles::includeCycles()
1353{
1354 static IncludeCycleHash cycles;
1355
1356 return cycles;
1357}
1358
1359TranslatorHash &CppFiles::translatedFiles()
1360{
1361 static TranslatorHash tors;
1362
1363 return tors;
1364}
1365
1366QSet<QString> &CppFiles::blacklistedFiles()
1367{
1368 static QSet<QString> blacklisted;
1369
1370 return blacklisted;
1371}
1372
1373QSet<const ParseResults *> CppFiles::getResults(const ResultsCacheKey &key)
1374{
1375 IncludeCycle * const cycle = includeCycles().value(key);
1376
1377 if (cycle)
1378 return cycle->results;
1379 else
1380 return QSet<const ParseResults *>();
1381}
1382
1383void CppFiles::setResults(const ResultsCacheKey &key, const ParseResults *results)
1384{
1385 IncludeCycle *cycle = includeCycles().value(key);
1386
1387 if (!cycle) {
1388 cycle = new IncludeCycle;
1389 includeCycles().insert(key, cycle);
1390 }
1391
1392 cycle->fileNames.insert(key.cleanFile);
1393 cycle->results.insert(results);
1394}
1395
1396const Translator *CppFiles::getTranslator(const QString &cleanFile)
1397{
1398 return translatedFiles().value(cleanFile);
1399}
1400
1401void CppFiles::setTranslator(const QString &cleanFile, const Translator *tor)
1402{
1403 translatedFiles().insert(cleanFile, tor);
1404}
1405
1406bool CppFiles::isBlacklisted(const QString &cleanFile)
1407{
1408 return blacklistedFiles().contains(cleanFile);
1409}
1410
1411void CppFiles::setBlacklisted(const QString &cleanFile)
1412{
1413 blacklistedFiles().insert(cleanFile);
1414}
1415
1416void CppFiles::addIncludeCycle(const QSet<QString> &fileNames, const CppParserState &parserState)
1417{
1418 IncludeCycle * const cycle = new IncludeCycle;
1419 cycle->fileNames = fileNames;
1420
1421 QSet<IncludeCycle *> intersectingCycles;
1422 for (const QString &fileName : fileNames) {
1423 const ResultsCacheKey key = { fileName, parserState };
1424 IncludeCycle *intersectingCycle = includeCycles().value(key);
1425
1426 if (intersectingCycle && !intersectingCycles.contains(intersectingCycle)) {
1427 intersectingCycles.insert(intersectingCycle);
1428
1429 cycle->fileNames.unite(intersectingCycle->fileNames);
1430 cycle->results.unite(intersectingCycle->results);
1431 }
1432 }
1433 qDeleteAll(intersectingCycles);
1434
1435 for (const QString &fileName : std::as_const(cycle->fileNames))
1436 includeCycles().insert({ fileName, parserState }, cycle);
1437}
1438
1439static bool isHeader(const QString &name)
1440{
1441 QString fileExt = QFileInfo(name).suffix();
1442 return fileExt.isEmpty() || fileExt.startsWith(u'h', Qt::CaseInsensitive);
1443}
1444
1445void CppParser::processInclude(const QString &file, ConversionData &cd, const QStringList &includeStack,
1446 QSet<QString> &inclusions)
1447{
1448 QString cleanFile = QDir::cleanPath(file);
1449
1450 for (const QRegularExpression &rx : std::as_const(cd.m_excludes)) {
1451 if (rx.match(cleanFile).hasMatch())
1452 return;
1453 }
1454
1455 const int index = includeStack.indexOf(cleanFile);
1456 if (index != -1) {
1457 CppFiles::addIncludeCycle(QSet<QString>(includeStack.cbegin() + index, includeStack.cend()),
1458 *this);
1459 return;
1460 }
1461
1462 // If the #include has been blacklisted previously,
1463 // or is not a header file (stdc++ extensionless or *.h*), then really include
1464 // it. Otherwise it is safe to process it stand-alone and re-use the parsed
1465 // namespace data for inclusion into other files.
1466 bool isIndirect = false;
1467 if (!CppFiles::isBlacklisted(cleanFile)
1468 && isHeader(cleanFile)) {
1469
1470 QSet<const ParseResults *> res = CppFiles::getResults(ResultsCacheKey(cleanFile, *this));
1471 if (!res.isEmpty()) {
1472 results->includes.unite(res);
1473 return;
1474 }
1475
1476 isIndirect = true;
1477 }
1478
1479 QFile f(cleanFile);
1480 if (!f.open(QIODevice::ReadOnly)) {
1481 yyMsg() << qPrintable(
1482 QStringLiteral("Cannot open %1: %2\n").arg(cleanFile, f.errorString()));
1483 return;
1484 }
1485
1486 QTextStream ts(&f);
1487 ts.setEncoding(yySourceEncoding);
1488 ts.setAutoDetectUnicode(true);
1489
1490 inclusions.insert(cleanFile);
1491 if (isIndirect) {
1492 CppParser parser;
1493 for (const QString &projectRoot : std::as_const(cd.m_projectRoots))
1494 if (cleanFile.startsWith(projectRoot)) {
1495 parser.setTranslator(new Translator);
1496 break;
1497 }
1498 parser.setInput(ts, cleanFile);
1499 QStringList stack = includeStack;
1500 stack << cleanFile;
1501 parser.parse(cd, stack, inclusions);
1502 results->includes.insert(parser.recordResults(true));
1503 } else {
1504 CppParser parser(results);
1505 parser.namespaces = namespaces;
1506 parser.functionContext = functionContext;
1507 parser.functionContextUnresolved = functionContextUnresolved;
1508 parser.setInput(ts, cleanFile);
1509 parser.setTranslator(tor);
1510 QStringList stack = includeStack;
1511 stack << cleanFile;
1512 parser.parseInternal(cd, stack, inclusions);
1513 // Avoid that messages obtained by direct scanning are used
1514 CppFiles::setBlacklisted(cleanFile);
1515 }
1516 inclusions.remove(cleanFile);
1517
1518 prospectiveContext.clear();
1519 pendingContext.clear();
1520}
1521
1522/*
1523 The third part of this source file is the parser. It accomplishes
1524 a very easy task: It finds all strings inside a tr() or translate()
1525 call, and possibly finds out the context of the call. It supports
1526 three cases: (1) the context is specified, as in
1527 FunnyDialog::tr("Hello") or translate("FunnyDialog", "Hello");
1528 (2) the call appears within an inlined function; (3) the call
1529 appears within a function defined outside the class definition.
1530*/
1531
1532bool CppParser::match(TokenType t)
1533{
1534 bool matches = (yyTok == t);
1535 if (matches)
1536 yyTok = getToken();
1537 return matches;
1538}
1539
1540bool CppParser::matchString(QString *s)
1541{
1542 bool matches = false;
1543 s->clear();
1544 forever {
1545 if (yyTok != Tok_String && yyTok != Tok_RawString)
1546 return matches;
1547 matches = true;
1548 if (yyTok == Tok_String)
1549 *s += transcode(yyWord);
1550 else
1551 *s += yyWord;
1552 s->detach();
1553 yyTok = getToken();
1554 }
1555}
1556
1557static const QString strQApplication = u"QApplication"_s;
1558static const QString strQCoreApplication = u"QCoreApplication"_s;
1559static const QString strUnicodeUTF8 = u"UnicodeUTF8"_s;
1560static const QString strDefaultCodec = u"DefaultCodec"_s;
1561static const QString strCodecForTr = u"CodecForTr"_s;
1562static const QString strLatin1 = u"Latin1"_s;
1563
1564bool CppParser::matchEncoding()
1565{
1566 if (yyTok != Tok_Ident)
1567 return false;
1568 if (yyWord == strQApplication || yyWord == strQCoreApplication) {
1569 yyTok = getToken();
1570 if (yyTok == Tok_ColonColon)
1571 yyTok = getToken();
1572 }
1573 if (yyWord == strUnicodeUTF8) {
1574 yyTok = getToken();
1575 return true;
1576 }
1577 if (yyWord == strLatin1 || yyWord == strDefaultCodec || yyWord == strCodecForTr)
1578 yyMsg() << "Unsupported encoding Latin1/DefaultCodec/CodecForTr\n";
1579 return false;
1580}
1581
1582bool CppParser::matchStringOrNull(QString *s)
1583{
1584 return matchString(s) || match(Tok_Null);
1585}
1586
1587/*
1588* Skip over a C++ expression by consuming tokens until reaching an unmatched
1589* closing parenthesis. This is used to skip expression arguments in translate
1590* calls without needing to parse or validate the expression structure.
1591*
1592* The function handles any valid C++ expression, including complex ones with
1593* nested parentheses, operators, casts, templates, and other constructs.
1594*
1595* Returns true if the expression was successfully skipped, false if EOF or
1596* a cancellation token was encountered before finding the end.
1597*/
1598bool CppParser::skipExpression()
1599{
1600 if (match(Tok_Null) || match(Tok_Integer))
1601 return true;
1602
1603 int parenlevel = 0;
1604 while (parenlevel >= 0) {
1605 yyTok = getToken();
1606 if (yyTok == Tok_RightParen)
1607 --parenlevel;
1608 else if (yyTok == Tok_LeftParen)
1609 ++parenlevel;
1610 else if (yyTok == Tok_Cancel || yyTok == Tok_Eof)
1611 return false;
1612 }
1613 return true;
1614}
1615
1616void CppParser::recordMessage(int line, const QString &context, const QString &text,
1617 const QString &comment, const QString &extracomment,
1618 const QString &msgid, const QString &label,
1619 const TranslatorMessage::ExtraData &extra,
1620 bool plural, int startOffset, int endOffset)
1621{
1622 TranslatorMessage msg(transcode(context), text, transcode(comment), QString(), yyFileName, line,
1623 QStringList(), TranslatorMessage::Unfinished, plural);
1624 msg.setExtraComment(transcode(extracomment.simplified()));
1625 msg.setId(msgid);
1626 msg.setExtras(extra);
1627 if (!msgid.isEmpty())
1628 msg.setLabel(label);
1629 msg.setStartOffset(startOffset);
1630 msg.setEndOffset(endOffset);
1631 tor->append(msg);
1632}
1633
1634void CppParser::handleTr(QString &prefix, bool plural)
1635{
1636 if (!m_metaStrings.sourcetext().isEmpty())
1637 yyMsg() << "//% cannot be used with tr() / QT_TR_NOOP(). Ignoring\n";
1638 if (!m_metaStrings.label().isEmpty())
1639 yyMsg() << "labels cannot be used with text-based translation. Ignoring\n";
1640
1641 int line = yyLineNo;
1642 const int startOffset = lookBackFunctionCallStart(prefix.size());
1643 yyTok = getToken();
1644 QString text;
1645 if (matchString(&text)) {
1646 QString comment;
1647 if (yyTok == Tok_RightParen) {
1648 // no comment
1649 } else if (match(Tok_Comma) && matchStringOrNull(&comment)) { //comment
1650 if (yyTok == Tok_RightParen) {
1651 // ok,
1652 } else if (match(Tok_Comma)) {
1653 plural = true;
1654 skipExpression();
1655 }
1656 }
1657 if (!pendingContext.isEmpty() && !prefix.startsWith("::"_L1)) {
1658 NamespaceList unresolved;
1659 if (!fullyQualify(namespaces, pendingContext, true, &functionContext, &unresolved)) {
1660 functionContextUnresolved = stringifyNamespace(0, unresolved);
1661 yyMsg() << qPrintable(
1662 QStringLiteral("Qualifying with unknown namespace/class %1::%2\n")
1663 .arg(stringifyNamespace(functionContext)).arg(unresolved.first().value()));
1664 }
1665 pendingContext.clear();
1666 }
1667 if (prefix.isEmpty()) {
1668 if (functionContextUnresolved.isEmpty()) {
1669 int idx = functionContext.size();
1670 if (idx < 2) {
1671 yyMsg() << "tr() cannot be called without context\n";
1672 return;
1673 }
1674 Namespace *fctx;
1675 while (!(fctx = findNamespace(functionContext, idx)->classDef)->hasTrFunctions) {
1676 if (idx == 1) {
1677 context = stringifyNamespace(functionContext);
1678 fctx = findNamespace(functionContext)->classDef;
1679 if (!fctx->complained) {
1680 yyMsg() << qPrintable(
1681 QStringLiteral("Class '%1' lacks Q_OBJECT macro\n").arg(context));
1682 fctx->complained = true;
1683 }
1684 goto gotctx;
1685 }
1686 --idx;
1687 }
1688 if (fctx->trQualification.isEmpty()) {
1689 context.clear();
1690 for (int i = 1;;) {
1691 context += functionContext.at(i).value();
1692 if (++i == idx)
1693 break;
1694 context += "::"_L1;
1695 }
1696 fctx->trQualification = context;
1697 } else {
1698 context = fctx->trQualification;
1699 }
1700 } else {
1701 context = joinNamespaces(stringifyNamespace(functionContext), functionContextUnresolved);
1702 }
1703 } else {
1704 prefix.chop(2);
1705 NamespaceList nsl;
1706 NamespaceList unresolved;
1707 if (fullyQualify(functionContext, prefix, false, &nsl, &unresolved)) {
1708 Namespace *fctx = findNamespace(nsl)->classDef;
1709 if (fctx->trQualification.isEmpty()) {
1710 context = stringifyNamespace(nsl);
1711 fctx->trQualification = context;
1712 } else {
1713 context = fctx->trQualification;
1714 }
1715 if (!fctx->hasTrFunctions && !fctx->complained) {
1716 yyMsg() << qPrintable(QStringLiteral("Class '%1' lacks Q_OBJECT macro\n")
1717 .arg(context));
1718 fctx->complained = true;
1719 }
1720 } else {
1721 context = joinNamespaces(stringifyNamespace(nsl), stringifyNamespace(0, unresolved));
1722 }
1723 prefix.clear();
1724 }
1725
1726 gotctx:
1727 const int endOffset = yyInPtr - (const ushort *)yyInStr.unicode() - 1;
1728 recordMessage(line, context, text, comment, m_metaStrings.extracomment(),
1729 m_metaStrings.msgid(), QString(), m_metaStrings.extra(),
1730 plural, startOffset, endOffset);
1731 }
1732 m_metaStrings.clear();
1733}
1734
1735void CppParser::handleTranslate(int prefixSize, bool plural)
1736{
1737 if (!m_metaStrings.sourcetext().isEmpty())
1738 yyMsg() << "//% cannot be used with translate() / QT_TRANSLATE_NOOP(). Ignoring\n";
1739 if (!m_metaStrings.label().isEmpty())
1740 yyMsg() << "labels cannot be used with text-based translation. Ignoring\n";
1741 const int startOffset = lookBackFunctionCallStart(prefixSize);
1742 int line = yyLineNo;
1743 yyTok = getToken();
1744 QString text;
1745 if (matchString(&context)
1746 && match(Tok_Comma)
1747 && matchString(&text) && !text.isEmpty())
1748 {
1749 QString comment;
1750 if (yyTok != Tok_RightParen) {
1751 // look for comment
1752 if (match(Tok_Comma) && matchStringOrNull(&comment)) {
1753 if (yyTok != Tok_RightParen) {
1754 // look for encoding
1755 if (match(Tok_Comma)) {
1756 if (matchEncoding()) {
1757 if (yyTok != Tok_RightParen) {
1758 // look for the plural quantifier,
1759 // this can be a number, an identifier or
1760 // a function call,
1761 // so for simplicity we mark it as plural if
1762 // we know we have a comma instead of an
1763 // right parentheses.
1764 plural |= match(Tok_Comma);
1765 }
1766 } else {
1767 // This can be a QTranslator::translate("context",
1768 // "source", "comment", n) plural translation
1769 if (skipExpression() && yyTok == Tok_RightParen) {
1770 plural = true;
1771 } else {
1772 return;
1773 }
1774 }
1775 } else {
1776 return;
1777 }
1778 }
1779 } else {
1780 return;
1781 }
1782 }
1783 const int endOffset = yyInPtr - (const ushort *)yyInStr.unicode() - 1;
1784
1785 recordMessage(line, context, text, comment, m_metaStrings.extracomment(),
1786 m_metaStrings.msgid(), QString(), m_metaStrings.extra(),
1787 plural, startOffset, endOffset);
1788 }
1789 m_metaStrings.clear();
1790}
1791
1792void CppParser::handleTrId(int prefixSize, bool plural)
1793{
1794 if (!m_metaStrings.msgid().isEmpty())
1795 yyMsg() << "//= cannot be used with qtTrId() / QT_TRID_NOOP(). Ignoring\n";
1796 int line = yyLineNo;
1797 const int startOffset = lookBackFunctionCallStart(prefixSize);
1798 yyTok = getToken();
1799 QString msgid;
1800 if (matchString(&msgid) && !msgid.isEmpty()) {
1801 plural |= match(Tok_Comma);
1802 const int endOffset = yyInPtr - (const ushort *)yyInStr.unicode() - 1;
1803
1804 recordMessage(line, QString(), transcode(m_metaStrings.sourcetext()), QString(),
1805 m_metaStrings.extracomment(), msgid, m_metaStrings.label(),
1806 m_metaStrings.extra(), plural, startOffset, endOffset);
1807 }
1808 m_metaStrings.clear();
1809}
1810
1811void CppParser::handleDeclareTrFunctions()
1812{
1813 QString name;
1814 forever {
1815 yyTok = getToken();
1816 if (yyTok != Tok_Ident)
1817 return;
1818 name += yyWord;
1819 name.detach();
1820 yyTok = getToken();
1821 if (yyTok == Tok_RightParen)
1822 break;
1823 if (yyTok != Tok_ColonColon)
1824 return;
1825 name += "::"_L1;
1826 }
1827 Namespace *ns = modifyNamespace(&namespaces);
1828 ns->hasTrFunctions = true;
1829 ns->trQualification = name;
1830 ns->trQualification.detach();
1831}
1832
1833void CppParser::parse(ConversionData &cd, const QStringList &includeStack,
1834 QSet<QString> &inclusions)
1835{
1836 namespaces << HashString();
1837 functionContext = namespaces;
1838 functionContextUnresolved.clear();
1839
1840 parseInternal(cd, includeStack, inclusions);
1841}
1842
1843int CppParser::lookBackFunctionCallStart(int prefixSize)
1844{
1845 const int funcNameOffset = yyInPtr - (const ushort *)yyInStr.unicode();
1846 QStringView prefix = QStringView(yyInStr).sliced(0, funcNameOffset);
1847 do
1848 prefix.chop(1);
1849 while (prefix.back() != '('_L1);
1850 prefix.chop(prefixSize + yyWord.size() + 1);
1851
1852 auto chopMatch = [](auto& str, const auto& match) {
1853 if (str.endsWith(match)) {
1854 str.chop(match.size());
1855 return true;
1856 }
1857 return false;
1858 };
1859 while (chopMatch(prefix, "."_L1) || chopMatch(prefix, "->"_L1)) {
1860 int pos = prefix.size() - 1;
1861 if (prefix.at(pos) == ')'_L1) {
1862 int count = 1;
1863 pos--;
1864 while (count != 0 && pos >= 0) {
1865 if (QChar c = prefix.at(pos--); c == ')'_L1)
1866 count++;
1867 else if (c == '('_L1)
1868 count--;
1869 else if (c == "\""_L1 || c == "'"_L1) {
1870 const QChar quotation = c;
1871 while (pos >= 0) {
1872 c = prefix[pos--];
1873 if (c == '\\'_L1)
1874 pos--;
1875 else if (c == quotation)
1876 break;
1877 }
1878 }
1879 }
1880 }
1881 while (pos >= 0 && (prefix.at(pos).isLetterOrNumber() || prefix.at(pos) == '_'_L1))
1882 pos--;
1883 prefix.slice(0, pos + 1);
1884 }
1885 return prefix.size();
1886}
1887
1888bool CppParser::parseTranslate(QString &prefix)
1889{
1890 bool forcePlural = false;
1891 switch (trFunctionAliasManager.trFunctionByName(yyWord)) {
1892 case TrFunctionAliasManager::Function_Q_DECLARE_TR_FUNCTIONS:
1893 handleDeclareTrFunctions();
1894 break;
1895 case TrFunctionAliasManager::Function_QT_TR_N_NOOP:
1896 forcePlural = true;
1897 Q_FALLTHROUGH();
1898 case TrFunctionAliasManager::Function_tr:
1899 case TrFunctionAliasManager::Function_trUtf8:
1900 case TrFunctionAliasManager::Function_QT_TR_NOOP:
1901 case TrFunctionAliasManager::Function_QT_TR_NOOP_UTF8:
1902 if (tor)
1903 handleTr(prefix, forcePlural);
1904 break;
1905 case TrFunctionAliasManager::Function_QT_TRANSLATE_N_NOOP:
1906 case TrFunctionAliasManager::Function_QT_TRANSLATE_N_NOOP3:
1907 forcePlural = true;
1908 Q_FALLTHROUGH();
1909 case TrFunctionAliasManager::Function_translate:
1910 case TrFunctionAliasManager::Function_findMessage:
1911 case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP:
1912 case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP_UTF8:
1913 case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP3:
1914 case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP3_UTF8:
1915 if (tor)
1916 handleTranslate(prefix.size(), forcePlural);
1917 break;
1918 case TrFunctionAliasManager::Function_QT_TRID_N_NOOP:
1919 forcePlural = true;
1920 Q_FALLTHROUGH();
1921 case TrFunctionAliasManager::Function_qtTrId:
1922 case TrFunctionAliasManager::Function_QT_TRID_NOOP:
1923 if (tor)
1924 handleTrId(prefix.size(), forcePlural);
1925 break;
1926 default:
1927 return false;
1928 }
1929 return true;
1930}
1931
1932void CppParser::parseInternal(ConversionData &cd, const QStringList &includeStack,
1933 QSet<QString> &inclusions)
1934{
1935 static constexpr auto strColons("::"_L1);
1936
1937 QString prefix;
1938 bool yyTokColonSeen = false; // Start of c'tor's initializer list
1939 bool yyTokIdentSeen = false; // Start of initializer (member or base class)
1940 bool maybeInTrailingReturnType = false;
1941
1942 const QList<TokenType> allowedTokensInFnTemplate{
1943 Tok_Ident, Tok_decltype, Tok_ColonColon, Tok_Comma,
1944 Tok_Integer, Tok_LeftAngleBracket, Tok_RightAngleBracket
1945 };
1946 metaExpected = true;
1947
1948 prospectiveContext.clear();
1949 pendingContext.clear();
1950
1951 yyWord.reserve(yyInStr.size()); // Rather insane. That's because we do no length checking.
1952 yyWordInitialCapacity = yyWord.capacity();
1953 yyInPtr = (const ushort *)yyInStr.unicode();
1954 yyCh = getChar();
1955 yyTok = getToken();
1956 while (yyTok != Tok_Eof) {
1957 // these are array indexing operations. we ignore them entirely
1958 // so they don't confuse our scoping of static initializers.
1959 // we enter the loop by either reading a left bracket or by an
1960 // #else popping the state.
1961 if (yyBracketDepth && yyBraceDepth == namespaceDepths.size()) {
1962 yyTok = getToken();
1963 continue;
1964 }
1965 //qDebug() << "TOKEN: " << yyTok;
1966 switch (yyTok) {
1967 case Tok_QuotedInclude: {
1968 QString text = QDir(QFileInfo(yyFileName).absolutePath()).absoluteFilePath(yyWord);
1969 text.detach();
1970 if (QFileInfo(text).isFile()) {
1971 processInclude(text, cd, includeStack, inclusions);
1972 yyTok = getToken();
1973 break;
1974 }
1975 }
1976 Q_FALLTHROUGH();
1977 case Tok_AngledInclude: {
1978 const QStringList cSources = cd.m_allCSources.values(yyWord);
1979 if (!cSources.isEmpty()) {
1980 for (const QString &cSource : cSources)
1981 processInclude(cSource, cd, includeStack, inclusions);
1982 goto incOk;
1983 }
1984 for (const QString &incPath : std::as_const(cd.m_includePath)) {
1985 QString text = QDir(incPath).absoluteFilePath(yyWord);
1986 text.detach();
1987 if (QFileInfo(text).isFile()) {
1988 processInclude(text, cd, includeStack, inclusions);
1989 goto incOk;
1990 }
1991 }
1992 incOk:
1993 yyTok = getToken();
1994 break;
1995 }
1996 case Tok_friend:
1997 yyTok = getToken();
1998 // These are forward declarations, so ignore them.
1999 if (yyTok == Tok_class)
2000 yyTok = getToken();
2001 break;
2002 case Tok_class:
2003 /*
2004 Partial support for inlined functions.
2005 */
2006
2007 case_class:
2008 yyTok = getToken();
2009 if (yyTok == Tok_Equals) { // we're in a template entity
2010 yyTok = getToken();
2011 break;
2012 } else if (yyBraceDepth == namespaceDepths.size() && yyParenDepth == 0) {
2013 NamespaceList quali;
2014 HashString fct;
2015
2016 // Find class name including qualification
2017 forever {
2018 QString text = yyWord;
2019 text.detach();
2020 fct.setValue(text);
2021 yyTok = getToken();
2022
2023 if (yyTok == Tok_ColonColon) {
2024 quali << fct;
2025 yyTok = getToken();
2026 } else if (yyTok == Tok_Ident) {
2027 if (yyWord == strfinal) {
2028 // C++11: final may appear immediately after the name of the class
2029 yyTok = getToken();
2030 break;
2031 }
2032
2033 // Handle impure definitions such as 'class Q_EXPORT QMessageBox', in
2034 // which case 'QMessageBox' is the class name, not 'Q_EXPORT', by
2035 // abandoning any qualification collected so far.
2036 quali.clear();
2037 } else if (yyTok == Tok_Attribute) {
2038 yyTok = getToken();
2039 } else {
2040 break;
2041 }
2042 }
2043
2044 if (yyTok == Tok_Colon || yyTok == Tok_LeftAngleBracket) {
2045 // Skip any token until '{' or ';' since we might do things wrong if we find
2046 // a '::' or ':' token here.
2047 do {
2048 yyTok = getToken();
2049 tokenInTemplate:
2050 if (yyTok == Tok_Eof)
2051 goto goteof;
2052 if (yyTok == Tok_Cancel)
2053 goto case_default;
2054 if (yyTok == Tok_class)
2055 goto case_class;
2056 if (yyTok == Tok_Ident) {
2057 yyTok = getToken();
2058 if (yyTok == Tok_LeftParen)
2059 parseTranslate(prefix);
2060 else
2061 goto tokenInTemplate;
2062 }
2063 } while (yyTok != Tok_LeftBrace && yyTok != Tok_Semicolon);
2064 if (yyTok == Tok_Semicolon)
2065 break;
2066 } else {
2067 if (yyTok != Tok_LeftBrace) {
2068 // Obviously a forward declaration. We skip those, as they
2069 // don't create actually usable namespaces.
2070 break;
2071 }
2072 }
2073
2074 if (!quali.isEmpty()) {
2075 // Forward-declared class definitions can be namespaced.
2076 NamespaceList nsl;
2077 if (!fullyQualify(namespaces, quali, true, &nsl, 0)) {
2078 yyMsg() << "Ignoring definition of undeclared qualified class\n";
2079 break;
2080 }
2081 namespaceDepths.push(namespaces.size());
2082 namespaces = nsl;
2083 } else {
2084 namespaceDepths.push(namespaces.size());
2085 }
2086 enterNamespace(&namespaces, fct);
2087
2088 // Mark this namespace as a class
2089 Namespace *ns = modifyNamespace(&namespaces);
2090 ns->isClass = true;
2091
2092 functionContext = namespaces;
2093 functionContextUnresolved.clear(); // Pointless
2094 prospectiveContext.clear();
2095 pendingContext.clear();
2096
2097 metaExpected = true;
2098 yyTok = getToken();
2099 }
2100 break;
2101 case Tok_namespace:
2102 yyTok = getToken();
2103 // Skip C++11 attributes on namespace
2104 while (yyTok == Tok_Attribute)
2105 yyTok = getToken();
2106 if (yyTok == Tok_Ident) {
2107 QString text = yyWord;
2108 text.detach();
2109 HashString ns = HashString(text);
2110 NamespaceList nestedNamespaces;
2111 forever {
2112 yyTok = getToken();
2113 if (yyTok != Tok_ColonColon)
2114 break;
2115 yyTok = getToken();
2116 if (yyTok != Tok_Ident)
2117 break; // whoops
2118 nestedNamespaces.append(ns);
2119 text = yyWord;
2120 text.detach();
2121 ns = HashString(text);
2122 }
2123 if (yyTok == Tok_LeftBrace) {
2124 namespaceDepths.push(namespaces.size());
2125 for (const auto &nns : nestedNamespaces)
2126 enterNamespace(&namespaces, nns);
2127 enterNamespace(&namespaces, ns);
2128
2129 functionContext = namespaces;
2130 functionContextUnresolved.clear();
2131 prospectiveContext.clear();
2132 pendingContext.clear();
2133 metaExpected = true;
2134 yyTok = getToken();
2135 } else if (yyTok == Tok_Equals) {
2136 // e.g. namespace Is = OuterSpace::InnerSpace;
2137 // Note: 'Is' being qualified is invalid per C++17.
2138 NamespaceList fullName;
2139 yyTok = getToken();
2140 if (yyTok == Tok_ColonColon)
2141 fullName.append(HashString(QString()));
2142 while (yyTok == Tok_ColonColon || yyTok == Tok_Ident) {
2143 if (yyTok == Tok_Ident) {
2144 text = yyWord;
2145 text.detach();
2146 fullName.append(HashString(text));
2147 }
2148 yyTok = getToken();
2149 }
2150 if (fullName.isEmpty())
2151 break;
2152 fullName.append(HashString(QString())); // Mark as unresolved
2153 modifyNamespace(&namespaces)->aliases[ns] = fullName;
2154 }
2155 } else if (yyTok == Tok_LeftBrace) {
2156 // Anonymous namespace
2157 namespaceDepths.push(namespaces.size());
2158 metaExpected = true;
2159 yyTok = getToken();
2160 }
2161 break;
2162 case Tok_using:
2163 yyTok = getToken();
2164 // XXX this should affect only the current scope, not the entire current namespace
2165 if (yyTok == Tok_namespace) {
2166 NamespaceList fullName;
2167 yyTok = getToken();
2168 if (yyTok == Tok_ColonColon)
2169 fullName.append(HashString(QString()));
2170 while (yyTok == Tok_ColonColon || yyTok == Tok_Ident) {
2171 if (yyTok == Tok_Ident) {
2172 QString text = yyWord;
2173 text.detach();
2174 fullName.append(HashString(text));
2175 }
2176 yyTok = getToken();
2177 }
2178 NamespaceList nsl;
2179 if (fullyQualify(namespaces, fullName, false, &nsl, 0))
2180 modifyNamespace(&namespaces)->usings << HashStringList(nsl);
2181 } else {
2182 NamespaceList fullName;
2183 if (yyTok == Tok_ColonColon)
2184 fullName.append(HashString(QString()));
2185 while (yyTok == Tok_ColonColon || yyTok == Tok_Ident) {
2186 if (yyTok == Tok_Ident) {
2187 QString text = yyWord;
2188 text.detach();
2189 fullName.append(HashString(text));
2190 }
2191 yyTok = getToken();
2192 }
2193 if (fullName.isEmpty())
2194 break;
2195 // using-declarations cannot rename classes, so the last element of
2196 // fullName is already the resolved name we actually want.
2197 // As we do no resolution here, we'll collect useless usings of data
2198 // members and methods as well. This is no big deal.
2199 fullName.append(HashString(QString())); // Mark as unresolved
2200 const HashString &ns = *(fullName.constEnd() - 2);
2201 modifyNamespace(&namespaces)->aliases[ns] = fullName;
2202 }
2203 break;
2204 case Tok_Q_OBJECT:
2205 modifyNamespace(&namespaces)->hasTrFunctions = true;
2206 yyTok = getToken();
2207 break;
2208 case Tok_Ident: {
2209 if (yyTokColonSeen &&
2210 yyBraceDepth == namespaceDepths.size() && yyParenDepth == 0) {
2211 // member or base class identifier
2212 yyTokIdentSeen = true;
2213 }
2214 yyTok = getToken();
2215 QString className;
2216 bool methodSpecialization = false;
2217 if (yyTok == Tok_LeftAngleBracket) { // maybe a method specialization
2218 int count = 1;
2219 className = yyWord;
2220 className.detach();
2221 yyTok = getToken();
2222 while (count && allowedTokensInFnTemplate.contains(yyTok)) {
2223 if (yyTok == Tok_LeftAngleBracket)
2224 count++;
2225 else if (yyTok == Tok_RightAngleBracket)
2226 count--;
2227 yyTok = getToken();
2228 }
2229 methodSpecialization = count == 0;
2230 }
2231 if (yyTok == Tok_LeftParen) {
2232 if (parseTranslate(prefix)) {
2233 yyTok = getToken();
2234 break;
2235 } else {
2236 prefix.clear();
2237 }
2238 }
2239 if (yyTok == Tok_ColonColon && !maybeInTrailingReturnType && !yyTrailingSpace) {
2240 prefix += methodSpecialization ? className : yyWord;
2241 prefix.detach();
2242 } else {
2243 // Breaking the :: chain in case of a return/member type
2244 if (!prefix.isEmpty() && yyTok != Tok_LeftParen && yyTok != Tok_RightParen
2245 && yyTok != Tok_LeftBracket && yyTok != Tok_Equals) {
2246 // We're breaking the chain NOT at a function call or member initialization
2247 // This means prospectiveContext was from a return/member type
2248 prospectiveContext.clear();
2249 }
2250 prefix.clear();
2251 }
2252 metaExpected = false;
2253 break;
2254 }
2255 case Tok_Arrow:
2256 if (yyParenDepth == 0 && yyBraceDepth == namespaceDepths.size())
2257 maybeInTrailingReturnType = true;
2258 yyTok = getToken();
2259 if (yyTok == Tok_Ident) {
2260 yyTok = getToken();
2261 if (yyTok == Tok_LeftParen) {
2262 switch (trFunctionAliasManager.trFunctionByName(yyWord)) {
2263 case TrFunctionAliasManager::Function_tr:
2264 case TrFunctionAliasManager::Function_trUtf8:
2265 yyMsg() << "Cannot invoke tr() like this\n";
2266 break;
2267 }
2268 }
2269 }
2270 break;
2271 case Tok_ColonColon:
2272 if (yyTokIdentSeen || maybeInTrailingReturnType) {
2273 // member or base class identifier
2274 yyTok = getToken();
2275 break;
2276 }
2277 if (yyBraceDepth == namespaceDepths.size() && yyParenDepth == 0 && !yyTokColonSeen)
2278 prospectiveContext = prefix;
2279 if (!prefix.isEmpty())
2280 prefix += strColons;
2281 yyTok = getToken();
2282 break;
2283 case Tok_RightBrace:
2284 if (!yyTokColonSeen) {
2285 if (yyBraceDepth + 1 == namespaceDepths.size()) {
2286 // class or namespace
2287 truncateNamespaces(&namespaces, namespaceDepths.pop());
2288 }
2289 if (yyBraceDepth == namespaceDepths.size()) {
2290 // function, class or namespace
2291 if (!yyBraceDepth && !directInclude)
2292 truncateNamespaces(&functionContext, 1);
2293 else
2294 functionContext = namespaces;
2295 functionContextUnresolved.clear();
2296 pendingContext.clear();
2297 }
2298 }
2299 Q_FALLTHROUGH();
2300 case Tok_Semicolon:
2301 maybeInTrailingReturnType = false;
2302 prospectiveContext.clear();
2303 prefix.clear();
2304 if (m_metaStrings.hasData()) {
2305 yyMsg() << "Discarding unconsumed meta data\n";
2306 m_metaStrings.clear();
2307 }
2308 metaExpected = true;
2309 yyTok = getToken();
2310 break;
2311 case Tok_Access:
2312 // Eat access specifiers, so their colons are not mistaken for c'tor initializer list starts
2313 do {
2314 yyTok = getToken();
2315 } while (yyTok == Tok_Access); // Multiple specifiers are possible, e.g. "public slots"
2316 metaExpected = true;
2317 if (yyTok == Tok_Colon)
2318 goto case_default;
2319 break;
2320 case Tok_Colon:
2321 case Tok_Equals:
2322 if (yyBraceDepth == namespaceDepths.size() && yyParenDepth == 0) {
2323 if (!prospectiveContext.isEmpty()) {
2324 pendingContext = prospectiveContext;
2325 prospectiveContext.clear();
2326 }
2327 //ignore colons for bitfields (are usually followed by a semicolon)
2328 if (yyTok == Tok_Colon) {
2329 if (lookAheadToSemicolonOrLeftBrace() != Tok_Semicolon)
2330 yyTokColonSeen = true;
2331 }
2332 }
2333 metaExpected = true;
2334 yyTok = getToken();
2335 break;
2336 case Tok_LeftBrace:
2337 if (yyBraceDepth == namespaceDepths.size() + 1 && yyParenDepth == 0) {
2338 if (!prospectiveContext.isEmpty()) {
2339 pendingContext = prospectiveContext;
2340 prospectiveContext.clear();
2341 }
2342 if (!yyTokIdentSeen) {
2343 // Function body
2344 yyTokColonSeen = false;
2345 }
2346 }
2347 maybeInTrailingReturnType = false;
2348 yyTokIdentSeen = false;
2349 metaExpected = true;
2350 yyTok = getToken();
2351 break;
2352 case Tok_LeftParen:
2353 if (!yyTokColonSeen && yyBraceDepth == namespaceDepths.size() && yyParenDepth == 1
2354 && !prospectiveContext.isEmpty()) {
2355 pendingContext = prospectiveContext;
2356 prospectiveContext.clear();
2357 }
2358 yyTokIdentSeen = false;
2359 metaExpected = true;
2360 yyTok = getToken();
2361 break;
2362 case Tok_Comma:
2363 case Tok_QuestionMark:
2364 metaExpected = true;
2365 yyTok = getToken();
2366 break;
2367 case Tok_RightParen:
2368 if (yyParenDepth == 0 && !yyTokColonSeen && !pendingContext.isEmpty()
2369 && yyBraceDepth == namespaceDepths.size()) {
2370 // Demote the pendingContext to prospectiveContext.
2371 prospectiveContext = pendingContext;
2372 pendingContext.clear();
2373 }
2374 metaExpected = true;
2375 yyTok = getToken();
2376 break;
2377 case Tok_decltype:
2378 {
2379 // Save the parentheses depth outside the 'decltype' specifier.
2380 auto initialParenDepth = yyParenDepth;
2381
2382 // Eat the opening parenthesis that follows 'decltype'.
2383 yyTok = getToken();
2384
2385 // Skip over everything within the parentheses that follow 'decltype'.
2386 while (yyParenDepth != initialParenDepth && yyTok != Tok_Eof)
2387 yyTok = getToken();
2388 }
2389 break;
2390 case Tok_enum:
2391 yyTok = getToken();
2392 // If it is an enum class then ignore
2393 if (yyTok == Tok_class)
2394 yyTok = getToken();
2395
2396 // Allow the parser to flexibly detect and ignore
2397 // colons in front of the typed enums.
2398 yyTok = getToken();
2399 if (yyTok == Tok_Colon) // ignore any colons in front of a typed enum
2400 yyTok = getToken();
2401 break;
2402 default:
2403 if (!yyParenDepth && !maybeInTrailingReturnType)
2404 prospectiveContext.clear();
2405 Q_FALLTHROUGH();
2406 case Tok_RightBracket: // ignoring indexing; for static initializers
2407 case_default:
2408 yyTok = getToken();
2409 break;
2410 }
2411 }
2412
2413 goteof:
2414 if (yyBraceDepth != 0)
2415 yyMsg(yyBraceLineNo)
2416 << "Unbalanced opening brace in C++ code (or abuse of the C++ preprocessor)\n";
2417 else if (yyParenDepth != 0)
2418 yyMsg(yyParenLineNo)
2419 << "Unbalanced opening parenthesis in C++ code"
2420 " (or abuse of the C++ preprocessor)\n";
2421 else if (yyBracketDepth != 0)
2422 yyMsg(yyBracketLineNo)
2423 << "Unbalanced opening bracket in C++ code"
2424 " (or abuse of the C++ preprocessor)\n";
2425}
2426
2427void CppParser::processComment()
2428{
2429 if (!tor || !metaExpected)
2430 return;
2431
2432 if (!m_metaStrings.parse(yyWord)) {
2433 yyMsg() << m_metaStrings.popError().toStdString();
2434 return;
2435 }
2436
2437 if (!m_metaStrings.label().isEmpty()) {
2438 // If pendingContext is set, it may contain aliases that need resolution
2439 NamespaceList contextForLabels = functionContext;
2440 if (!pendingContext.isEmpty())
2441 fullyQualify(namespaces, pendingContext, true, &contextForLabels, nullptr);
2442
2443 QString context = stringifyNamespace(contextForLabels);
2444 QString className;
2445
2446 // Extract class name if the current context is a class
2447 if (const Namespace *ns = findNamespace(contextForLabels); ns && ns->isClass) {
2448 int idx = context.lastIndexOf("::"_L1);
2449 className = idx >= 0 ? context.mid(idx + 2) : context;
2450 }
2451
2452 if (!m_metaStrings.resolveLabel(yyFileName, context, className))
2453 yyMsg() << m_metaStrings.popError().toStdString();
2454 }
2455
2456 if (m_metaStrings.magicComment()) {
2457 auto [context, comment] = *m_metaStrings.magicComment();
2458 TranslatorMessage msg(transcode(context), QString(), transcode(comment), QString(),
2459 yyFileName, yyLineNo, QStringList(), TranslatorMessage::Finished,
2460 false);
2461 msg.setExtraComment(transcode(m_metaStrings.extracomment().simplified()));
2462 tor->append(msg);
2463 tor->setExtras(m_metaStrings.extra());
2464 m_metaStrings.clear();
2465 }
2466}
2467
2469{
2470 if (tor) {
2471 if (tor->messageCount()) {
2472 CppFiles::setTranslator(yyFileName, tor);
2473 } else {
2474 delete tor;
2475 tor = 0;
2476 }
2477 }
2478 if (isHeader) {
2479 const ParseResults *pr;
2480 if (!tor && results->includes.size() == 1
2481 && results->rootNamespace.children.isEmpty()
2482 && results->rootNamespace.aliases.isEmpty()
2483 && results->rootNamespace.usings.isEmpty()) {
2484 // This is a forwarding header. Slash it.
2485 pr = *results->includes.cbegin();
2486 delete results;
2487 } else {
2488 results->fileId = nextFileId++;
2489 pr = results;
2490 }
2491 CppFiles::setResults(ResultsCacheKey(yyFileName, *this), pr);
2492 return pr;
2493 } else {
2494 delete results;
2495 return 0;
2496 }
2497}
2498
2499void loadCPP(Translator &translator, const QStringList &filenames, ConversionData &cd)
2500{
2501 QStringConverter::Encoding e = cd.m_sourceIsUtf16 ? QStringConverter::Utf16 : QStringConverter::Utf8;
2502
2503 for (const QString &filename : filenames) {
2504 if (!CppFiles::getResults(ResultsCacheKey(filename)).isEmpty() || CppFiles::isBlacklisted(filename))
2505 continue;
2506
2507 QFile file(filename);
2508 if (!file.open(QIODevice::ReadOnly)) {
2509 cd.appendError(QStringLiteral("Cannot open %1: %2").arg(filename,
2510 file.errorString()));
2511 continue;
2512 }
2513
2514 CppParser parser;
2515 QTextStream ts(&file);
2516 ts.setEncoding(e);
2517 ts.setAutoDetectUnicode(true);
2518 parser.setInput(ts, filename);
2519 Translator *tor = new Translator;
2520 parser.setTranslator(tor);
2521 QSet<QString> inclusions;
2522 parser.parse(cd, QStringList(), inclusions);
2523 parser.recordResults(isHeader(filename));
2524 }
2525
2526 for (const QString &filename : filenames) {
2527 if (!CppFiles::isBlacklisted(filename)) {
2528 if (const Translator *tor = CppFiles::getTranslator(filename)) {
2529 for (const TranslatorMessage &msg : tor->messages())
2530 translator.extend(msg, cd);
2531 }
2532 }
2533 }
2534}
2535
2536QT_END_NAMESPACE
static void setResults(const ResultsCacheKey &key, const ParseResults *results)
Definition cpp.cpp:1383
bool parseTranslate(QString &prefix)
Definition cpp.cpp:1888
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:1932
CppParser(ParseResults *results=0)
Definition cpp.cpp:245
void parse(ConversionData &cd, const QStringList &includeStack, QSet< QString > &inclusions)
Definition cpp.cpp:1833
const ParseResults * recordResults(bool isHeader)
Definition cpp.cpp:2468
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:1558
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:1345
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:1561
static const QString strpublic
Definition cpp.cpp:401
static const QString strQ_SLOTS
Definition cpp.cpp:404
static const QString strUnicodeUTF8
Definition cpp.cpp:1559
static const QString strprotected
Definition cpp.cpp:400
static const QString strLatin1
Definition cpp.cpp:1562
static int nextFileId
Definition cpp.cpp:54
static bool isHeader(const QString &name)
Definition cpp.cpp:1439
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:1557
size_t qHash(const CppParserState &s, size_t seed)
Definition cpp.cpp:1335
static const QString strDefaultCodec
Definition cpp.cpp:1560
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:2499
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