1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Copyright (C) 2016 Intel Corporation.
5 ** Contact: https://www.qt.io/licensing/
6 **
7 ** This file is part of the qmake application of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:GPL-EXCEPT$
10 ** Commercial License Usage
11 ** Licensees holding valid commercial Qt licenses may use this file in
12 ** accordance with the commercial license agreement provided with the
13 ** Software or, alternatively, in accordance with the terms contained in
14 ** a written agreement between you and The Qt Company. For licensing terms
15 ** and conditions see https://www.qt.io/terms-conditions. For further
16 ** information use the contact form at https://www.qt.io/contact-us.
17 **
18 ** GNU General Public License Usage
19 ** Alternatively, this file may be used under the terms of the GNU
20 ** General Public License version 3 as published by the Free Software
21 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
22 ** included in the packaging of this file. Please review the following
23 ** information to ensure the GNU General Public License requirements will
24 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
25 **
26 ** $QT_END_LICENSE$
27 **
28 ****************************************************************************/
29 
30 #include "makefiledeps.h"
31 #include "option.h"
32 #include <qdir.h>
33 #include <qdatetime.h>
34 #include <qfileinfo.h>
35 #include <qbuffer.h>
36 #include <qplatformdefs.h>
37 #if defined(Q_OS_UNIX)
38 # include <unistd.h>
39 #else
40 # include <io.h>
41 #endif
42 #include <qdebug.h>
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <time.h>
46 #include <fcntl.h>
47 #include <sys/types.h>
48 #include <sys/stat.h>
49 #include <limits.h>
50 #if defined(_MSC_VER) && _MSC_VER >= 1400
51 #include <share.h>
52 #endif
53 
54 QT_BEGIN_NAMESPACE
55 
56 // FIXME: a line ending in CRLF gets counted as two lines.
57 #if 1
58 #define qmake_endOfLine(c) (c == '\r' || c == '\n')
59 #else
60 inline bool qmake_endOfLine(const char &c) { return (c == '\r' || c == '\n'); }
61 #endif
62 
name.isNullnull63 QMakeLocalFileName::QMakeLocalFileName(const QString &name) : is_null(name.isNull())
64 {
65     if(!name.isEmpty()) {
66         if(name.at(0) == QLatin1Char('"') && name.at(name.length()-2) == QLatin1Char('"'))
67             real_name = name.mid(1, name.length()-2);
68         else
69             real_name = name;
70     }
71 }
72 const QString
QMakeLocalFileName::localnull73 &QMakeLocalFileName::local() const
74 {
75     if(!is_null && local_name.isNull())
76         local_name = Option::normalizePath(real_name);
77     return local_name;
78 }
79 
80 struct SourceDependChildren;
81 struct SourceFile {
SourceFilenull82     SourceFile() : deps(nullptr), type(QMakeSourceFileInfo::TYPE_UNKNOWN),
83                    mocable(0), traversed(0), exists(1),
84                    moc_checked(0), dep_checked(0), included_count(0) { }
~SourceFilenull85     ~SourceFile();
86     QMakeLocalFileName file;
87     SourceDependChildren *deps;
88     QMakeSourceFileInfo::SourceFileType type;
89     uint mocable : 1, traversed : 1, exists : 1;
90     uint moc_checked : 1,  dep_checked : 1;
91     uchar included_count;
92 };
93 struct SourceDependChildren {
94     SourceFile **children;
95     int num_nodes, used_nodes;
SourceDependChildrennull96     SourceDependChildren() : children(nullptr), num_nodes(0), used_nodes(0) { }
~SourceDependChildrennull97     ~SourceDependChildren() { if (children) free(children); children = nullptr; }
98     void addChild(SourceFile *s) {
99         if(num_nodes <= used_nodes) {
100             num_nodes += 200;
101             children = (SourceFile**)realloc(children, sizeof(SourceFile*)*(num_nodes));
102         }
103         children[used_nodes++] = s;
104     }
105 };
SourceFile::~SourceFilenull106 SourceFile::~SourceFile() { delete deps; }
107 class SourceFiles {
108     int hash(const char *);
109 public:
SourceFilesnull110     SourceFiles();
~SourceFilesnull111     ~SourceFiles();
112 
113     SourceFile *lookupFile(const char *);
.constDatanull114     inline SourceFile *lookupFile(const QString &f) { return lookupFile(f.toLatin1().constData()); }
f.localnull115     inline SourceFile *lookupFile(const QMakeLocalFileName &f) { return lookupFile(f.local().toLatin1().constData()); }
116     void addFile(SourceFile *, const char *k = nullptr, bool own = true);
117 
118     struct SourceFileNode {
SourceFileNodenull119         SourceFileNode() : key(nullptr), next(nullptr), file(nullptr), own_file(1) { }
~SourceFileNodenull120         ~SourceFileNode() {
121             delete [] key;
122             if(own_file)
123                 delete file;
124         }
125         char *key;
126         SourceFileNode *next;
127         SourceFile *file;
128         uint own_file : 1;
129     } **nodes;
130     int num_nodes;
131 };
SourceFiles::SourceFilesnull132 SourceFiles::SourceFiles()
133 {
134     nodes = (SourceFileNode**)malloc(sizeof(SourceFileNode*)*(num_nodes=3037));
135     for(int n = 0; n < num_nodes; n++)
136         nodes[n] = nullptr;
137 }
138 
SourceFiles::~SourceFilesnull139 SourceFiles::~SourceFiles()
140 {
141     for(int n = 0; n < num_nodes; n++) {
142         for(SourceFileNode *next = nodes[n]; next;) {
143             SourceFileNode *next_next = next->next;
144             delete next;
145             next = next_next;
146         }
147     }
148     free(nodes);
149 }
150 
151 int SourceFiles::hash(const char *file)
152 {
153     uint h = 0, g;
154     while (*file) {
155         h = (h << 4) + *file;
156         if ((g = (h & 0xf0000000)) != 0)
157             h ^= g >> 23;
158         h &= ~g;
159         file++;
160     }
161     return h;
162 }
163 
164 SourceFile *SourceFiles::lookupFile(const char *file)
165 {
166     int h = hash(file) % num_nodes;
167     for(SourceFileNode *p = nodes[h]; p; p = p->next) {
168         if(!strcmp(p->key, file))
169             return p->file;
170     }
171     return nullptr;
172 }
173 
174 void SourceFiles::addFile(SourceFile *p, const char *k, bool own_file)
175 {
176     const QByteArray ba = p->file.local().toLatin1();
177     if(!k)
178         k = ba.constData();
179     int h = hash(k) % num_nodes;
180     SourceFileNode *pn = new SourceFileNode;
181     pn->own_file = own_file;
182     pn->key = qstrdup(k);
183     pn->file = p;
184     pn->next = nodes[h];
185     nodes[h] = pn;
186 }
187 
188 void QMakeSourceFileInfo::dependTreeWalker(SourceFile *node, SourceDependChildren *place)
189 {
190     if(node->traversed || !node->exists)
191         return;
192     place->addChild(node);
193     node->traversed = true; //set flag
194     if(node->deps) {
195         for(int i = 0; i < node->deps->used_nodes; i++)
196             dependTreeWalker(node->deps->children[i], place);
197     }
198 }
199 
200 void QMakeSourceFileInfo::setDependencyPaths(const QVector<QMakeLocalFileName> &l)
201 {
202     // Ensure that depdirs does not contain the same paths several times, to minimize the stats
203     QVector<QMakeLocalFileName> ll;
204     for (int i = 0; i < l.count(); ++i) {
205         if (!ll.contains(l.at(i)))
206             ll.append(l.at(i));
207     }
208     depdirs = ll;
209 }
210 
211 QStringList QMakeSourceFileInfo::dependencies(const QString &file)
212 {
213     QStringList ret;
214     if(!files)
215         return ret;
216 
217     if(SourceFile *node = files->lookupFile(QMakeLocalFileName(file))) {
218         if(node->deps) {
219             /* I stick them into a SourceDependChildren here because it is faster to just
220                iterate over the list to stick them in the list, and reset the flag, then it is
221                to loop over the tree (about 50% faster I saw) --Sam */
222             SourceDependChildren place;
223             for(int i = 0; i < node->deps->used_nodes; i++)
224                 dependTreeWalker(node->deps->children[i], &place);
225             if(place.children) {
226                 for(int i = 0; i < place.used_nodes; i++) {
227                     place.children[i]->traversed = false; //reset flag
228                     ret.append(place.children[i]->file.real());
229                 }
230            }
231        }
232     }
233     return ret;
234 }
235 
236 int
237 QMakeSourceFileInfo::included(const QString &file)
238 {
239     if (!files)
240         return 0;
241 
242     if(SourceFile *node = files->lookupFile(QMakeLocalFileName(file)))
243         return node->included_count;
244     return 0;
245 }
246 
247 bool QMakeSourceFileInfo::mocable(const QString &file)
248 {
249     if(SourceFile *node = files->lookupFile(QMakeLocalFileName(file)))
250         return node->mocable;
251     return false;
252 }
253 
254 QMakeSourceFileInfo::QMakeSourceFileInfo(const QString &cf)
255 {
256     //dep_mode
257     dep_mode = Recursive;
258 
259     //quick project lookups
260     includes = files = nullptr;
261     files_changed = false;
262 
263     //buffer
264     spare_buffer = nullptr;
265     spare_buffer_size = 0;
266 }
267 
268 QMakeSourceFileInfo::~QMakeSourceFileInfo()
269 {
270     //buffer
271     if(spare_buffer) {
272         free(spare_buffer);
273         spare_buffer = nullptr;
274         spare_buffer_size = 0;
275     }
276 
277     //quick project lookup
278     delete files;
279     delete includes;
280 }
281 
282 void QMakeSourceFileInfo::addSourceFiles(const ProStringList &l, uchar seek,
283                                          QMakeSourceFileInfo::SourceFileType type)
284 {
285     for(int i=0; i<l.size(); ++i)
286         addSourceFile(l.at(i).toQString(), seek, type);
287 }
288 void QMakeSourceFileInfo::addSourceFile(const QString &f, uchar seek,
289                                         QMakeSourceFileInfo::SourceFileType type)
290 {
291     if(!files)
292         files = new SourceFiles;
293 
294     QMakeLocalFileName fn(f);
295     SourceFile *file = files->lookupFile(fn);
296     if(!file) {
297         file = new SourceFile;
298         file->file = fn;
299         files->addFile(file);
300     } else {
301         if(file->type != type && file->type != TYPE_UNKNOWN && type != TYPE_UNKNOWN)
302             warn_msg(WarnLogic, "%s is marked as %d, then %d!", f.toLatin1().constData(),
303                      file->type, type);
304     }
305     if(type != TYPE_UNKNOWN)
306         file->type = type;
307 
308     if(seek & SEEK_MOCS && !file->moc_checked)
309         findMocs(file);
310     if(seek & SEEK_DEPS && !file->dep_checked)
311         findDeps(file);
312 }
313 
314 bool QMakeSourceFileInfo::containsSourceFile(const QString &f, SourceFileType type)
315 {
316     if(SourceFile *file = files->lookupFile(QMakeLocalFileName(f)))
317         return (file->type == type || file->type == TYPE_UNKNOWN || type == TYPE_UNKNOWN);
318     return false;
319 }
320 
321 bool QMakeSourceFileInfo::isSystemInclude(const QString &name)
322 {
323     if (QDir::isRelativePath(name)) {
324         // if we got a relative path here, it's either an -I flag with a relative path
325         // or an include file we couldn't locate. Either way, conclude it's not
326         // a system include.
327         return false;
328     }
329 
330     for (int i = 0; i < systemIncludes.size(); ++i) {
331         // check if name is located inside the system include dir:
332         QDir systemDir(systemIncludes.at(i));
333         QString relativePath = systemDir.relativeFilePath(name);
334 
335         // the relative path might be absolute if we're crossing drives on Windows
336         if (QDir::isAbsolutePath(relativePath) || relativePath.startsWith("../"))
337             continue;
338         debug_msg(5, "File/dir %s is in system dir %s, skipping",
339                   qPrintable(name), qPrintable(systemIncludes.at(i)));
340         return true;
341     }
342     return false;
343 }
344 
345 char *QMakeSourceFileInfo::getBuffer(int s) {
346     if(!spare_buffer || spare_buffer_size < s)
347         spare_buffer = (char *)realloc(spare_buffer, spare_buffer_size=s);
348     return spare_buffer;
349 }
350 
351 #ifndef S_ISDIR
352 #define S_ISDIR(x) (x & _S_IFDIR)
353 #endif
354 
355 QMakeLocalFileName QMakeSourceFileInfo::fixPathForFile(const QMakeLocalFileName &f, bool)
356 {
357     return f;
358 }
359 
360 QMakeLocalFileName QMakeSourceFileInfo::findFileForDep(const QMakeLocalFileName &/*dep*/,
361                                                        const QMakeLocalFileName &/*file*/)
362 {
363     return QMakeLocalFileName();
364 }
365 
366 QFileInfo QMakeSourceFileInfo::findFileInfo(const QMakeLocalFileName &dep)
367 {
368     return QFileInfo(dep.real());
369 }
370 
371 static int skipEscapedLineEnds(const char *buffer, int buffer_len, int offset, int *lines)
372 {
373     // Join physical lines to make logical lines, as in the C preprocessor
374     while (offset + 1 < buffer_len
375            && buffer[offset] == '\\'
376            && qmake_endOfLine(buffer[offset + 1])) {
377         offset += 2;
378         ++*lines;
379         if (offset < buffer_len
380             && buffer[offset - 1] == '\r'
381             && buffer[offset] == '\n') // CRLF
382             offset++;
383     }
384     return offset;
385 }
386 
387 static bool matchWhileUnsplitting(const char *buffer, int buffer_len, int start,
388                                   const char *needle, int needle_len,
389                                   int *matchlen, int *lines)
390 {
391     int x = start;
392     for (int n = 0; n < needle_len;
393          n++, x = skipEscapedLineEnds(buffer, buffer_len, x + 1, lines)) {
394         if (x >= buffer_len || buffer[x] != needle[n])
395             return false;
396     }
397     // That also skipped any remaining BSNLs immediately after the match.
398 
399     // Tell caller how long the match was:
400     *matchlen = x - start;
401 
402     return true;
403 }
404 
405 /* Advance from an opening quote at buffer[offset] to the matching close quote. */
406 static int scanPastString(char *buffer, int buffer_len, int offset, int *lines)
407 {
408     // http://en.cppreference.com/w/cpp/language/string_literal
409     // It might be a C++11 raw string.
410     bool israw = false;
411     if (buffer[offset] == '"' && offset > 0) {
412         int explore = offset - 1;
413         bool prefix = false; // One of L, U, u or u8 may appear before R
414         bool saw8 = false; // Partial scan of u8
415         while (explore >= 0) {
416             // Cope with backslash-newline interruptions of the prefix:
417             if (explore > 0
418                 && qmake_endOfLine(buffer[explore])
419                 && buffer[explore - 1] == '\\') {
420                 explore -= 2;
421             } else if (explore > 1
422                        && buffer[explore] == '\n'
423                        && buffer[explore - 1] == '\r'
424                        && buffer[explore - 2] == '\\') {
425                 explore -= 3;
426                 // Remaining cases can only decrement explore by one at a time:
427             } else if (saw8 && buffer[explore] == 'u') {
428                 explore--;
429                 saw8 = false;
430                 prefix = true;
431             } else if (saw8 || prefix) {
432                 break;
433             } else if (explore > 1 && buffer[explore] == '8') {
434                 explore--;
435                 saw8 = true;
436             } else if (buffer[explore] == 'L'
437                        || buffer[explore] == 'U'
438                        || buffer[explore] == 'u') {
439                 explore--;
440                 prefix = true;
441             } else if (buffer[explore] == 'R') {
442                 if (israw)
443                     break;
444                 explore--;
445                 israw = true;
446             } else {
447                 break;
448             }
449         }
450         // Check the R (with possible prefix) isn't just part of an identifier:
451         if (israw && explore >= 0
452             && (isalnum(buffer[explore]) || buffer[explore] == '_')) {
453             israw = false;
454         }
455     }
456 
457     if (israw) {
458 #define SKIP_BSNL(pos) skipEscapedLineEnds(buffer, buffer_len, (pos), lines)
459 
460         offset = SKIP_BSNL(offset + 1);
461         const char *const delim = buffer + offset;
462         int clean = offset;
463         while (offset < buffer_len && buffer[offset] != '(') {
464             if (clean < offset)
465                 buffer[clean++] = buffer[offset];
466             else
467                 clean++;
468 
469             offset = SKIP_BSNL(offset + 1);
470         }
471         /*
472           Not checking correctness (trust real compiler to do that):
473           - no controls, spaces, '(', ')', '\\' or (presumably) '"' in delim;
474           - at most 16 bytes in delim
475 
476           Raw strings are surely defined after phase 2, when BSNLs are resolved;
477           so the delimiter's exclusion of '\\' and space (including newlines)
478           applies too late to save us the need to cope with BSNLs in it.
479         */
480 
481         const int delimlen = buffer + clean - delim;
482         int matchlen = delimlen, extralines = 0;
483         while ((offset = SKIP_BSNL(offset + 1)) < buffer_len
484                && (buffer[offset] != ')'
485                    || (delimlen > 0 &&
486                        !matchWhileUnsplitting(buffer, buffer_len,
487                                               offset + 1, delim, delimlen,
488                                               &matchlen, &extralines))
489                    || buffer[offset + 1 + matchlen] != '"')) {
490             // skip, but keep track of lines
491             if (qmake_endOfLine(buffer[offset]))
492                 ++*lines;
493             extralines = 0;
494         }
495         *lines += extralines; // from the match
496         // buffer[offset] is ')'
497         offset += 1 + matchlen; // 1 for ')', then delim
498         // buffer[offset] is '"'
499 
500 #undef SKIP_BSNL
501     } else { // Traditional string or char literal:
502         const char term = buffer[offset];
503         while (++offset < buffer_len && buffer[offset] != term) {
504             if (buffer[offset] == '\\')
505                 ++offset;
506             else if (qmake_endOfLine(buffer[offset]))
507                 ++*lines;
508         }
509     }
510 
511     return offset;
512 }
513 
514 bool QMakeSourceFileInfo::findDeps(SourceFile *file)
515 {
516     if(file->dep_checked || file->type == TYPE_UNKNOWN)
517         return true;
518     files_changed = true;
519     file->dep_checked = true;
520 
521     const QMakeLocalFileName sourceFile = fixPathForFile(file->file, true);
522 
523     struct stat fst;
524     char *buffer = nullptr;
525     int buffer_len = 0;
526     {
527         int fd;
528 #if defined(_MSC_VER) && _MSC_VER >= 1400
529         if (_sopen_s(&fd, sourceFile.local().toLatin1().constData(),
530             _O_RDONLY, _SH_DENYNO, _S_IREAD) != 0)
531             fd = -1;
532 #else
533         fd = open(sourceFile.local().toLatin1().constData(), O_RDONLY);
534 #endif
535         if (fd == -1 || fstat(fd, &fst) || S_ISDIR(fst.st_mode)) {
536             if (fd != -1)
537                 QT_CLOSE(fd);
538             return false;
539         }
540         buffer = getBuffer(fst.st_size);
541         for(int have_read = 0;
542             (have_read = QT_READ(fd, buffer + buffer_len, fst.st_size - buffer_len));
543             buffer_len += have_read) ;
544         QT_CLOSE(fd);
545     }
546     if(!buffer)
547         return false;
548     if(!file->deps)
549         file->deps = new SourceDependChildren;
550 
551     int line_count = 1;
552     enum {
553         /*
554           States of C preprocessing (for TYPE_C only), after backslash-newline
555           elimination and skipping comments and spaces (i.e. in ANSI X3.159-1989
556           section 2.1.1.2's phase 4).  We're about to study buffer[x] to decide
557           on which transition to do.
558          */
559         AtStart, // start of logical line; a # may start a preprocessor directive
560         HadHash, // saw a # at start, looking for preprocessor keyword
561         WantName, // saw #include or #import, waiting for name
562         InCode // after directive, parsing non-#include directive or in actual code
563     } cpp_state = AtStart;
564 
565     int x = 0;
566     if (buffer_len >= 3) {
567         const unsigned char *p = (unsigned char *)buffer;
568         // skip UTF-8 BOM, if present
569         if (p[0] == 0xEF && p[1] == 0xBB && p[2] == 0xBF)
570             x += 3;
571     }
572     for (; x < buffer_len; ++x) {
573         bool try_local = true;
574         char *inc = nullptr;
575         if(file->type == QMakeSourceFileInfo::TYPE_UI) {
576             // skip whitespaces
577             while (x < buffer_len && (buffer[x] == ' ' || buffer[x] == '\t'))
578                 ++x;
579             if (buffer[x] == '<') {
580                 ++x;
581                 if (buffer_len >= x + 12 && !strncmp(buffer + x, "includehint", 11) &&
582                     (buffer[x + 11] == ' ' || buffer[x + 11] == '>')) {
583                     for (x += 11; x < buffer_len && buffer[x] != '>'; ++x) {} // skip
584                     int inc_len = 0;
585                     for (++x; x + inc_len < buffer_len && buffer[x + inc_len] != '<'; ++inc_len) {} // skip
586                     if (x + inc_len < buffer_len) {
587                         buffer[x + inc_len] = '\0';
588                         inc = buffer + x;
589                     }
590                 } else if (buffer_len >= x + 13 && !strncmp(buffer + x, "customwidget", 12) &&
591                            (buffer[x + 12] == ' ' || buffer[x + 12] == '>')) {
592                     for (x += 13; x < buffer_len && buffer[x] != '>'; ++x) {} // skip up to >
593                     while(x < buffer_len) {
594                         while (++x < buffer_len && buffer[x] != '<') {} // skip up to <
595                         x++;
596                         if(buffer_len >= x + 7 && !strncmp(buffer+x, "header", 6) &&
597                            (buffer[x + 6] == ' ' || buffer[x + 6] == '>')) {
598                             for (x += 7; x < buffer_len && buffer[x] != '>'; ++x) {} // skip up to >
599                             int inc_len = 0;
600                             for (++x; x + inc_len < buffer_len && buffer[x + inc_len] != '<';
601                                  ++inc_len) {} // skip
602                             if (x + inc_len < buffer_len) {
603                                 buffer[x + inc_len] = '\0';
604                                 inc = buffer + x;
605                             }
606                             break;
607                         } else if(buffer_len >= x + 14 && !strncmp(buffer+x, "/customwidget", 13) &&
608                                   (buffer[x + 13] == ' ' || buffer[x + 13] == '>')) {
609                             x += 14;
610                             break;
611                         }
612                     }
613                 } else if(buffer_len >= x + 8 && !strncmp(buffer + x, "include", 7) &&
614                           (buffer[x + 7] == ' ' || buffer[x + 7] == '>')) {
615                     for (x += 8; x < buffer_len && buffer[x] != '>'; ++x) {
616                         if (buffer_len >= x + 9 && buffer[x] == 'i' &&
617                             !strncmp(buffer + x, "impldecl", 8)) {
618                             for (x += 8; x < buffer_len && buffer[x] != '='; ++x) {} // skip
619                             while (++x < buffer_len && (buffer[x] == '\t' || buffer[x] == ' ')) {} // skip
620                             char quote = 0;
621                             if (x < buffer_len && (buffer[x] == '\'' || buffer[x] == '"')) {
622                                 quote = buffer[x];
623                                 ++x;
624                             }
625                             int val_len;
626                             for (val_len = 0; x + val_len < buffer_len; ++val_len) {
627                                 if(quote) {
628                                     if (buffer[x + val_len] == quote)
629                                         break;
630                                 } else if (buffer[x + val_len] == '>' ||
631                                            buffer[x + val_len] == ' ') {
632                                     break;
633                                 }
634                             }
635 //?                            char saved = buffer[x + val_len];
636                             if (x + val_len < buffer_len) {
637                                 buffer[x + val_len] = '\0';
638                                 if (!strcmp(buffer + x, "in implementation")) {
639                                     //### do this
640                                 }
641                             }
642                         }
643                     }
644                     int inc_len = 0;
645                     for (++x; x + inc_len < buffer_len && buffer[x + inc_len] != '<';
646                          ++inc_len) {} // skip
647 
648                     if (x + inc_len < buffer_len) {
649                         buffer[x + inc_len] = '\0';
650                         inc = buffer + x;
651                     }
652                 }
653             }
654             //read past new line now..
655             for (; x < buffer_len && !qmake_endOfLine(buffer[x]); ++x) {} // skip
656             ++line_count;
657         } else if(file->type == QMakeSourceFileInfo::TYPE_QRC) {
658         } else if(file->type == QMakeSourceFileInfo::TYPE_C) {
659             // We've studied all buffer[i] for i < x
660             for (; x < buffer_len; ++x) {
661                 // How to handle backslash-newline (BSNL) pairs:
662 #define SKIP_BSNL(pos) skipEscapedLineEnds(buffer, buffer_len, (pos), &line_count)
663 
664                 // Seek code or directive, skipping comments and space:
665                 for (; (x = SKIP_BSNL(x)) < buffer_len; ++x) {
666                     if (buffer[x] == ' ' || buffer[x] == '\t') {
667                         // keep going
668                     } else if (buffer[x] == '/') {
669                         int extralines = 0;
670                         int y = skipEscapedLineEnds(buffer, buffer_len, x + 1, &extralines);
671                         if (y >= buffer_len) {
672                             x = y;
673                             break;
674                         } else if (buffer[y] == '/') { // C++-style comment
675                             line_count += extralines;
676                             x = SKIP_BSNL(y + 1);
677                             while (x < buffer_len && !qmake_endOfLine(buffer[x]))
678                                 x = SKIP_BSNL(x + 1); // skip
679 
680                             cpp_state = AtStart;
681                             ++line_count;
682                         } else if (buffer[y] == '*') { // C-style comment
683                             line_count += extralines;
684                             x = y;
685                             while ((x = SKIP_BSNL(++x)) < buffer_len) {
686                                 if (buffer[x] == '*') {
687                                     extralines = 0;
688                                     y = skipEscapedLineEnds(buffer, buffer_len,
689                                                             x + 1, &extralines);
690                                     if (y < buffer_len && buffer[y] == '/') {
691                                         line_count += extralines;
692                                         x = y; // for loop shall step past this
693                                         break;
694                                     }
695                                 } else if (qmake_endOfLine(buffer[x])) {
696                                     ++line_count;
697                                 }
698                             }
699                         } else {
700                             // buffer[x] is the division operator
701                             break;
702                         }
703                     } else if (qmake_endOfLine(buffer[x])) {
704                         ++line_count;
705                         cpp_state = AtStart;
706                     } else {
707                         /* Drop out of phases 1, 2, 3, into phase 4 */
708                         break;
709                     }
710                 }
711                 // Phase 4 study of buffer[x]:
712 
713                 if(x >= buffer_len)
714                     break;
715 
716                 switch (cpp_state) {
717                 case HadHash:
718                 {
719                     // Read keyword; buffer[x] starts first preprocessing token after #
720                     const char *const keyword = buffer + x;
721                     int clean = x;
722                     while (x < buffer_len && buffer[x] >= 'a' && buffer[x] <= 'z') {
723                         // skip over keyword, consolidating it if it contains BSNLs
724                         // (see WantName's similar code consolidating inc, below)
725                         if (clean < x)
726                             buffer[clean++] = buffer[x];
727                         else
728                             clean++;
729 
730                         x = SKIP_BSNL(x + 1);
731                     }
732                     const int keyword_len = buffer + clean - keyword;
733                     x--; // Still need to study buffer[x] next time round for loop.
734 
735                     cpp_state =
736                         ((keyword_len == 7 && !strncmp(keyword, "include", 7)) // C & Obj-C
737                       || (keyword_len == 6 && !strncmp(keyword, "import", 6))) // Obj-C
738                         ? WantName : InCode;
739                     break;
740                 }
741 
742                 case WantName:
743                 {
744                     char term = buffer[x];
745                     if (term == '<') {
746                         try_local = false;
747                         term = '>';
748                     } else if (term != '"') {
749                         /*
750                           Possibly malformed, but this may be something like:
751                           #include IDENTIFIER
752                           which does work, if #define IDENTIFIER "filename" is
753                           in effect.  This is beyond this noddy preprocessor's
754                           powers of tracking.  So give up and resume searching
755                           for a directive.  We haven't made sense of buffer[x],
756                           so back up to ensure we do study it (now as code) next
757                           time round the loop.
758                         */
759                         x--;
760                         cpp_state = InCode;
761                         continue;
762                     }
763 
764                     x = SKIP_BSNL(x + 1);
765                     inc = buffer + x;
766                     int clean = x; // offset if we need to clear \-newlines
767                     for (; x < buffer_len && buffer[x] != term; x = SKIP_BSNL(x + 1)) {
768                         if (qmake_endOfLine(buffer[x])) { // malformed
769                             cpp_state = AtStart;
770                             ++line_count;
771                             break;
772                         }
773 
774                         /*
775                           If we do skip any BSNLs, we need to consolidate the
776                           surviving text by copying to lower indices.  For that
777                           to be possible, we also have to keep 'clean' advanced
778                           in step with x even when we've yet to see any BSNLs.
779                         */
780                         if (clean < x)
781                             buffer[clean++] = buffer[x];
782                         else
783                             clean++;
784                     }
785                     if (cpp_state == WantName)
786                         buffer[clean] = '\0';
787                     else // i.e. malformed
788                         inc = nullptr;
789 
790                     cpp_state = InCode; // hereafter
791                     break;
792                 }
793 
794                 case AtStart:
795                     // Preprocessor directive?
796                     if (buffer[x] == '#') {
797                         cpp_state = HadHash;
798                         break;
799                     }
800                     cpp_state = InCode;
801                     Q_FALLTHROUGH(); // to handle buffer[x] as such.
802                 case InCode:
803                     // matching quotes (string literals and character literals)
804                     if (buffer[x] == '\'' || buffer[x] == '"') {
805                         x = scanPastString(buffer, buffer_len, x, &line_count);
806                         // for loop's ++x shall step over the closing quote.
807                     }
808                     // else: buffer[x] is just some code; move on.
809                     break;
810                 }
811 
812                 if (inc) // We were in WantName and found a name.
813                     break;
814 #undef SKIP_BSNL
815             }
816             if(x >= buffer_len)
817                 break;
818         }
819 
820         if(inc) {
821             if(!includes)
822                 includes = new SourceFiles;
823             /* QTBUG-72383: Local includes "foo.h" must first be resolved relative to the
824              * sourceDir, only global includes <bar.h> are unique. */
825             SourceFile *dep = try_local ? nullptr : includes->lookupFile(inc);
826             if(!dep) {
827                 bool exists = false;
828                 QMakeLocalFileName lfn(inc);
829                 if(QDir::isRelativePath(lfn.real())) {
830                     if(try_local) {
831                         QDir sourceDir = findFileInfo(sourceFile).dir();
832                         QMakeLocalFileName f(sourceDir.absoluteFilePath(lfn.local()));
833                         if(findFileInfo(f).exists()) {
834                             lfn = fixPathForFile(f);
835                             exists = true;
836                         }
837                     }
838                     if(!exists) { //path lookup
839                         for (const QMakeLocalFileName &depdir : qAsConst(depdirs)) {
840                             QMakeLocalFileName f(depdir.real() + Option::dir_sep + lfn.real());
841                             QFileInfo fi(findFileInfo(f));
842                             if(fi.exists() && !fi.isDir()) {
843                                 lfn = fixPathForFile(f);
844                                 exists = true;
845                                 break;
846                             }
847                         }
848                     }
849                     if(!exists) { //heuristic lookup
850                         lfn = findFileForDep(QMakeLocalFileName(inc), file->file);
851                         if((exists = !lfn.isNull()))
852                             lfn = fixPathForFile(lfn);
853                     }
854                 } else {
855                     exists = QFile::exists(lfn.real());
856                 }
857                 if (!lfn.isNull() && !isSystemInclude(lfn.real())) {
858                     dep = files->lookupFile(lfn);
859                     if(!dep) {
860                         dep = new SourceFile;
861                         dep->file = lfn;
862                         dep->type = QMakeSourceFileInfo::TYPE_C;
863                         files->addFile(dep);
864                         /* QTBUG-72383: Local includes "foo.h" are keyed by the resolved
865                          * path (stored in dep itself), only global includes <bar.h> are
866                          * unique keys immediately. */
867                         const char *key = try_local ? nullptr : inc;
868                         includes->addFile(dep, key, false);
869                     }
870                     dep->exists = exists;
871                 }
872             }
873             if(dep && dep->file != file->file) {
874                 dep->included_count++;
875                 if(dep->exists) {
876                     debug_msg(5, "%s:%d Found dependency to %s", file->file.real().toLatin1().constData(),
877                               line_count, dep->file.local().toLatin1().constData());
878                     file->deps->addChild(dep);
879                 }
880             }
881         }
882     }
883     if(dependencyMode() == Recursive) { //done last because buffer is shared
884         for(int i = 0; i < file->deps->used_nodes; i++) {
885             if(!file->deps->children[i]->deps)
886                 findDeps(file->deps->children[i]);
887         }
888     }
889     return true;
890 }
891 
892 static bool isCWordChar(char c) {
893     return c == '_'
894         || (c >= 'a' && c <= 'z')
895         || (c >= 'A' && c <= 'Z')
896         || (c >= '0' && c <= '9');
897 }
898 
899 bool QMakeSourceFileInfo::findMocs(SourceFile *file)
900 {
901     if(file->moc_checked)
902         return true;
903     files_changed = true;
904     file->moc_checked = true;
905 
906     int buffer_len = 0;
907     char *buffer = nullptr;
908     {
909         struct stat fst;
910         int fd;
911 #if defined(_MSC_VER) && _MSC_VER >= 1400
912         if (_sopen_s(&fd, fixPathForFile(file->file, true).local().toLocal8Bit().constData(),
913             _O_RDONLY, _SH_DENYNO, _S_IREAD) != 0)
914             fd = -1;
915 #else
916         fd = open(fixPathForFile(file->file, true).local().toLocal8Bit().constData(), O_RDONLY);
917 #endif
918         if (fd == -1 || fstat(fd, &fst) || S_ISDIR(fst.st_mode)) {
919             if (fd != -1)
920                 QT_CLOSE(fd);
921             return false; //shouldn't happen
922         }
923         buffer = getBuffer(fst.st_size);
924         while (int have_read = QT_READ(fd, buffer + buffer_len, fst.st_size - buffer_len))
925             buffer_len += have_read;
926 
927         QT_CLOSE(fd);
928     }
929 
930     debug_msg(2, "findMocs: %s", file->file.local().toLatin1().constData());
931     int line_count = 1;
932     // [0] for Q_OBJECT, [1] for Q_GADGET, [2] for Q_NAMESPACE, [3] for Q_NAMESPACE_EXPORT
933     bool ignore[4] = { false, false, false, false };
934  /* qmake ignore Q_GADGET */
935  /* qmake ignore Q_OBJECT */
936  /* qmake ignore Q_NAMESPACE */
937  /* qmake ignore Q_NAMESPACE_EXPORT */
938     for(int x = 0; x < buffer_len; x++) {
939 #define SKIP_BSNL(pos) skipEscapedLineEnds(buffer, buffer_len, (pos), &line_count)
940         x = SKIP_BSNL(x);
941         if (buffer[x] == '/') {
942             int extralines = 0;
943             int y = skipEscapedLineEnds(buffer, buffer_len, x + 1, &extralines);
944             if (buffer_len > y) {
945                 // If comment, advance to the character that ends it:
946                 if (buffer[y] == '/') { // C++-style comment
947                     line_count += extralines;
948                     x = y;
949                     do {
950                         x = SKIP_BSNL(x + 1);
951                     } while (x < buffer_len && !qmake_endOfLine(buffer[x]));
952 
953                 } else if (buffer[y] == '*') { // C-style comment
954                     line_count += extralines;
955                     x = SKIP_BSNL(y + 1);
956                     for (; x < buffer_len; x = SKIP_BSNL(x + 1)) {
957                         if (buffer[x] == 't' || buffer[x] == 'q') { // ignore
958                             if(buffer_len >= (x + 20) &&
959                                !strncmp(buffer + x + 1, "make ignore Q_OBJECT", 20)) {
960                                 debug_msg(2, "Mocgen: %s:%d Found \"qmake ignore Q_OBJECT\"",
961                                           file->file.real().toLatin1().constData(), line_count);
962                                 x += 20;
963                                 ignore[0] = true;
964                             } else if(buffer_len >= (x + 20) &&
965                                       !strncmp(buffer + x + 1, "make ignore Q_GADGET", 20)) {
966                                 debug_msg(2, "Mocgen: %s:%d Found \"qmake ignore Q_GADGET\"",
967                                           file->file.real().toLatin1().constData(), line_count);
968                                 x += 20;
969                                 ignore[1] = true;
970                             } else if (buffer_len >= (x + 23) &&
971                                       !strncmp(buffer + x + 1, "make ignore Q_NAMESPACE", 23)) {
972                                 debug_msg(2, "Mocgen: %s:%d Found \"qmake ignore Q_NAMESPACE\"",
973                                           file->file.real().toLatin1().constData(), line_count);
974                                 x += 23;
975                                 ignore[2] = true;
976                             } else if (buffer_len >= (x + 30) &&
977                                        !strncmp(buffer + x + 1, "make ignore Q_NAMESPACE_EXPORT", 30)) {
978                                  debug_msg(2, "Mocgen: %s:%d Found \"qmake ignore Q_NAMESPACE_EXPORT\"",
979                                            file->file.real().toLatin1().constData(), line_count);
980                                  x += 30;
981                                  ignore[3] = true;
982                             }
983                         } else if (buffer[x] == '*') {
984                             extralines = 0;
985                             y = skipEscapedLineEnds(buffer, buffer_len, x + 1, &extralines);
986                             if (buffer_len > y && buffer[y] == '/') {
987                                 line_count += extralines;
988                                 x = y;
989                                 break;
990                             }
991                         } else if (Option::debug_level && qmake_endOfLine(buffer[x])) {
992                             ++line_count;
993                         }
994                     }
995                 }
996                 // else: don't update x, buffer[x] is just the division operator.
997             }
998         } else if (buffer[x] == '\'' || buffer[x] == '"') {
999             x = scanPastString(buffer, buffer_len, x, &line_count);
1000             // Leaves us on closing quote; for loop's x++ steps us past it.
1001         }
1002 
1003         if (x < buffer_len && Option::debug_level && qmake_endOfLine(buffer[x]))
1004             ++line_count;
1005         if (buffer_len > x + 8 && !isCWordChar(buffer[x])) {
1006             int morelines = 0;
1007             int y = skipEscapedLineEnds(buffer, buffer_len, x + 1, &morelines);
1008             if (buffer[y] == 'Q') {
1009                 static const char interesting[][19] = { "Q_OBJECT", "Q_GADGET", "Q_NAMESPACE", "Q_NAMESPACE_EXPORT" };
1010                 for (int interest = 0; interest < 4; ++interest) {
1011                     if (ignore[interest])
1012                         continue;
1013 
1014                     int matchlen = 0, extralines = 0;
1015                     size_t needle_len = strlen(interesting[interest]);
1016                     Q_ASSERT(needle_len <= INT_MAX);
1017                     if (matchWhileUnsplitting(buffer, buffer_len, y,
1018                                               interesting[interest],
1019                                               static_cast<int>(needle_len),
1020                                               &matchlen, &extralines)
1021                         && y + matchlen < buffer_len
1022                         && !isCWordChar(buffer[y + matchlen])) {
1023                         if (Option::debug_level) {
1024                             buffer[y + matchlen] = '\0';
1025                             debug_msg(2, "Mocgen: %s:%d Found MOC symbol %s",
1026                                       file->file.real().toLatin1().constData(),
1027                                       line_count + morelines, buffer + y);
1028                         }
1029                         file->mocable = true;
1030                         return true;
1031                     }
1032                 }
1033             }
1034         }
1035 #undef SKIP_BSNL
1036     }
1037     return true;
1038 }
1039 
1040 QT_END_NAMESPACE
1041