From 20189574f68c93276cfd856d0ed4f201eeb31db7 Mon Sep 17 00:00:00 2001
From: Kai Koehne <kai.koehne@nokia.com>
Date: Fri, 25 Feb 2011 11:57:34 +0100
Subject: [PATCH] QmlJsDebugger: Handle breakpoints in qrc:// resources more
 gracefully

Reviewed-by: Christiaan Janssen
---
 .../qml/qmljsdebugger/jsdebuggeragent.cpp     |  32 ++--
 src/plugins/debugger/qml/qmlengine.cpp        | 153 ++++++++++--------
 src/plugins/debugger/qml/qmlengine.h          |   3 +-
 3 files changed, 103 insertions(+), 85 deletions(-)

diff --git a/share/qtcreator/qml/qmljsdebugger/jsdebuggeragent.cpp b/share/qtcreator/qml/qmljsdebugger/jsdebuggeragent.cpp
index 3c200d797d7..7e231d91315 100644
--- a/share/qtcreator/qml/qmljsdebugger/jsdebuggeragent.cpp
+++ b/share/qtcreator/qml/qmljsdebugger/jsdebuggeragent.cpp
@@ -46,7 +46,6 @@
 #include <QtCore/qdebug.h>
 #include <QtCore/qcoreapplication.h>
 #include <QtCore/qset.h>
-#include <QtCore/qfileinfo.h>
 #include <QtCore/qurl.h>
 #include <QtScript/qscriptcontextinfo.h>
 #include <QtScript/qscriptengine.h>
@@ -82,19 +81,19 @@ QDataStream &operator<<(QDataStream &s, const JSAgentWatchData &data)
 struct JSAgentStackData
 {
     QByteArray functionName;
-    QByteArray fileName;
+    QByteArray fileUrl;
     qint32 lineNumber;
 };
 
 QDataStream &operator<<(QDataStream &s, const JSAgentStackData &data)
 {
-    return s << data.functionName << data.fileName << data.lineNumber;
+    return s << data.functionName << data.fileUrl << data.lineNumber;
 }
 
 struct JSAgentBreakpointData
 {
     QByteArray functionName;
-    QByteArray fileName;
+    QByteArray fileUrl;
     qint32 lineNumber;
 };
 
@@ -102,22 +101,22 @@ typedef QSet<JSAgentBreakpointData> JSAgentBreakpoints;
 
 QDataStream &operator<<(QDataStream &s, const JSAgentBreakpointData &data)
 {
-    return s << data.functionName << data.fileName << data.lineNumber;
+    return s << data.functionName << data.fileUrl << data.lineNumber;
 }
 
 QDataStream &operator>>(QDataStream &s, JSAgentBreakpointData &data)
 {
-    return s >> data.functionName >> data.fileName >> data.lineNumber;
+    return s >> data.functionName >> data.fileUrl >> data.lineNumber;
 }
 
 bool operator==(const JSAgentBreakpointData &b1, const JSAgentBreakpointData &b2)
 {
-    return b1.lineNumber == b2.lineNumber && b1.fileName == b2.fileName;
+    return b1.lineNumber == b2.lineNumber && b1.fileUrl == b2.fileUrl;
 }
 
 uint qHash(const JSAgentBreakpointData &b)
 {
-    return b.lineNumber ^ qHash(b.fileName);
+    return b.lineNumber ^ qHash(b.fileUrl);
 }
 
 class JSDebuggerAgentPrivate
@@ -316,7 +315,7 @@ void JSDebuggerAgent::scriptLoad(qint64 id, const QString &program,
                                  const QString &fileName, int)
 {
     Q_UNUSED(program);
-    d->filenames.insert(id, QUrl(fileName).toLocalFile());
+    d->filenames.insert(id, fileName);
 }
 
 /*!
@@ -368,6 +367,12 @@ void JSDebuggerAgent::positionChange(qint64 scriptId, int lineNumber, int column
     d->positionChange(scriptId, lineNumber, columnNumber);
 }
 
+QString fileName(const QString &fileUrl)
+{
+    int lastDelimiterPos = fileUrl.lastIndexOf(QLatin1Char('/'));
+    return fileUrl.mid(lastDelimiterPos, fileUrl.size() - lastDelimiterPos);
+}
+
 void JSDebuggerAgentPrivate::positionChange(qint64 scriptId, int lineNumber, int columnNumber)
 {
     Q_UNUSED(columnNumber);
@@ -382,7 +387,7 @@ void JSDebuggerAgentPrivate::positionChange(qint64 scriptId, int lineNumber, int
         QScriptContextInfo info(ctx);
         if (it == filenames.constEnd()) {
             // It is possible that the scripts are loaded before the agent is attached
-            QString filename = QUrl(info.fileName()).toLocalFile();
+            QString filename = info.fileName();
 
             JSAgentStackData frame;
             frame.functionName = info.functionName().toUtf8();
@@ -392,7 +397,7 @@ void JSDebuggerAgentPrivate::positionChange(qint64 scriptId, int lineNumber, int
         }
 
         const QString filePath = it->toUtf8();
-        JSAgentBreakpoints bps = fileNameToBreakpoints.values(QFileInfo(filePath).fileName()).toSet();
+        JSAgentBreakpoints bps = fileNameToBreakpoints.values(fileName(filePath)).toSet();
 
         foreach (const JSAgentBreakpointData &bp, bps) {
             if (bp.lineNumber == lineNumber) {
@@ -477,7 +482,7 @@ void JSDebuggerAgentPrivate::messageReceived(const QByteArray &message)
 
         fileNameToBreakpoints.clear();
         foreach (const JSAgentBreakpointData &bp, breakpoints) {
-            fileNameToBreakpoints.insert(QFileInfo(bp.fileName).fileName(), bp);
+            fileNameToBreakpoints.insert(fileName(bp.fileUrl), bp);
         }
 
         //qDebug() << "BREAKPOINTS";
@@ -621,7 +626,8 @@ void JSDebuggerAgentPrivate::stopped()
         // if the line number is unknown, fallback to the function line number
         if (frame.lineNumber == -1)
             frame.lineNumber = info.functionStartLineNumber();
-        frame.fileName = QUrl(info.fileName()).toLocalFile().toUtf8();
+
+        frame.fileUrl = info.fileName().toUtf8();
         backtrace.append(frame);
     }
     QList<JSAgentWatchData> watches;
diff --git a/src/plugins/debugger/qml/qmlengine.cpp b/src/plugins/debugger/qml/qmlengine.cpp
index cb52e02aa58..a5958ac2ef0 100644
--- a/src/plugins/debugger/qml/qmlengine.cpp
+++ b/src/plugins/debugger/qml/qmlengine.cpp
@@ -57,6 +57,7 @@
 #include <utils/environment.h>
 #include <utils/abstractprocess.h>
 #include <utils/qtcassert.h>
+#include <utils/fileinprojectfinder.h>
 
 #include <coreplugin/icore.h>
 #include <coreplugin/helpmanager.h>
@@ -93,31 +94,49 @@ namespace Internal {
 struct JSAgentBreakpointData
 {
     QByteArray functionName;
-    QByteArray fileName;
+    QByteArray fileUrl;
+    qint32 lineNumber;
+};
+
+struct JSAgentStackData
+{
+    QByteArray functionName;
+    QByteArray fileUrl;
     qint32 lineNumber;
 };
 
 uint qHash(const JSAgentBreakpointData &b)
 {
-    return b.lineNumber ^ qHash(b.fileName);
+    return b.lineNumber ^ qHash(b.fileUrl);
 }
 
 QDataStream &operator<<(QDataStream &s, const JSAgentBreakpointData &data)
 {
-    return s << data.functionName << data.fileName << data.lineNumber;
+    return s << data.functionName << data.fileUrl << data.lineNumber;
+}
+
+QDataStream &operator<<(QDataStream &s, const JSAgentStackData &data)
+{
+    return s << data.functionName << data.fileUrl << data.lineNumber;
 }
 
 QDataStream &operator>>(QDataStream &s, JSAgentBreakpointData &data)
 {
-    return s >> data.functionName >> data.fileName >> data.lineNumber;
+    return s >> data.functionName >> data.fileUrl >> data.lineNumber;
+}
+
+QDataStream &operator>>(QDataStream &s, JSAgentStackData &data)
+{
+    return s >> data.functionName >> data.fileUrl >> data.lineNumber;
 }
 
 bool operator==(const JSAgentBreakpointData &b1, const JSAgentBreakpointData &b2)
 {
-    return b1.lineNumber == b2.lineNumber && b1.fileName == b2.fileName;
+    return b1.lineNumber == b2.lineNumber && b1.fileUrl == b2.fileUrl;
 }
 
 typedef QSet<JSAgentBreakpointData> JSAgentBreakpoints;
+typedef QList<JSAgentStackData> JSAgentStackFrames;
 
 
 static QDataStream &operator>>(QDataStream &s, WatchData &data)
@@ -136,19 +155,6 @@ static QDataStream &operator>>(QDataStream &s, WatchData &data)
     return s;
 }
 
-static QDataStream &operator>>(QDataStream &s, StackFrame &frame)
-{
-    frame = StackFrame();
-    QByteArray function;
-    QByteArray file;
-    s >> function >> file >> frame.line;
-    frame.function = QString::fromUtf8(function);
-    frame.file = QString::fromUtf8(file);
-    frame.usable = QFileInfo(frame.file).isReadable();
-    return s;
-}
-
-
 class QmlEnginePrivate
 {
 public:
@@ -159,6 +165,7 @@ private:
     int m_ping;
     QmlAdapter m_adapter;
     ApplicationLauncher m_applicationLauncher;
+    Utils::FileInProjectFinder fileFinder;
 };
 
 QmlEnginePrivate::QmlEnginePrivate(QmlEngine *q)
@@ -183,14 +190,6 @@ QmlEngine::QmlEngine(const DebuggerStartParameters &startParameters,
 QmlEngine::~QmlEngine()
 {}
 
-void QmlEngine::gotoLocation(const Location &loc0)
-{
-    Location loc = loc0;
-    if (isShadowBuildProject())
-        loc.setFileName(fromShadowBuildFilename(loc0.fileName()));
-    DebuggerEngine::gotoLocation(loc);
-}
-
 void QmlEngine::setupInferior()
 {
     QTC_ASSERT(state() == InferiorSetupRequested, qDebug() << state());
@@ -576,18 +575,8 @@ void QmlEngine::attemptBreakpointSynchronization()
             if (handler->state(id) == BreakpointInsertRequested) {
                 handler->notifyBreakpointInsertProceeding(id);
             }
-            QString processedFilename = handler->fileName(id);
-#ifdef Q_OS_MACX
-            // Qt Quick Applications by default copy the qml directory
-            // to buildDir()/X.app/Contents/Resources
-            const QString applicationBundleDir
-                    = QFileInfo(startParameters().executable).absolutePath() + "/../..";
-            processedFilename = mangleFilenamePaths(handler->fileName(id), startParameters().projectDir, applicationBundleDir + "/Contents/Resources");
-#endif
-            if (isShadowBuildProject())
-                processedFilename = toShadowBuildFilename(processedFilename);
             JSAgentBreakpointData bp;
-            bp.fileName = processedFilename.toUtf8();
+            bp.fileUrl = QUrl::fromLocalFile(handler->fileName(id)).toString().toUtf8();
             bp.lineNumber = handler->lineNumber(id);
             bp.functionName = handler->functionName(id).toUtf8();
             breakpoints.insert(bp);
@@ -606,7 +595,7 @@ void QmlEngine::attemptBreakpointSynchronization()
     QStringList breakPointsStr;
     foreach (const JSAgentBreakpointData &bp, breakpoints) {
         breakPointsStr << QString("('%1' '%2' %3)").arg(QString(bp.functionName),
-                                  QString(bp.fileName), QString::number(bp.lineNumber));
+                                  QString(bp.fileUrl), QString::number(bp.lineNumber));
     }
     logMessage(LogSend, QString("%1 [%2]").arg(QString(cmd), breakPointsStr.join(", ")));
 
@@ -754,6 +743,38 @@ unsigned QmlEngine::debuggerCapabilities() const
         | AddWatcherCapability;*/
 }
 
+QString QmlEngine::toFileInProject(const QString &fileUrl)
+{
+    if (fileUrl.isEmpty())
+        return fileUrl;
+
+    const QString path = QUrl(fileUrl).path();
+
+    // Try to find shadow-build file in source dir first
+    if (!QUrl(fileUrl).toLocalFile().isEmpty()
+            && isShadowBuildProject()) {
+        const QString sourcePath = fromShadowBuildFilename(path);
+        if (QFileInfo(sourcePath).exists())
+            return sourcePath;
+    }
+
+    // Try whether file is absolute & exists
+    if (QFileInfo(path).isAbsolute()
+            && QFileInfo(path).exists()) {
+        return path;
+    }
+
+    if (d->fileFinder.projectDirectory().isEmpty())
+        d->fileFinder.setProjectDirectory(startParameters().projectDir);
+
+    // Try to find file with biggest common path in source directory
+    bool fileFound = false;
+    QString fileInProject = d->fileFinder.findFile(path, &fileFound);
+    if (fileFound)
+        return fileInProject;
+    return fileUrl;
+}
+
 void QmlEngine::messageReceived(const QByteArray &message)
 {
     QByteArray rwData = message;
@@ -769,7 +790,7 @@ void QmlEngine::messageReceived(const QByteArray &message)
 
         QString logString = QString::fromLatin1(command);
 
-        StackFrames stackFrames;
+        JSAgentStackFrames stackFrames;
         QList<WatchData> watches;
         QList<WatchData> locals;
         stream >> stackFrames >> watches >> locals;
@@ -777,12 +798,20 @@ void QmlEngine::messageReceived(const QByteArray &message)
         logString += QString::fromLatin1(" (%1 stack frames) (%2 watches)  (%3 locals)").
                      arg(stackFrames.size()).arg(watches.size()).arg(locals.size());
 
-        for (int i = 0; i != stackFrames.size(); ++i)
-            stackFrames[i].level = i + 1;
+        StackFrames ideStackFrames;
+        for (int i = 0; i != stackFrames.size(); ++i) {
+            StackFrame frame;
+            frame.line = stackFrames.at(i).lineNumber;
+            frame.function = stackFrames.at(i).functionName;
+            frame.file = toFileInProject(stackFrames.at(i).fileUrl);
+            frame.usable = QFileInfo(frame.file).isReadable();
+            frame.level = i + 1;
+            ideStackFrames << frame;
+        }
 
-        if (stackFrames.size() && stackFrames.back().function == "<global>")
-            stackFrames.takeLast();
-        stackHandler()->setFrames(stackFrames);
+        if (ideStackFrames.size() && ideStackFrames.back().function == "<global>")
+            ideStackFrames.takeLast();
+        stackHandler()->setFrames(ideStackFrames);
 
         watchHandler()->beginCycle();
         bool needPing = false;
@@ -807,10 +836,11 @@ void QmlEngine::messageReceived(const QByteArray &message)
             }
         }
 
-        if (needPing)
+        if (needPing) {
             sendPing();
-        else
+        } else {
             watchHandler()->endCycle();
+        }
 
         bool becauseOfException;
         stream >> becauseOfException;
@@ -829,7 +859,7 @@ void QmlEngine::messageReceived(const QByteArray &message)
                 ? tr("<p>An uncaught exception occurred:</p><p>%1</p>")
                     .arg(Qt::escape(error))
                 : tr("<p>An uncaught exception occurred in <i>%1</i>:</p><p>%2</p>")
-                    .arg(stackFrames.value(0).file, Qt::escape(error));
+                    .arg(stackFrames.value(0).fileUrl, Qt::escape(error));
             showMessageBox(QMessageBox::Information, tr("Uncaught Exception"), msg);
         } else {
             //
@@ -839,14 +869,10 @@ void QmlEngine::messageReceived(const QByteArray &message)
             QString function;
             int line = -1;
 
-            if (!stackFrames.isEmpty()) {
-                file = stackFrames.at(0).file;
-                line = stackFrames.at(0).line;
-                function = stackFrames.at(0).function;
-
-                if (isShadowBuildProject()) {
-                    file = fromShadowBuildFilename(file);
-                }
+            if (!ideStackFrames.isEmpty()) {
+                file = ideStackFrames.at(0).file;
+                line = ideStackFrames.at(0).line;
+                function = ideStackFrames.at(0).function;
             }
 
             BreakHandler *handler = breakHandler();
@@ -865,8 +891,8 @@ void QmlEngine::messageReceived(const QByteArray &message)
             logMessage(LogReceive, logString);
         }
 
-        if (!stackFrames.isEmpty())
-            gotoLocation(stackFrames.value(0));
+        if (!ideStackFrames.isEmpty())
+            gotoLocation(ideStackFrames.value(0));
 
     } else if (command == "RESULT") {
         WatchData data;
@@ -977,19 +1003,6 @@ QString QmlEngine::qmlImportPath() const
     return startParameters().environment.value("QML_IMPORT_PATH");
 }
 
-QString QmlEngine::toShadowBuildFilename(const QString &filename) const
-{
-    QString newFilename = filename;
-    QString importPath = qmlImportPath();
-
-    newFilename = mangleFilenamePaths(filename, startParameters().projectDir, startParameters().projectBuildDir);
-    if (newFilename == filename && !importPath.isEmpty()) {
-        newFilename = mangleFilenamePaths(filename, startParameters().projectDir, importPath);
-    }
-
-    return newFilename;
-}
-
 QString QmlEngine::mangleFilenamePaths(const QString &filename,
     const QString &oldBasePath, const QString &newBasePath) const
 {
diff --git a/src/plugins/debugger/qml/qmlengine.h b/src/plugins/debugger/qml/qmlengine.h
index d230a927a4e..b9c23fca116 100644
--- a/src/plugins/debugger/qml/qmlengine.h
+++ b/src/plugins/debugger/qml/qmlengine.h
@@ -58,7 +58,6 @@ public:
     void handleRemoteSetupDone(int gdbServerPort, int qmlPort);
     void handleRemoteSetupFailed(const QString &message);
 
-    void gotoLocation(const Location &location);
     bool canDisplayTooltip() const;
 
     void showMessage(const QString &msg, int channel = LogDebug,
@@ -145,7 +144,6 @@ private:
     QString fromShadowBuildFilename(const QString &filename) const;
     QString mangleFilenamePaths(const QString &filename,
         const QString &oldBasePath, const QString &newBasePath) const;
-    QString toShadowBuildFilename(const QString &filename) const;
     QString qmlImportPath() const;
 
     enum LogDirection {
@@ -153,6 +151,7 @@ private:
         LogReceive
     };
     void logMessage(LogDirection direction, const QString &str);
+    QString toFileInProject(const QString &file);
 
 private:
     friend class QmlCppEngine;
-- 
GitLab