From ee3dde9cee445b59e5c41350f2da219e8e2da10b Mon Sep 17 00:00:00 2001
From: Friedemann Kleint <Friedemann.Kleint@nokia.com>
Date: Tue, 15 Dec 2009 11:02:09 +0100
Subject: [PATCH] CDB: Case-normalize file names returned by CDB

Fixing various problems with breakpoints and opened files caused by CDB
returning lower-case file names.
Task-number: QTCREATORBUG-438
Reviewed-by: hjk <qtc-committer@nokia.com>
Acked-by: mariusSO <qt-info@nokia.com>
---
 src/plugins/debugger/cdb/cdb.pri              |   1 +
 src/plugins/debugger/cdb/cdbbreakpoint.cpp    | 110 ++++++++++++++++--
 src/plugins/debugger/cdb/cdbbreakpoint.h      |   3 +-
 src/plugins/debugger/cdb/cdbdebugengine.cpp   |   2 +
 .../debugger/cdb/cdbstacktracecontext.cpp     |   2 +-
 5 files changed, 107 insertions(+), 11 deletions(-)

diff --git a/src/plugins/debugger/cdb/cdb.pri b/src/plugins/debugger/cdb/cdb.pri
index a1ab1d4c4e6..6ac5997efa9 100644
--- a/src/plugins/debugger/cdb/cdb.pri
+++ b/src/plugins/debugger/cdb/cdb.pri
@@ -62,6 +62,7 @@ SOURCES += \
 
 FORMS += $$PWD/cdboptionspagewidget.ui
 
+LIBS+=-lpsapi
 } else {
    message("Debugging Tools for Windows could not be found in $$CDB_PATH")
 } # exists($$CDB_PATH)
diff --git a/src/plugins/debugger/cdb/cdbbreakpoint.cpp b/src/plugins/debugger/cdb/cdbbreakpoint.cpp
index 40bec69fae0..4393c6a3612 100644
--- a/src/plugins/debugger/cdb/cdbbreakpoint.cpp
+++ b/src/plugins/debugger/cdb/cdbbreakpoint.cpp
@@ -37,6 +37,8 @@
 #include <QtCore/QDebug>
 #include <QtCore/QMap>
 
+#include <psapi.h>
+
 enum { debugBP = 0 };
 
 namespace Debugger {
@@ -215,16 +217,106 @@ bool CDBBreakPoint::add(CIDebugControl* debugControl,
     return true;
 }
 
-// Make sure file can be found in editor manager and text markers
-// Use '/' and capitalize drive letter
-QString CDBBreakPoint::canonicalSourceFile(const QString &f)
+// Helper for normalizing file names:
+// Map the device paths in  a file name to back to drive letters
+// "/Device/HarddiskVolume1/file.cpp" -> "C:/file.cpp"
+
+static bool mapDeviceToDriveLetter(QString *s)
 {
-    if (f.isEmpty())
+    enum { bufSize = 512 };
+    // Retrieve drive letters and get their device names.
+    // Do not cache as it may change due to removable/network drives.
+    TCHAR driveLetters[bufSize];
+    if (!GetLogicalDriveStrings(bufSize-1, driveLetters))
+        return false;
+
+    TCHAR driveName[MAX_PATH];
+    TCHAR szDrive[3] = TEXT(" :");
+    for (const TCHAR *driveLetter = driveLetters; *driveLetter; driveLetter++) {
+        szDrive[0] = *driveLetter; // Look up each device name
+        if (QueryDosDevice(szDrive, driveName, MAX_PATH)) {
+            const QString deviceName = QString::fromUtf16(driveName);
+            if (s->startsWith(deviceName)) {
+                s->replace(0, deviceName.size(), QString::fromUtf16(szDrive));
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+// Helper for normalizing file names:
+// Determine normalized case of a Windows file name (camelcase.cpp -> CamelCase.cpp)
+// as the debugger reports lower case file names.
+// Restriction: File needs to exists and be non-empty and will be to be opened/mapped.
+// This is the MSDN-recommended way of doing that. The result should be cached.
+
+static inline QString normalizeFileNameCaseHelper(const QString &f)
+{
+    HANDLE hFile = CreateFile(f.utf16(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
+    if(hFile == INVALID_HANDLE_VALUE)
         return f;
-    QString rc = QDir::fromNativeSeparators(f);
-    if (rc.size() > 2 && rc.at(1) == QLatin1Char(':'))
-        rc[0] = rc.at(0).toUpper();
-    return rc;
+    // Get the file size. We need a non-empty file to map it.
+    DWORD dwFileSizeHi = 0;
+    DWORD dwFileSizeLo = GetFileSize(hFile, &dwFileSizeHi);
+    if (dwFileSizeLo == 0 && dwFileSizeHi == 0) {
+        CloseHandle(hFile);
+        return f;
+    }
+    // Create a file mapping object.
+    HANDLE hFileMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 1, NULL);
+    if (!hFileMap)  {
+        CloseHandle(hFile);
+        return f;
+    }
+
+    // Create a file mapping to get the file name.
+    void* pMem = MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 1);
+    if (!pMem) {
+        CloseHandle(hFileMap);
+        CloseHandle(hFile);
+        return f;
+    }
+
+    QString rc;
+    WCHAR pszFilename[MAX_PATH];
+    pszFilename[0] = 0;
+    // Get a file name of the form "/Device/HarddiskVolume1/file.cpp"
+    if (GetMappedFileName (GetCurrentProcess(), pMem, pszFilename, MAX_PATH)) {
+        rc = QString::fromUtf16(pszFilename);
+        if (!mapDeviceToDriveLetter(&rc))
+            rc.clear();
+    }
+
+    UnmapViewOfFile(pMem);
+    CloseHandle(hFileMap);
+    CloseHandle(hFile);
+    return rc.isEmpty() ? f : rc;
+}
+
+// Make sure file can be found in editor manager and text markers
+// Use '/', correct case and capitalize drive letter. Use a cache.
+
+typedef QHash<QString, QString> NormalizedFileCache;
+Q_GLOBAL_STATIC(NormalizedFileCache, normalizedFileNameCache)
+
+QString CDBBreakPoint::normalizeFileName(const QString &f)
+{
+    QTC_ASSERT(!f.isEmpty(), return f)
+    const NormalizedFileCache::const_iterator it = normalizedFileNameCache()->constFind(f);
+    if (it != normalizedFileNameCache()->constEnd())
+        return it.value();
+    QString normalizedName = QDir::fromNativeSeparators(normalizeFileNameCaseHelper(f));
+    // Upcase drive letter for consistency even if case mapping fails.
+    if (normalizedName.size() > 2 && normalizedName.at(1) == QLatin1Char(':'))
+        normalizedName[0] = normalizedName.at(0).toUpper();
+    normalizedFileNameCache()->insert(f, normalizedName);
+    return f;
+}
+
+void CDBBreakPoint::clearNormalizeFileNameCache()
+{
+    normalizedFileNameCache()->clear();
 }
 
 bool CDBBreakPoint::retrieve(CIDebugBreakpoint *ibp, QString *errorMessage)
@@ -267,7 +359,7 @@ bool CDBBreakPoint::parseExpression(const QString &expr)
         conditionPos = expr.indexOf(sourceFileQuote, colonPos + 1);
         if (conditionPos == -1)
             return false;
-        fileName = canonicalSourceFile(expr.mid(1, colonPos - 1));
+        fileName = normalizeFileName(expr.mid(1, colonPos - 1));
         const QString lineNumberS = expr.mid(colonPos + 1, conditionPos - colonPos - 1);
         bool lineNumberOk = false;
         lineNumber = lineNumberS.toInt(&lineNumberOk);
diff --git a/src/plugins/debugger/cdb/cdbbreakpoint.h b/src/plugins/debugger/cdb/cdbbreakpoint.h
index 7b1379617e5..3a1e8e09b5f 100644
--- a/src/plugins/debugger/cdb/cdbbreakpoint.h
+++ b/src/plugins/debugger/cdb/cdbbreakpoint.h
@@ -80,7 +80,8 @@ struct CDBBreakPoint
                                        QString *errorMessage, QStringList *warnings);
 
     // Return a 'canonical' file (using '/' and capitalized drive letter)
-    static QString canonicalSourceFile(const QString &f);
+    static QString normalizeFileName(const QString &f);
+    static void clearNormalizeFileNameCache();
 
     QString fileName;       // short name of source file
     QString condition;      // condition associated with breakpoint
diff --git a/src/plugins/debugger/cdb/cdbdebugengine.cpp b/src/plugins/debugger/cdb/cdbdebugengine.cpp
index 9f74ec7f9ef..ae17ff1ea0f 100644
--- a/src/plugins/debugger/cdb/cdbdebugengine.cpp
+++ b/src/plugins/debugger/cdb/cdbdebugengine.cpp
@@ -625,12 +625,14 @@ void CdbDebugEngine::startDebugger(const QSharedPointer<DebuggerStartParameters>
 {
     if (debugCDBExecution)
         qDebug() << "startDebugger" << *sp;
+    CDBBreakPoint::clearNormalizeFileNameCache();
     setState(AdapterStarting, Q_FUNC_INFO, __LINE__);
     m_d->checkVersion();
     if (m_d->m_hDebuggeeProcess) {
         warning(QLatin1String("Internal error: Attempt to start debugger while another process is being debugged."));
         setState(AdapterStartFailed, Q_FUNC_INFO, __LINE__);
         emit startFailed();
+        return;
     }
     m_d->clearDisplay();
     m_d->m_inferiorStartupComplete = false;
diff --git a/src/plugins/debugger/cdb/cdbstacktracecontext.cpp b/src/plugins/debugger/cdb/cdbstacktracecontext.cpp
index 90329dfe04f..e0c4ba0c798 100644
--- a/src/plugins/debugger/cdb/cdbstacktracecontext.cpp
+++ b/src/plugins/debugger/cdb/cdbstacktracecontext.cpp
@@ -116,7 +116,7 @@ bool CdbStackTraceContext::init(unsigned long frameCount, QString * /*errorMessa
             frame.line = ulLine;
             // Vitally important  to use canonical file that matches editormanager,
             // else the marker will not show.
-            frame.file = CDBBreakPoint::canonicalSourceFile(QString::fromUtf16(reinterpret_cast<const ushort *>(wszBuf)));
+            frame.file = CDBBreakPoint::normalizeFileName(QString::fromUtf16(reinterpret_cast<const ushort *>(wszBuf)));
         }
         m_frames.push_back(frame);
     }
-- 
GitLab