Revision control

++ // Copyright (C) 2016 The Qt Company Ltd.
++ // Copyright (C) 2016 Intel Corporation.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
++
++ #include "makefiledeps.h"
++ #include "option.h"
++ #include <qfile.h>
++ #include <qdir.h>
++ #include <qdatetime.h>
++ #include <qfileinfo.h>
++ #include <qbuffer.h>
++ #include <qplatformdefs.h>
++ #if defined(Q_OS_UNIX)
++ # include <unistd.h>
++ #else
++ # include <io.h>
++ #endif
++ #include <qdebug.h>
++ #include <time.h>
++ #include <limits.h>
++
++ QT_BEGIN_NAMESPACE
++
++ // FIXME: a line ending in CRLF gets counted as two lines.
++ #if 1
++ #define qmake_endOfLine(c) (c == '\r' || c == '\n')
++ #else
++ inline bool qmake_endOfLine(const char &c) { return (c == '\r' || c == '\n'); }
++ #endif
++
++ QMakeLocalFileName::QMakeLocalFileName(const QString &name)
++ : real_name(name)
++ {
++ }
++ const QString
++ &QMakeLocalFileName::local() const
++ {
++ if (!isNull() && local_name.isNull())
++ local_name = Option::normalizePath(real_name);
++ return local_name;
++ }
++
++ struct SourceDependChildren;
++ struct SourceFile {
++ SourceFile() : deps(nullptr), type(QMakeSourceFileInfo::TYPE_UNKNOWN),
++ mocable(0), traversed(0), exists(1),
++ moc_checked(0), dep_checked(0), included_count(0) { }
++ ~SourceFile();
++ QMakeLocalFileName file;
++ SourceDependChildren *deps;
++ QMakeSourceFileInfo::SourceFileType type;
++ uint mocable : 1, traversed : 1, exists : 1;
++ uint moc_checked : 1, dep_checked : 1;
++ uchar included_count;
++ };
++ struct SourceDependChildren {
++ SourceFile **children;
++ int num_nodes, used_nodes;
++ SourceDependChildren() : children(nullptr), num_nodes(0), used_nodes(0) { }
++ ~SourceDependChildren() { if (children) free(children); children = nullptr; }
++ void addChild(SourceFile *s) {
++ if(num_nodes <= used_nodes) {
++ num_nodes += 200;
++ children = (SourceFile**)realloc(children, sizeof(SourceFile*)*(num_nodes));
++ }
++ children[used_nodes++] = s;
++ }
++ };
++ SourceFile::~SourceFile() { delete deps; }
++ class SourceFiles {
++ int hash(const char *);
++ public:
++ SourceFiles();
++ ~SourceFiles();
++
++ SourceFile *lookupFile(const char *);
++ inline SourceFile *lookupFile(const QString &f) { return lookupFile(f.toLatin1().constData()); }
++ inline SourceFile *lookupFile(const QMakeLocalFileName &f) { return lookupFile(f.local().toLatin1().constData()); }
++ void addFile(SourceFile *, const char *k = nullptr, bool own = true);
++
++ struct SourceFileNode {
++ SourceFileNode() : key(nullptr), next(nullptr), file(nullptr), own_file(1) { }
++ ~SourceFileNode() {
++ delete [] key;
++ if(own_file)
++ delete file;
++ }
++ char *key;
++ SourceFileNode *next;
++ SourceFile *file;
++ uint own_file : 1;
++ } **nodes;
++ int num_nodes;
++ };
++ SourceFiles::SourceFiles()
++ {
++ nodes = (SourceFileNode**)malloc(sizeof(SourceFileNode*)*(num_nodes=3037));
++ for(int n = 0; n < num_nodes; n++)
++ nodes[n] = nullptr;
++ }
++
++ SourceFiles::~SourceFiles()
++ {
++ for(int n = 0; n < num_nodes; n++) {
++ for(SourceFileNode *next = nodes[n]; next;) {
++ SourceFileNode *next_next = next->next;
++ delete next;
++ next = next_next;
++ }
++ }
++ free(nodes);
++ }
++
++ int SourceFiles::hash(const char *file)
++ {
++ uint h = 0, g;
++ while (*file) {
++ h = (h << 4) + *file;
++ if ((g = (h & 0xf0000000)) != 0)
++ h ^= g >> 23;
++ h &= ~g;
++ file++;
++ }
++ return h;
++ }
++
++ SourceFile *SourceFiles::lookupFile(const char *file)
++ {
++ int h = hash(file) % num_nodes;
++ for(SourceFileNode *p = nodes[h]; p; p = p->next) {
++ if(!strcmp(p->key, file))
++ return p->file;
++ }
++ return nullptr;
++ }
++
++ void SourceFiles::addFile(SourceFile *p, const char *k, bool own_file)
++ {
++ const QByteArray ba = p->file.local().toLatin1();
++ if(!k)
++ k = ba.constData();
++ int h = hash(k) % num_nodes;
++ SourceFileNode *pn = new SourceFileNode;
++ pn->own_file = own_file;
++ pn->key = qstrdup(k);
++ pn->file = p;
++ pn->next = nodes[h];
++ nodes[h] = pn;
++ }
++
++ void QMakeSourceFileInfo::dependTreeWalker(SourceFile *node, SourceDependChildren *place)
++ {
++ if(node->traversed || !node->exists)
++ return;
++ place->addChild(node);
++ node->traversed = true; //set flag
++ if(node->deps) {
++ for(int i = 0; i < node->deps->used_nodes; i++)
++ dependTreeWalker(node->deps->children[i], place);
++ }
++ }
++
++ void QMakeSourceFileInfo::setDependencyPaths(const QList<QMakeLocalFileName> &l)
++ {
++ // Ensure that depdirs does not contain the same paths several times, to minimize the stats
++ QList<QMakeLocalFileName> ll;
++ for (int i = 0; i < l.size(); ++i) {
++ if (!ll.contains(l.at(i)))
++ ll.append(l.at(i));
++ }
++ depdirs = ll;
++ }
++
++ QStringList QMakeSourceFileInfo::dependencies(const QString &file)
++ {
++ QStringList ret;
++ if(!files)
++ return ret;
++
++ if(SourceFile *node = files->lookupFile(QMakeLocalFileName(file))) {
++ if(node->deps) {
++ /* I stick them into a SourceDependChildren here because it is faster to just
++ iterate over the list to stick them in the list, and reset the flag, then it is
++ to loop over the tree (about 50% faster I saw) --Sam */
++ SourceDependChildren place;
++ for(int i = 0; i < node->deps->used_nodes; i++)
++ dependTreeWalker(node->deps->children[i], &place);
++ if(place.children) {
++ for(int i = 0; i < place.used_nodes; i++) {
++ place.children[i]->traversed = false; //reset flag
++ ret.append(place.children[i]->file.real());
++ }
++ }
++ }
++ }
++ return ret;
++ }
++
++ int
++ QMakeSourceFileInfo::included(const QString &file)
++ {
++ if (!files)
++ return 0;
++
++ if(SourceFile *node = files->lookupFile(QMakeLocalFileName(file)))
++ return node->included_count;
++ return 0;
++ }
++
++ bool QMakeSourceFileInfo::mocable(const QString &file)
++ {
++ if(SourceFile *node = files->lookupFile(QMakeLocalFileName(file)))
++ return node->mocable;
++ return false;
++ }
++
++ QMakeSourceFileInfo::QMakeSourceFileInfo()
++ {
++ //dep_mode
++ dep_mode = Recursive;
++
++ //quick project lookups
++ includes = files = nullptr;
++ files_changed = false;
++
++ //buffer
++ spare_buffer = nullptr;
++ spare_buffer_size = 0;
++ }
++
++ QMakeSourceFileInfo::~QMakeSourceFileInfo()
++ {
++ //buffer
++ if(spare_buffer) {
++ free(spare_buffer);
++ spare_buffer = nullptr;
++ spare_buffer_size = 0;
++ }
++
++ //quick project lookup
++ delete files;
++ delete includes;
++ }
++
++ void QMakeSourceFileInfo::addSourceFiles(const ProStringList &l, uchar seek,
++ QMakeSourceFileInfo::SourceFileType type)
++ {
++ for(int i=0; i<l.size(); ++i)
++ addSourceFile(l.at(i).toQString(), seek, type);
++ }
++ void QMakeSourceFileInfo::addSourceFile(const QString &f, uchar seek,
++ QMakeSourceFileInfo::SourceFileType type)
++ {
++ if(!files)
++ files = new SourceFiles;
++
++ QMakeLocalFileName fn(f);
++ SourceFile *file = files->lookupFile(fn);
++ if(!file) {
++ file = new SourceFile;
++ file->file = fn;
++ files->addFile(file);
++ } else {
++ if(file->type != type && file->type != TYPE_UNKNOWN && type != TYPE_UNKNOWN)
++ warn_msg(WarnLogic, "%s is marked as %d, then %d!", f.toLatin1().constData(),
++ file->type, type);
++ }
++ if(type != TYPE_UNKNOWN)
++ file->type = type;
++
++ if(seek & SEEK_MOCS && !file->moc_checked)
++ findMocs(file);
++ if(seek & SEEK_DEPS && !file->dep_checked)
++ findDeps(file);
++ }
++
++ bool QMakeSourceFileInfo::containsSourceFile(const QString &f, SourceFileType type)
++ {
++ if(SourceFile *file = files->lookupFile(QMakeLocalFileName(f)))
++ return (file->type == type || file->type == TYPE_UNKNOWN || type == TYPE_UNKNOWN);
++ return false;
++ }
++
++ bool QMakeSourceFileInfo::isSystemInclude(const QString &name)
++ {
++ if (QDir::isRelativePath(name)) {
++ // if we got a relative path here, it's either an -I flag with a relative path
++ // or an include file we couldn't locate. Either way, conclude it's not
++ // a system include.
++ return false;
++ }
++
++ for (int i = 0; i < systemIncludes.size(); ++i) {
++ // check if name is located inside the system include dir:
++ QDir systemDir(systemIncludes.at(i));
++ QString relativePath = systemDir.relativeFilePath(name);
++
++ // the relative path might be absolute if we're crossing drives on Windows
++ if (QDir::isAbsolutePath(relativePath) || relativePath.startsWith("../"))
++ continue;
++ debug_msg(5, "File/dir %s is in system dir %s, skipping",
++ qPrintable(name), qPrintable(systemIncludes.at(i)));
++ return true;
++ }
++ return false;
++ }
++
++ char *QMakeSourceFileInfo::getBuffer(int s) {
++ if(!spare_buffer || spare_buffer_size < s)
++ spare_buffer = (char *)realloc(spare_buffer, spare_buffer_size=s);
++ return spare_buffer;
++ }
++
++ #ifndef S_ISDIR
++ #define S_ISDIR(x) (x & _S_IFDIR)
++ #endif
++
++ QMakeLocalFileName QMakeSourceFileInfo::fixPathForFile(const QMakeLocalFileName &f, bool)
++ {
++ return f;
++ }
++
++ QMakeLocalFileName QMakeSourceFileInfo::findFileForDep(const QMakeLocalFileName &/*dep*/,
++ const QMakeLocalFileName &/*file*/)
++ {
++ return QMakeLocalFileName();
++ }
++
++ QFileInfo QMakeSourceFileInfo::findFileInfo(const QMakeLocalFileName &dep)
++ {
++ return QFileInfo(dep.real());
++ }
++
++ static int skipEscapedLineEnds(const char *buffer, int buffer_len, int offset, int *lines)
++ {
++ // Join physical lines to make logical lines, as in the C preprocessor
++ while (offset + 1 < buffer_len
++ && buffer[offset] == '\\'
++ && qmake_endOfLine(buffer[offset + 1])) {
++ offset += 2;
++ ++*lines;
++ if (offset < buffer_len
++ && buffer[offset - 1] == '\r'
++ && buffer[offset] == '\n') // CRLF
++ offset++;
++ }
++ return offset;
++ }
++
++ static bool matchWhileUnsplitting(const char *buffer, int buffer_len, int start,
++ const char *needle, int needle_len,
++ int *matchlen, int *lines)
++ {
++ int x = start;
++ for (int n = 0; n < needle_len;
++ n++, x = skipEscapedLineEnds(buffer, buffer_len, x + 1, lines)) {
++ if (x >= buffer_len || buffer[x] != needle[n])
++ return false;
++ }
++ // That also skipped any remaining BSNLs immediately after the match.
++
++ // Tell caller how long the match was:
++ *matchlen = x - start;
++
++ return true;
++ }
++
++ /* Advance from an opening quote at buffer[offset] to the matching close quote.
++ If an apostrophe turns out to be a digit-separator in a numeric literal,
++ rather than the start of a character literal, treat it as both the open and
++ the close quote of the "string" that isn't there.
++ */
++ static int scanPastString(char *buffer, int buffer_len, int offset, int *lines)
++ {
++ // It might be a C++11 raw string.
++ bool israw = false;
++
++ Q_ASSERT(offset < buffer_len);
++ if (offset <= 0) {
++ // skip, neither of these special cases applies here
++ } else if (buffer[offset] == '"') {
++ int explore = offset - 1;
++ bool prefix = false; // One of L, U, u or u8 may appear before R
++ bool saw8 = false; // Partial scan of u8
++ while (explore >= 0) {
++ // Cope with backslash-newline interruptions of the prefix:
++ if (explore > 0
++ && qmake_endOfLine(buffer[explore])
++ && buffer[explore - 1] == '\\') {
++ explore -= 2;
++ } else if (explore > 1
++ && buffer[explore] == '\n'
++ && buffer[explore - 1] == '\r'
++ && buffer[explore - 2] == '\\') {
++ explore -= 3;
++ // Remaining cases can only decrement explore by one at a time:
++ } else if (saw8 && buffer[explore] == 'u') {
++ explore--;
++ saw8 = false;
++ prefix = true;
++ } else if (saw8 || prefix) {
++ break;
++ } else if (explore > 1 && buffer[explore] == '8') {
++ explore--;
++ saw8 = true;
++ } else if (buffer[explore] == 'L'
++ || buffer[explore] == 'U'
++ || buffer[explore] == 'u') {
++ explore--;
++ prefix = true;
++ } else if (buffer[explore] == 'R') {
++ if (israw)
++ break;
++ explore--;
++ israw = true;
++ } else {
++ break;
++ }
++ }
++ // Check the R (with possible prefix) isn't just part of an identifier:
++ if (israw && explore >= 0
++ && (isalnum(buffer[explore]) || buffer[explore] == '_')) {
++ israw = false;
++ }
++
++ } else {
++ // Is this apostrophe a digit separator rather than the start of a
++ // character literal ? If so, there was no string to scan past, so
++ // treat the apostrophe as both open and close.
++ Q_ASSERT(buffer[offset] == '\'' && offset > 0);
++ // Wrap std::isdigit() to package the casting to unsigned char.
++ const auto isDigit = [](unsigned char c) { return std::isdigit(c); };
++ if (isDigit(buffer[offset - 1]) && offset + 1 < buffer_len && isDigit(buffer[offset + 1])) {
++ // One exception: u8'0' is a perfectly good character literal.
++ if (offset < 2 || buffer[offset - 1] != '8' || buffer[offset - 2] != 'u')
++ return offset;
++ }
++ }
++
++ if (israw) {
++ #define SKIP_BSNL(pos) skipEscapedLineEnds(buffer, buffer_len, (pos), lines)
++
++ offset = SKIP_BSNL(offset + 1);
++ const char *const delim = buffer + offset;
++ int clean = offset;
++ while (offset < buffer_len && buffer[offset] != '(') {
++ if (clean < offset)
++ buffer[clean++] = buffer[offset];
++ else
++ clean++;
++
++ offset = SKIP_BSNL(offset + 1);
++ }
++ /*
++ Not checking correctness (trust real compiler to do that):
++ - no controls, spaces, '(', ')', '\\' or (presumably) '"' in delim;
++ - at most 16 bytes in delim
++
++ Raw strings are surely defined after phase 2, when BSNLs are resolved;
++ so the delimiter's exclusion of '\\' and space (including newlines)
++ applies too late to save us the need to cope with BSNLs in it.
++ */
++
++ const int delimlen = buffer + clean - delim;
++ int matchlen = delimlen, extralines = 0;
++ while ((offset = SKIP_BSNL(offset + 1)) < buffer_len
++ && (buffer[offset] != ')'
++ || (delimlen > 0 &&
++ !matchWhileUnsplitting(buffer, buffer_len,
++ offset + 1, delim, delimlen,
++ &matchlen, &extralines))
++ || buffer[offset + 1 + matchlen] != '"')) {
++ // skip, but keep track of lines
++ if (qmake_endOfLine(buffer[offset]))
++ ++*lines;
++ extralines = 0;
++ }
++ *lines += extralines; // from the match
++ // buffer[offset] is ')'
++ offset += 1 + matchlen; // 1 for ')', then delim
++ // buffer[offset] is '"'
++
++ #undef SKIP_BSNL
++ } else { // Traditional string or char literal:
++ const char term = buffer[offset];
++ while (++offset < buffer_len && buffer[offset] != term) {
++ if (buffer[offset] == '\\')
++ ++offset;
++ else if (qmake_endOfLine(buffer[offset]))
++ ++*lines;
++ }
++ }
++
++ return offset;
++ }
++
++ bool QMakeSourceFileInfo::findDeps(SourceFile *file)
++ {
++ if(file->dep_checked || file->type == TYPE_UNKNOWN)
++ return true;
++ files_changed = true;
++ file->dep_checked = true;
++
++ const QMakeLocalFileName sourceFile = fixPathForFile(file->file, true);
++
++ char *buffer = nullptr;
++ int buffer_len = 0;
++ {
++ QFile f(sourceFile.local());
++ if (!f.open(QIODevice::ReadOnly))
++ return false;
++ const qint64 fs = f.size();
++ buffer = getBuffer(fs);
++ for(int have_read = 0;
++ (have_read = f.read(buffer + buffer_len, fs - buffer_len));
++ buffer_len += have_read) ;
++ }
++ if(!buffer)
++ return false;
++ if(!file->deps)
++ file->deps = new SourceDependChildren;
++
++ int line_count = 1;
++ enum {
++ /*
++ States of C preprocessing (for TYPE_C only), after backslash-newline
++ elimination and skipping comments and spaces (i.e. in ANSI X3.159-1989
++ section 2.1.1.2's phase 4). We're about to study buffer[x] to decide
++ on which transition to do.
++ */
++ AtStart, // start of logical line; a # may start a preprocessor directive
++ HadHash, // saw a # at start, looking for preprocessor keyword
++ WantName, // saw #include or #import, waiting for name
++ InCode // after directive, parsing non-#include directive or in actual code
++ } cpp_state = AtStart;
++
++ int x = 0;
++ if (buffer_len >= 3) {
++ const unsigned char *p = (unsigned char *)buffer;
++ // skip UTF-8 BOM, if present
++ if (p[0] == 0xEF && p[1] == 0xBB && p[2] == 0xBF)
++ x += 3;
++ }
++ for (; x < buffer_len; ++x) {
++ bool try_local = true;
++ char *inc = nullptr;
++ if(file->type == QMakeSourceFileInfo::TYPE_UI) {
++ // skip whitespaces
++ while (x < buffer_len && (buffer[x] == ' ' || buffer[x] == '\t'))
++ ++x;
++ if (buffer[x] == '<') {
++ ++x;
++ if (buffer_len >= x + 12 && !strncmp(buffer + x, "includehint", 11) &&
++ (buffer[x + 11] == ' ' || buffer[x + 11] == '>')) {
++ for (x += 11; x < buffer_len && buffer[x] != '>'; ++x) {} // skip
++ int inc_len = 0;
++ for (++x; x + inc_len < buffer_len && buffer[x + inc_len] != '<'; ++inc_len) {} // skip
++ if (x + inc_len < buffer_len) {
++ buffer[x + inc_len] = '\0';
++ inc = buffer + x;
++ }
++ } else if (buffer_len >= x + 13 && !strncmp(buffer + x, "customwidget", 12) &&
++ (buffer[x + 12] == ' ' || buffer[x + 12] == '>')) {
++ for (x += 13; x < buffer_len && buffer[x] != '>'; ++x) {} // skip up to >
++ while(x < buffer_len) {
++ while (++x < buffer_len && buffer[x] != '<') {} // skip up to <
++ x++;
++ if(buffer_len >= x + 7 && !strncmp(buffer+x, "header", 6) &&
++ (buffer[x + 6] == ' ' || buffer[x + 6] == '>')) {
++ for (x += 7; x < buffer_len && buffer[x] != '>'; ++x) {} // skip up to >
++ int inc_len = 0;
++ for (++x; x + inc_len < buffer_len && buffer[x + inc_len] != '<';
++ ++inc_len) {} // skip
++ if (x + inc_len < buffer_len) {
++ buffer[x + inc_len] = '\0';
++ inc = buffer + x;
++ }
++ break;
++ } else if(buffer_len >= x + 14 && !strncmp(buffer+x, "/customwidget", 13) &&
++ (buffer[x + 13] == ' ' || buffer[x + 13] == '>')) {
++ x += 14;
++ break;
++ }
++ }
++ } else if(buffer_len >= x + 8 && !strncmp(buffer + x, "include", 7) &&
++ (buffer[x + 7] == ' ' || buffer[x + 7] == '>')) {
++ for (x += 8; x < buffer_len && buffer[x] != '>'; ++x) {
++ if (buffer_len >= x + 9 && buffer[x] == 'i' &&
++ !strncmp(buffer + x, "impldecl", 8)) {
++ for (x += 8; x < buffer_len && buffer[x] != '='; ++x) {} // skip
++ while (++x < buffer_len && (buffer[x] == '\t' || buffer[x] == ' ')) {} // skip
++ char quote = 0;
++ if (x < buffer_len && (buffer[x] == '\'' || buffer[x] == '"')) {
++ quote = buffer[x];
++ ++x;
++ }
++ int val_len;
++ for (val_len = 0; x + val_len < buffer_len; ++val_len) {
++ if(quote) {
++ if (buffer[x + val_len] == quote)
++ break;
++ } else if (buffer[x + val_len] == '>' ||
++ buffer[x + val_len] == ' ') {
++ break;
++ }
++ }
++ //? char saved = buffer[x + val_len];
++ if (x + val_len < buffer_len) {
++ buffer[x + val_len] = '\0';
++ if (!strcmp(buffer + x, "in implementation")) {
++ //### do this
++ }
++ }
++ }
++ }
++ int inc_len = 0;
++ for (++x; x + inc_len < buffer_len && buffer[x + inc_len] != '<';
++ ++inc_len) {} // skip
++
++ if (x + inc_len < buffer_len) {
++ buffer[x + inc_len] = '\0';
++ inc = buffer + x;
++ }
++ }
++ }
++ //read past new line now..
++ for (; x < buffer_len && !qmake_endOfLine(buffer[x]); ++x) {} // skip
++ ++line_count;
++ } else if(file->type == QMakeSourceFileInfo::TYPE_QRC) {
++ } else if(file->type == QMakeSourceFileInfo::TYPE_C) {
++ // We've studied all buffer[i] for i < x
++ for (; x < buffer_len; ++x) {
++ // How to handle backslash-newline (BSNL) pairs:
++ #define SKIP_BSNL(pos) skipEscapedLineEnds(buffer, buffer_len, (pos), &line_count)
++
++ // Seek code or directive, skipping comments and space:
++ for (; (x = SKIP_BSNL(x)) < buffer_len; ++x) {
++ if (buffer[x] == ' ' || buffer[x] == '\t') {
++ // keep going
++ } else if (buffer[x] == '/') {
++ int extralines = 0;
++ int y = skipEscapedLineEnds(buffer, buffer_len, x + 1, &extralines);
++ if (y >= buffer_len) {
++ x = y;
++ break;
++ } else if (buffer[y] == '/') { // C++-style comment
++ line_count += extralines;
++ x = SKIP_BSNL(y + 1);
++ while (x < buffer_len && !qmake_endOfLine(buffer[x]))
++ x = SKIP_BSNL(x + 1); // skip
++
++ cpp_state = AtStart;
++ ++line_count;
++ } else if (buffer[y] == '*') { // C-style comment
++ line_count += extralines;
++ x = y;
++ while ((x = SKIP_BSNL(++x)) < buffer_len) {
++ if (buffer[x] == '*') {
++ extralines = 0;
++ y = skipEscapedLineEnds(buffer, buffer_len,
++ x + 1, &extralines);
++ if (y < buffer_len && buffer[y] == '/') {
++ line_count += extralines;
++ x = y; // for loop shall step past this
++ break;
++ }
++ } else if (qmake_endOfLine(buffer[x])) {
++ ++line_count;
++ }
++ }
++ } else {
++ // buffer[x] is the division operator
++ break;
++ }
++ } else if (qmake_endOfLine(buffer[x])) {
++ ++line_count;
++ cpp_state = AtStart;
++ } else {
++ /* Drop out of phases 1, 2, 3, into phase 4 */
++ break;
++ }
++ }
++ // Phase 4 study of buffer[x]:
++
++ if(x >= buffer_len)
++ break;
++
++ switch (cpp_state) {
++ case HadHash:
++ {
++ // Read keyword; buffer[x] starts first preprocessing token after #
++ const char *const keyword = buffer + x;
++ int clean = x;
++ while (x < buffer_len && buffer[x] >= 'a' && buffer[x] <= 'z') {
++ // skip over keyword, consolidating it if it contains BSNLs
++ // (see WantName's similar code consolidating inc, below)
++ if (clean < x)
++ buffer[clean++] = buffer[x];
++ else
++ clean++;
++
++ x = SKIP_BSNL(x + 1);
++ }
++ const int keyword_len = buffer + clean - keyword;
++ x--; // Still need to study buffer[x] next time round for loop.
++
++ cpp_state =
++ ((keyword_len == 7 && !strncmp(keyword, "include", 7)) // C & Obj-C
++ || (keyword_len == 6 && !strncmp(keyword, "import", 6))) // Obj-C
++ ? WantName : InCode;
++ break;
++ }
++
++ case WantName:
++ {
++ char term = buffer[x];
++ if (term == '<') {
++ try_local = false;
++ term = '>';
++ } else if (term != '"') {
++ /*
++ Possibly malformed, but this may be something like:
++ #include IDENTIFIER
++ which does work, if #define IDENTIFIER "filename" is
++ in effect. This is beyond this noddy preprocessor's
++ powers of tracking. So give up and resume searching
++ for a directive. We haven't made sense of buffer[x],
++ so back up to ensure we do study it (now as code) next
++ time round the loop.
++ */
++ x--;
++ cpp_state = InCode;
++ continue;
++ }
++
++ x = SKIP_BSNL(x + 1);
++ inc = buffer + x;
++ int clean = x; // offset if we need to clear \-newlines
++ for (; x < buffer_len && buffer[x] != term; x = SKIP_BSNL(x + 1)) {
++ if (qmake_endOfLine(buffer[x])) { // malformed
++ cpp_state = AtStart;
++ ++line_count;
++ break;
++ }
++
++ /*
++ If we do skip any BSNLs, we need to consolidate the
++ surviving text by copying to lower indices. For that
++ to be possible, we also have to keep 'clean' advanced
++ in step with x even when we've yet to see any BSNLs.
++ */
++ if (clean < x)
++ buffer[clean++] = buffer[x];
++ else
++ clean++;
++ }
++ if (cpp_state == WantName)
++ buffer[clean] = '\0';
++ else // i.e. malformed
++ inc = nullptr;
++
++ cpp_state = InCode; // hereafter
++ break;
++ }
++
++ case AtStart:
++ // Preprocessor directive?
++ if (buffer[x] == '#') {
++ cpp_state = HadHash;
++ break;
++ }
++ cpp_state = InCode;
++ Q_FALLTHROUGH(); // to handle buffer[x] as such.
++ case InCode:
++ // matching quotes (string literals and character literals)
++ if (buffer[x] == '\'' || buffer[x] == '"') {
++ x = scanPastString(buffer, buffer_len, x, &line_count);
++ // for loop's ++x shall step over the closing quote.
++ }
++ // else: buffer[x] is just some code; move on.
++ break;
++ }
++
++ if (inc) // We were in WantName and found a name.
++ break;
++ #undef SKIP_BSNL
++ }
++ if(x >= buffer_len)
++ break;
++ }
++
++ if(inc) {
++ if(!includes)
++ includes = new SourceFiles;
++ /* QTBUG-72383: Local includes "foo.h" must first be resolved relative to the
++ * sourceDir, only global includes <bar.h> are unique. */
++ SourceFile *dep = try_local ? nullptr : includes->lookupFile(inc);
++ if(!dep) {
++ bool exists = false;
++ QMakeLocalFileName lfn(inc);
++ if(QDir::isRelativePath(lfn.real())) {
++ if(try_local) {
++ QDir sourceDir = findFileInfo(sourceFile).dir();
++ QMakeLocalFileName f(sourceDir.absoluteFilePath(lfn.local()));
++ if(findFileInfo(f).exists()) {
++ lfn = fixPathForFile(f);
++ exists = true;
++ }
++ }
++ if(!exists) { //path lookup
++ for (const QMakeLocalFileName &depdir : std::as_const(depdirs)) {
++ QMakeLocalFileName f(depdir.real() + Option::dir_sep + lfn.real());
++ QFileInfo fi(findFileInfo(f));
++ if(fi.exists() && !fi.isDir()) {
++ lfn = fixPathForFile(f);
++ exists = true;
++ break;
++ }
++ }
++ }
++ if(!exists) { //heuristic lookup
++ lfn = findFileForDep(QMakeLocalFileName(inc), file->file);
++ if((exists = !lfn.isNull()))
++ lfn = fixPathForFile(lfn);
++ }
++ } else {
++ exists = QFile::exists(lfn.real());
++ }
++ if (!lfn.isNull() && !isSystemInclude(lfn.real())) {
++ dep = files->lookupFile(lfn);
++ if(!dep) {
++ dep = new SourceFile;
++ dep->file = lfn;
++ dep->type = QMakeSourceFileInfo::TYPE_C;
++ files->addFile(dep);
++ /* QTBUG-72383: Local includes "foo.h" are keyed by the resolved
++ * path (stored in dep itself), only global includes <bar.h> are
++ * unique keys immediately. */
++ const char *key = try_local ? nullptr : inc;
++ includes->addFile(dep, key, false);
++ }
++ dep->exists = exists;
++ }
++ }
++ if(dep && dep->file != file->file) {
++ dep->included_count++;
++ if(dep->exists) {
++ debug_msg(5, "%s:%d Found dependency to %s", file->file.real().toLatin1().constData(),
++ line_count, dep->file.local().toLatin1().constData());
++ file->deps->addChild(dep);
++ }
++ }
++ }
++ }
++ if(dependencyMode() == Recursive) { //done last because buffer is shared
++ for(int i = 0; i < file->deps->used_nodes; i++) {
++ if(!file->deps->children[i]->deps)
++ findDeps(file->deps->children[i]);
++ }
++ }
++ return true;
++ }
++
++ static bool isCWordChar(char c) {
++ return c == '_'
++ || (c >= 'a' && c <= 'z')
++ || (c >= 'A' && c <= 'Z')
++ || (c >= '0' && c <= '9');
++ }
++
++ bool QMakeSourceFileInfo::findMocs(SourceFile *file)
++ {
++ if(file->moc_checked)
++ return true;
++ files_changed = true;
++ file->moc_checked = true;
++
++ int buffer_len = 0;
++ char *buffer = nullptr;
++ {
++ QFile f(fixPathForFile(file->file, true).local());
++ if (!f.open(QIODevice::ReadOnly))
++ return false; //shouldn't happen
++ const qint64 fs = f.size();
++ buffer = getBuffer(fs);
++ while (int have_read = f.read(buffer + buffer_len, fs - buffer_len))
++ buffer_len += have_read;
++ }
++
++ debug_msg(2, "findMocs: %s", file->file.local().toLatin1().constData());
++ int line_count = 1;
++ enum Keywords {
++ Q_OBJECT_Keyword,
++ Q_GADGET_Keyword,
++ Q_GADGET_EXPORT_Keyword,
++ Q_NAMESPACE_Keyword,
++ Q_NAMESPACE_EXPORT_Keyword,
++
++ NumKeywords
++ };
++ static const char keywords[][19] = {
++ "Q_OBJECT",
++ "Q_GADGET",
++ "Q_GADGET_EXPORT",
++ "Q_NAMESPACE",
++ "Q_NAMESPACE_EXPORT",
++ };
++ static_assert(std::size(keywords) == NumKeywords);
++ bool ignore[NumKeywords] = {};
++ /* qmake ignore Q_GADGET */
++ /* qmake ignore Q_GADGET_EXPORT */
++ /* qmake ignore Q_OBJECT */
++ /* qmake ignore Q_NAMESPACE */
++ /* qmake ignore Q_NAMESPACE_EXPORT */
++ for(int x = 0; x < buffer_len; x++) {
++ #define SKIP_BSNL(pos) skipEscapedLineEnds(buffer, buffer_len, (pos), &line_count)
++ x = SKIP_BSNL(x);
++ if (buffer[x] == '/') {
++ int extralines = 0;
++ int y = skipEscapedLineEnds(buffer, buffer_len, x + 1, &extralines);
++ if (buffer_len > y) {
++ // If comment, advance to the character that ends it:
++ if (buffer[y] == '/') { // C++-style comment
++ line_count += extralines;
++ x = y;
++ do {
++ x = SKIP_BSNL(x + 1);
++ } while (x < buffer_len && !qmake_endOfLine(buffer[x]));
++
++ } else if (buffer[y] == '*') { // C-style comment
++ line_count += extralines;
++ x = SKIP_BSNL(y + 1);
++ for (; x < buffer_len; x = SKIP_BSNL(x + 1)) {
++ if (buffer[x] == 't' || buffer[x] == 'q') { // ignore
++ const char tag[] = "make ignore ";
++ const auto starts_with = [](const char *haystack, const char *needle) {
++ return strncmp(haystack, needle, strlen(needle)) == 0;
++ };
++ const auto is_ignore = [&](const char *keyword) {
++ return buffer_len >= int(x + strlen(tag) + strlen(keyword)) &&
++ starts_with(buffer + x + 1, tag) &&
++ starts_with(buffer + x + 1 + strlen(tag), keyword);
++ };
++ int interest = 0;
++ for (const char *keyword : keywords) {
++ if (is_ignore(keyword)){
++ debug_msg(2, "Mocgen: %s:%d Found \"q%s%s\"",
++ file->file.real().toLatin1().constData(), line_count,
++ tag, keyword);
++ x += static_cast<int>(strlen(tag));
++ x += static_cast<int>(strlen(keyword));
++ ignore[interest] = true;
++ }
++ ++interest;
++ }
++ } else if (buffer[x] == '*') {
++ extralines = 0;
++ y = skipEscapedLineEnds(buffer, buffer_len, x + 1, &extralines);
++ if (buffer_len > y && buffer[y] == '/') {
++ line_count += extralines;
++ x = y;
++ break;
++ }
++ } else if (Option::debug_level && qmake_endOfLine(buffer[x])) {
++ ++line_count;
++ }
++ }
++ }
++ // else: don't update x, buffer[x] is just the division operator.
++ }
++ } else if (buffer[x] == '\'' || buffer[x] == '"') {
++ x = scanPastString(buffer, buffer_len, x, &line_count);
++ // Leaves us on closing quote; for loop's x++ steps us past it.
++ }
++
++ if (x < buffer_len && Option::debug_level && qmake_endOfLine(buffer[x]))
++ ++line_count;
++ if (buffer_len > x + 8 && !isCWordChar(buffer[x])) {
++ int morelines = 0;
++ int y = skipEscapedLineEnds(buffer, buffer_len, x + 1, &morelines);
++ if (buffer[y] == 'Q') {
++ for (int interest = 0; interest < NumKeywords; ++interest) {
++ if (ignore[interest])
++ continue;
++
++ int matchlen = 0, extralines = 0;
++ size_t needle_len = strlen(keywords[interest]);
++ Q_ASSERT(needle_len <= INT_MAX);
++ if (matchWhileUnsplitting(buffer, buffer_len, y,
++ keywords[interest],
++ static_cast<int>(needle_len),
++ &matchlen, &extralines)
++ && y + matchlen < buffer_len
++ && !isCWordChar(buffer[y + matchlen])) {
++ if (Option::debug_level) {
++ buffer[y + matchlen] = '\0';
++ debug_msg(2, "Mocgen: %s:%d Found MOC symbol %s",
++ file->file.real().toLatin1().constData(),
++ line_count + morelines, buffer + y);
++ }
++ file->mocable = true;
++ return true;
++ }
++ }
++ }
++ }
++ #undef SKIP_BSNL
++ }
++ return true;
++ }
++
++ QT_END_NAMESPACE