diff --git a/qmake/generators/makefiledeps.cpp b/qmake/generators/makefiledeps.cpp index 43f368f0bed399ed01df47732257d41b1856fa45..656acdc6ba2036a9ca33013bf36aa1b0ba0ab18f 100644 --- a/qmake/generators/makefiledeps.cpp +++ b/qmake/generators/makefiledeps.cpp @@ -388,6 +388,40 @@ 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 && x < buffer_len; + n++, x = skipEscapedLineEnds(buffer, buffer_len, x + 1, lines)) { + if (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; +} + bool QMakeSourceFileInfo::findDeps(SourceFile *file) { if(file->dep_checked || file->type == TYPE_UNKNOWN) @@ -426,6 +460,18 @@ bool QMakeSourceFileInfo::findDeps(SourceFile *file) 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; for(int x = 0; x < buffer_len; ++x) { bool try_local = true; @@ -505,144 +551,232 @@ bool QMakeSourceFileInfo::findDeps(SourceFile *file) ++line_count; } else if(file->type == QMakeSourceFileInfo::TYPE_QRC) { } else if(file->type == QMakeSourceFileInfo::TYPE_C) { - for(int beginning=1; x < buffer_len; ++x) { + // 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 < buffer_len; ++x) { + x = SKIP_BSNL(x); if (buffer[x] == ' ' || buffer[x] == '\t') { // keep going - } else if (buffer[x] == '/' && x + 1 < buffer_len && - (buffer[x + 1] == '/' || buffer[x + 1] == '*')) { - ++x; - if (buffer[x] == '/') { // C++-style comment - for (; x < buffer_len && !qmake_endOfLine(buffer[x]); ++x) {} // skip - beginning = 1; - } else { // C-style comment + } else if (buffer[x] == '/') { + int extralines = 0; + int y = skipEscapedLineEnds(buffer, buffer_len, x + 1, &extralines); + 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 < buffer_len) { + x = SKIP_BSNL(x); if (buffer[x] == '*') { - if (x + 1 < buffer_len && buffer[x + 1] == '/') { - ++x; // skip '*'; for loop skips '/'. + 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; - beginning = 1; + 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; - // preprocessor directive - if (beginning && buffer[x] == '#') { - // Advance to start of preprocessing directive - while (++x < buffer_len - && (buffer[x] == ' ' || buffer[x] == '\t')) {} // skip - - if (qmake_endOfLine(buffer[x])) { - ++line_count; - beginning = 1; - continue; + 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; } - // quoted strings - if (buffer[x] == '\'' || buffer[x] == '"') { - // It might be a C++11 raw string. - bool israw = false; - if (buffer[x] == '"' && x > 0) { - int y = x; - while (--y > 0 && (buffer[y] == '8' || buffer[y] == 'u' || buffer[y] == 'U')) {} // skip - israw = (buffer[y] == 'R'); - } - if (israw) { - x++; - const char *const delim = buffer + x; - while (x < buffer_len && buffer[x] != '(') - x++; + case WantName: + { + char term = buffer[x]; + if (term == '<') { + try_local = false; + term = '>'; + } else if (term != '"') { /* - Not checking correctness (trust real compiler to do that): - - no controls, spaces, '(', ')', '\\' or (presumably) '"' in delim; - - at most 16 bytes in delim - */ - - const int delimlen = buffer + x - delim; - while (++x < buffer_len - && (buffer[x] != ')' - || (delimlen > 0 && - strncmp(buffer + x + 1, delim, delimlen)) - || buffer[x + 1 + delimlen] != '"')) {} // skip - // buffer[x] is ')' - x += 1 + delimlen; // 1 for ')', then delim - // buffer[x] is '"' - } else { - const char term = buffer[x]; - while (++x < buffer_len && buffer[x] != term) { - if (buffer[x] == '\\') - ++x; - else if (qmake_endOfLine(buffer[x])) - ++line_count; + 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++; } - // for loop's ++x shall step over the closing quote. - } - beginning = 0; - } - if(x >= buffer_len) - break; + if (cpp_state == WantName) + buffer[clean] = '\0'; + else // i.e. malformed + inc = 0; - // Got a preprocessor directive - const char *const keyword = buffer + x; - for (; - x < buffer_len && buffer[x] >= 'a' && buffer[x] <= 'z'; - x++) {} // skip over identifier - int keyword_len = buffer + x - keyword; - for (; - x < buffer_len && (buffer[x] == ' ' || buffer[x] == '\t'); - x++) {} // skip spaces after keyword - - /* Keyword with nothing after it, e.g. #endif: not interesting. */ - if (qmake_endOfLine(buffer[x])) - keyword_len = 0; - - if((keyword_len == 7 && !strncmp(keyword, "include", 7)) // C & Obj-C - || (keyword_len == 6 && !strncmp(keyword, "import", 6))) { // Obj-C - char term = buffer[x]; - if(term == '<') { - try_local = false; - term = '>'; - } else if(term != '"') { //wtf? - continue; + cpp_state = InCode; // hereafter + break; } - x++; - inc = buffer + x; - for (; - buffer[x] != term && !qmake_endOfLine(buffer[x]); - ++x) {} // skip until end of include name - buffer[x] = '\0'; - } else if (buffer[x] == '\'' || buffer[x] == '"') { - const char term = buffer[x++]; - while(x < buffer_len) { - if (buffer[x] == term) + + case AtStart: + // Preprocessor directive? + if (buffer[x] == '#') { + cpp_state = HadHash; break; - if (buffer[x] == '\\') { - x+=2; - } else { - if (qmake_endOfLine(buffer[x])) - ++line_count; - ++x; } + cpp_state = InCode; + // ... and fall through to handle buffer[x] as such. + case InCode: + // matching quotes (string literals and character literals) + if (buffer[x] == '\'' || buffer[x] == '"') { + // It might be a C++11 raw string. + bool israw = false; + if (buffer[x] == '"' && x > 0) { + int y = x - 1; + while (y > 0 && buffer[y] != 'R') { + if (buffer[y] == '8' || buffer[y] == 'u' || buffer[y] == 'U') + y--; + else if (y > 1 && qmake_endOfLine(buffer[y]) + && buffer[y - 1] == '\\') + y -= 2; + else if (y > 2 && buffer[y] == '\n' + && buffer[y - 1] == '\r' + && buffer[y - 2] == '\\') + y -= 3; + else + break; + } + israw = (buffer[y] == 'R'); + } + if (israw) { + x = SKIP_BSNL(x + 1); + const char *const delim = buffer + x; + int clean = x; + while (x < buffer_len && buffer[x] != '(') { + if (clean < x) + buffer[clean++] = buffer[x]; + else + clean++; + + x = SKIP_BSNL(x + 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 ((x = SKIP_BSNL(x + 1)) < buffer_len + && (buffer[x] != ')' + || (delimlen > 0 && + !matchWhileUnsplitting(buffer, buffer_len, + x + 1, delim, delimlen, + &matchlen, &extralines)) + || buffer[x + 1 + matchlen] != '"')) { + // skip, but keep track of lines + if (qmake_endOfLine(buffer[x])) + ++line_count; + extralines = 0; + } + line_count += extralines; // from the match + // buffer[x] is ')' + x += 1 + matchlen; // 1 for ')', then delim + // buffer[x] is '"' + } else { + const char term = buffer[x]; + while (++x < buffer_len && buffer[x] != term) { + if (buffer[x] == '\\') + ++x; + else if (qmake_endOfLine(buffer[x])) + ++line_count; + } + } + // for loop's ++x shall step over the closing quote. + } + // else: buffer[x] is just some code; move on. + break; } - } else { - --x; + + if (inc) // We were in WantName and found a name. + break; +#undef SKIP_BSNL } + if(x >= buffer_len) + break; } if(inc) { diff --git a/tests/auto/tools/qmake/testdata/findDeps/findDeps.pro b/tests/auto/tools/qmake/testdata/findDeps/findDeps.pro index 2713296f5b17995f151d0da580de5cbc029fb57f..afb3f65297b3af9439ac9a05af766e8d96ca0e95 100644 --- a/tests/auto/tools/qmake/testdata/findDeps/findDeps.pro +++ b/tests/auto/tools/qmake/testdata/findDeps/findDeps.pro @@ -1,4 +1,5 @@ DESTDIR = ./ +gcc: QMAKE_CXXFLAGS += -Wno-comment HEADERS += object1.h \ object2.h \ @@ -8,5 +9,7 @@ HEADERS += object1.h \ object6.h \ object7.h \ object8.h \ - object9.h + object9.h \ + objecta.h \ + objectf.h SOURCES += main.cpp needed.cpp diff --git a/tests/auto/tools/qmake/testdata/findDeps/main.cpp b/tests/auto/tools/qmake/testdata/findDeps/main.cpp index d5dd5b81d33d28c75fb82e7fd14269e7189deb3b..3f4f5def3c1e3daa70ee89dbadcdde4454a20f23 100644 --- a/tests/auto/tools/qmake/testdata/findDeps/main.cpp +++ b/tests/auto/tools/qmake/testdata/findDeps/main.cpp @@ -39,23 +39,52 @@ static const char text[] = "lorem ""ipsum /*"; #include <moc_object1.cpp> -/**/ #include <moc_object2.cpp> -/**//**/ #include <moc_object3.cpp> -/*'*/ #include <moc_object4.cpp> -/* -*/ #include <moc_object5.cpp> - -// +/**/ #include "\ +moc_object2.cpp\ +" +/**//**/ #include <moc_\ +o\ +b\ +j\ +e\ +c\ +t\ +3\ +.cpp> +/*'"*/ #include <moc_object4.cpp> +/*"' +*/ #include <moc_object5.cpp> /* +#include "missing.cpp" +*/// a backslash newline does make the next line part of this comment \ +/* so this text is in last line's C++-style comment, not a C-comment ! #include <moc_object6.cpp> +#if 0 +#pragma "ignore me" '&' L"me" +#line 4321 "main.cpp" more /* preprocessing */ tokens +#endif static void function1(); -#include <moc_object7.cpp> +#include/* every comment +gets replaced (in phase 3) by a single +space */<moc_object7.cpp> static void function2(); /**/ -#include <moc_object8.cpp> +#include \ +<moc_object8.cpp> static void function3(); // #include <moc_object9.cpp> +/* backslash-newline elimination happens in phase 2 *\ +/ # /* and that's valid here, too. *\ +/ include/* and, of course, here *\ +/<moc_objecta.cpp>// while we're here, ... \ +#include "needed.cpp" int main () { extern int needed(void); return needed(); } + +/* + Deliberately end file in a #include, with nothing after it but the mandatory + (unescaped) newline at the end of every source file. +*/ +#include "moc_objectf.cpp" diff --git a/tests/auto/tools/qmake/testdata/findDeps/objecta.h b/tests/auto/tools/qmake/testdata/findDeps/objecta.h new file mode 100644 index 0000000000000000000000000000000000000000..b9813b9e18f50967561107daf667958156546253 --- /dev/null +++ b/tests/auto/tools/qmake/testdata/findDeps/objecta.h @@ -0,0 +1,41 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include <QObject> + +class ObjectA : public QObject +{ + Q_OBJECT +}; + diff --git a/tests/auto/tools/qmake/testdata/findDeps/objectf.h b/tests/auto/tools/qmake/testdata/findDeps/objectf.h new file mode 100644 index 0000000000000000000000000000000000000000..5dead815a087db90058577c93fdde3a10335a5e9 --- /dev/null +++ b/tests/auto/tools/qmake/testdata/findDeps/objectf.h @@ -0,0 +1,41 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include <QObject> + +class ObjectF : public QObject +{ + Q_OBJECT +}; + diff --git a/tests/auto/tools/qmake/testdata/rawString/main.cpp b/tests/auto/tools/qmake/testdata/rawString/main.cpp index fb7008a1a9256bdd4a9506a4e72dc8082a51ed48..604986666a50d7bbbdc5eda4a0a4196a441fde89 100644 --- a/tests/auto/tools/qmake/testdata/rawString/main.cpp +++ b/tests/auto/tools/qmake/testdata/rawString/main.cpp @@ -31,7 +31,8 @@ ** ****************************************************************************/ -static const char raw[] = R"blah(lorem " ipsum /*)blah"; +static const char raw[] = R"blah(lorem " ipsum /*)blah"\ +; #include <moc_object1.cpp> int main () { return 0; }