From 5e7492325a745908e11403b1f729a840c180fc6c Mon Sep 17 00:00:00 2001
From: Edward Welbourne <edward.welbourne@theqtcompany.com>
Date: Tue, 8 Dec 2015 14:50:45 +0100
Subject: [PATCH] qmake: teach moc-detector to handle C++-11 raw strings

As for the #include-parser, the moc-detector's minimal C preprocessor
could be confused by a raw string into ignoring large chunks of code.

Change-Id: Id688e9a1f04628ce75a51a7d15269078c734288e
Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@theqtcompany.com>
---
 qmake/generators/makefiledeps.cpp             | 166 +++++++++---------
 .../tools/qmake/testdata/rawString/object1.h  |   3 +-
 2 files changed, 86 insertions(+), 83 deletions(-)

diff --git a/qmake/generators/makefiledeps.cpp b/qmake/generators/makefiledeps.cpp
index 53e09547492..635a775cb30 100644
--- a/qmake/generators/makefiledeps.cpp
+++ b/qmake/generators/makefiledeps.cpp
@@ -422,6 +422,87 @@ static bool matchWhileUnsplitting(const char *buffer, int buffer_len, int start,
     return true;
 }
 
+/* Advance from an opening quote at buffer[offset] to the matching close quote. */
+static int scanPastString(char *buffer, int buffer_len, int offset, int *lines)
+{
+    // It might be a C++11 raw string.
+    bool israw = false;
+    if (buffer[offset] == '"' && offset > 0) {
+        int explore = offset - 1;
+        while (explore > 0 && buffer[explore] != 'R') {
+            if (buffer[explore] == '8' || buffer[explore] == 'u' || buffer[explore] == 'U') {
+                explore--;
+            } else if (explore > 1 && qmake_endOfLine(buffer[explore])
+                       && buffer[explore - 1] == '\\') {
+                explore -= 2;
+            } else if (explore > 2 && buffer[explore] == '\n'
+                       && buffer[explore - 1] == '\r'
+                       && buffer[explore - 2] == '\\') {
+                explore -= 3;
+            } else {
+                break;
+            }
+        }
+        israw = (buffer[explore] == 'R');
+    }
+
+    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)
@@ -696,75 +777,7 @@ bool QMakeSourceFileInfo::findDeps(SourceFile *file)
                 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;
-                            }
-                        }
+                        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.
@@ -924,19 +937,10 @@ bool QMakeSourceFileInfo::findMocs(SourceFile *file)
                 }
             }
         } else if (buffer[x] == '\'' || buffer[x] == '"') {
-            const char term = buffer[x++];
-            while(x < buffer_len) {
-                if (buffer[x] == term)
-                    break;
-                if (buffer[x] == '\\') {
-                    x+=2;
-                } else {
-                    if (qmake_endOfLine(buffer[x]))
-                        ++line_count;
-                    ++x;
-                }
-            }
+            x = scanPastString(buffer, buffer_len, x, &line_count);
+            // Leaves us on closing quote; for loop's x++ steps us past it.
         }
+
         if (Option::debug_level && qmake_endOfLine(buffer[x]))
             ++line_count;
         if (buffer_len > x + 2 && buffer[x + 1] == 'Q' &&
diff --git a/tests/auto/tools/qmake/testdata/rawString/object1.h b/tests/auto/tools/qmake/testdata/rawString/object1.h
index 07af4229fb1..1435624f7b9 100644
--- a/tests/auto/tools/qmake/testdata/rawString/object1.h
+++ b/tests/auto/tools/qmake/testdata/rawString/object1.h
@@ -31,11 +31,10 @@
 **
 ****************************************************************************/
 
-
+#define rawstring R"blah(lorem " ipsum /*)blah";
 #include <QObject>
 
 class Object1 : public QObject
 {
     Q_OBJECT
 };
-
-- 
GitLab