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