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