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

source code of qtbase/qmake/generators/makefiledeps.cpp