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