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