diff --git a/lldb/include/lldb/Core/ConstString.h b/lldb/include/lldb/Core/ConstString.h --- a/lldb/include/lldb/Core/ConstString.h +++ b/lldb/include/lldb/Core/ConstString.h @@ -291,12 +291,37 @@ } //------------------------------------------------------------------ + /// Equal to operator + /// + /// Returns true if this string is equal to the string in \a rhs. + /// If case sensitive equality is tested, this operation is very + /// fast as it results in a pointer comparison since all strings + /// are in a uniqued in a global string pool. + /// + /// @param[in] rhs + /// The Left Hand Side const ConstString object reference. + /// + /// @param[in] rhs + /// The Right Hand Side const ConstString object reference. + /// + /// @param[in] case_sensitive + /// Case sensitivity. If true, case sensitive equality + /// will be tested, otherwise character case will be ignored + /// + /// @return + /// @li \b true if this object is equal to \a rhs. + /// @li \b false if this object is not equal to \a rhs. + //------------------------------------------------------------------ + static bool + Equals(const ConstString &lhs, const ConstString &rhs, const bool case_sensitive = true); + + //------------------------------------------------------------------ /// Compare two string objects. /// /// Compares the C string values contained in \a lhs and \a rhs and /// returns an integer result. /// - /// NOTE: only call this function when you want a true string + /// NOTE: only call this function when you want a true string /// comparison. If you want string equality use the, use the == /// operator as it is much more efficient. Also if you want string /// inequality, use the != operator for the same reasons. @@ -307,13 +332,17 @@ /// @param[in] rhs /// The Right Hand Side const ConstString object reference. /// + /// @param[in] case_sensitive + /// Case sensitivity of compare. If true, case sensitive compare + /// will be performed, otherwise character case will be ignored + /// /// @return /// @li -1 if lhs < rhs /// @li 0 if lhs == rhs /// @li 1 if lhs > rhs //------------------------------------------------------------------ static int - Compare (const ConstString& lhs, const ConstString& rhs); + Compare(const ConstString &lhs, const ConstString &rhs, const bool case_sensitive = true); //------------------------------------------------------------------ /// Dump the object description to a stream. diff --git a/lldb/include/lldb/Host/FileSpec.h b/lldb/include/lldb/Host/FileSpec.h --- a/lldb/include/lldb/Host/FileSpec.h +++ b/lldb/include/lldb/Host/FileSpec.h @@ -261,6 +261,19 @@ Equal (const FileSpec& a, const FileSpec& b, bool full, bool remove_backups = false); //------------------------------------------------------------------ + /// Case sensitivity of path. + /// + /// @return + /// \b true if the file path is case sensitive (POSIX), false + /// if case insensitive (Windows). + //------------------------------------------------------------------ + bool + IsCaseSensitive() const + { + return m_syntax != ePathSyntaxWindows; + } + + //------------------------------------------------------------------ /// Dump this object to a Stream. /// /// Dump the object to the supplied stream \a s. If the object diff --git a/lldb/packages/Python/lldbsuite/test/functionalities/breakpoint/breakpoint_case_sensitivity/Makefile b/lldb/packages/Python/lldbsuite/test/functionalities/breakpoint/breakpoint_case_sensitivity/Makefile new file mode 100644 --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/functionalities/breakpoint/breakpoint_case_sensitivity/Makefile @@ -0,0 +1,6 @@ +LEVEL = ../../../make + +C_SOURCES := main.c +CFLAGS_EXTRAS += -std=c99 + +include $(LEVEL)/Makefile.rules diff --git a/lldb/packages/Python/lldbsuite/test/functionalities/breakpoint/breakpoint_case_sensitivity/TestBreakpointCaseSensitivity.py b/lldb/packages/Python/lldbsuite/test/functionalities/breakpoint/breakpoint_case_sensitivity/TestBreakpointCaseSensitivity.py new file mode 100644 --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/functionalities/breakpoint/breakpoint_case_sensitivity/TestBreakpointCaseSensitivity.py @@ -0,0 +1,120 @@ +""" +Test case sensitivity of paths on Windows / POSIX +llvm.org/pr22667 +""" + +import os +import lldb +from lldbsuite.test.lldbtest import * +from lldbsuite.test.decorators import * +from lldbsuite.test import lldbplatform, lldbplatformutil + +class BreakpointCaseSensitivityTestCase(TestBase): + mydir = TestBase.compute_mydir(__file__) + BREAKPOINT_TEXT = 'Set a breakpoint here' + + def setUp(self): + # Call super's setUp(). + TestBase.setUp(self) + self.line = line_number('main.c', self.BREAKPOINT_TEXT) + + @skipIf(oslist=no_match(['windows'])) # Skip for non-windows platforms + def test_breakpoint_matches_file_with_different_case(self): + """Set breakpoint on file, should match files with different case on Windows""" + self.build() + self.case_sensitivity_breakpoint(True) + + @skipIf(oslist=['windows']) # Skip for windows platforms + def test_breakpoint_doesnt_match_file_with_different_case(self): + """Set breakpoint on file, shouldn't match files with different case on POSIX systems""" + self.build() + self.case_sensitivity_breakpoint(False) + + def case_sensitivity_breakpoint(self, case_insensitive): + """Set breakpoint on file, should match files with different case if case_insensitive is True""" + + # use different case to check CreateTarget + exe = 'a.out' + if case_insensitive: + exe = exe.upper() + + exe = os.path.join(os.getcwd(), exe) + + # Create a target by the debugger. + self.target = self.dbg.CreateTarget(exe) + self.assertTrue(self.target, VALID_TARGET) + cwd = self.get_process_working_directory(); + + # try both BreakpointCreateByLocation and BreakpointCreateBySourceRegex + for regex in [False, True]: + # should always hit + self.check_breakpoint('main.c', regex, True) + # should always hit + self.check_breakpoint(os.path.join(cwd, 'main.c'), regex, True) + # different case for directory + self.check_breakpoint(os.path.join(cwd.upper(), 'main.c'), + regex, + case_insensitive) + # different case for file + self.check_breakpoint('Main.c', + regex, + case_insensitive) + # different case for both + self.check_breakpoint(os.path.join(cwd.upper(), 'Main.c'), + regex, + case_insensitive) + + def check_breakpoint(self, file, source_regex, should_hit): + """ + Check breakpoint hit at given file set by given method + + file: + File where insert the breakpoint + + source_regex: + True for testing using BreakpointCreateBySourceRegex, + False for BreakpointCreateByLocation + + should_hit: + True if the breakpoint should hit, False otherwise + """ + + desc = ' file %s set by %s' % (file, 'regex' if source_regex else 'location') + if source_regex: + breakpoint = self.target.BreakpointCreateBySourceRegex(self.BREAKPOINT_TEXT, + lldb.SBFileSpec(file)) + else: + breakpoint = self.target.BreakpointCreateByLocation(file, self.line) + + self.assertEqual(breakpoint and breakpoint.GetNumLocations() == 1, + should_hit, + VALID_BREAKPOINT + desc) + + # Get the breakpoint location from breakpoint after we verified that, + # indeed, it has one location. + location = breakpoint.GetLocationAtIndex(0) + self.assertEqual(location and location.IsEnabled(), + should_hit, + VALID_BREAKPOINT_LOCATION + desc) + + process = self.target.LaunchSimple(None, None, self.get_process_working_directory()) + self.assertTrue(process, PROCESS_IS_VALID + desc) + + if should_hit: + # Did we hit our breakpoint? + from lldbsuite.test.lldbutil import get_threads_stopped_at_breakpoint + threads = get_threads_stopped_at_breakpoint (process, breakpoint) + self.assertEqual(len(threads), 1, "There should be a thread stopped at breakpoint" + desc) + # The hit count for the breakpoint should be 1. + self.assertEqual(breakpoint.GetHitCount(), 1) + + else: + # check the breakpoint was not hit + self.assertEqual(lldb.eStateExited, process.GetState()) + self.assertEqual(breakpoint.GetHitCount(), 0) + + # let process finish + process.Continue() + + # cleanup + self.target.BreakpointDelete(breakpoint.GetID()) diff --git a/lldb/packages/Python/lldbsuite/test/functionalities/breakpoint/breakpoint_case_sensitivity/main.c b/lldb/packages/Python/lldbsuite/test/functionalities/breakpoint/breakpoint_case_sensitivity/main.c new file mode 100644 --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/functionalities/breakpoint/breakpoint_case_sensitivity/main.c @@ -0,0 +1,8 @@ +#include + +int +main() +{ + printf("Set a breakpoint here.\n"); + return 0; +} diff --git a/lldb/source/Core/ConstString.cpp b/lldb/source/Core/ConstString.cpp --- a/lldb/source/Core/ConstString.cpp +++ b/lldb/source/Core/ConstString.cpp @@ -265,8 +265,25 @@ return StringPool().GetConstCStringLength (m_string); } +bool +ConstString::Equals(const ConstString &lhs, const ConstString &rhs, const bool case_sensitive) +{ + if (lhs.m_string == rhs.m_string) + return true; + + // Since the pointers weren't equal, and identical ConstStrings always have identical pointers, + // the result must be false for case sensitive equality test. + if (case_sensitive) + return false; + + // perform case insensitive equality test + llvm::StringRef lhs_string_ref(lhs.m_string, StringPool().GetConstCStringLength(lhs.m_string)); + llvm::StringRef rhs_string_ref(rhs.m_string, StringPool().GetConstCStringLength(rhs.m_string)); + return lhs_string_ref.equals_lower(rhs_string_ref); +} + int -ConstString::Compare (const ConstString& lhs, const ConstString& rhs) +ConstString::Compare(const ConstString &lhs, const ConstString &rhs, const bool case_sensitive) { // If the iterators are the same, this is the same string const char *lhs_cstr = lhs.m_string; @@ -277,7 +294,15 @@ { llvm::StringRef lhs_string_ref (lhs_cstr, StringPool().GetConstCStringLength (lhs_cstr)); llvm::StringRef rhs_string_ref (rhs_cstr, StringPool().GetConstCStringLength (rhs_cstr)); - return lhs_string_ref.compare(rhs_string_ref); + + if (case_sensitive) + { + return lhs_string_ref.compare(rhs_string_ref); + } + else + { + return lhs_string_ref.compare_lower(rhs_string_ref); + } } if (lhs_cstr) diff --git a/lldb/source/Core/FileSpecList.cpp b/lldb/source/Core/FileSpecList.cpp --- a/lldb/source/Core/FileSpecList.cpp +++ b/lldb/source/Core/FileSpecList.cpp @@ -119,7 +119,8 @@ { if (compare_filename_only) { - if (m_files[idx].GetFilename() == file_spec.GetFilename()) + if (ConstString::Equals(m_files[idx].GetFilename(), file_spec.GetFilename(), + file_spec.IsCaseSensitive() || m_files[idx].IsCaseSensitive())) return idx; } else diff --git a/lldb/source/Host/common/FileSpec.cpp b/lldb/source/Host/common/FileSpec.cpp --- a/lldb/source/Host/common/FileSpec.cpp +++ b/lldb/source/Host/common/FileSpec.cpp @@ -396,60 +396,62 @@ bool FileSpec::operator== (const FileSpec& rhs) const { - if (m_filename == rhs.m_filename) + // case sensitivity of equality test + const bool case_sensitive = IsCaseSensitive() || rhs.IsCaseSensitive(); + + if (!ConstString::Equals(m_filename, rhs.m_filename, case_sensitive)) + return false; + + if (ConstString::Equals(m_directory, rhs.m_directory, case_sensitive)) + return true; + + // TODO: determine if we want to keep this code in here. + // The code below was added to handle a case where we were + // trying to set a file and line breakpoint and one path + // was resolved, and the other not and the directory was + // in a mount point that resolved to a more complete path: + // "/tmp/a.c" == "/private/tmp/a.c". I might end up pulling + // this out... + if (IsResolved() && rhs.IsResolved()) { - if (m_directory == rhs.m_directory) - return true; - - // TODO: determine if we want to keep this code in here. - // The code below was added to handle a case where we were - // trying to set a file and line breakpoint and one path - // was resolved, and the other not and the directory was - // in a mount point that resolved to a more complete path: - // "/tmp/a.c" == "/private/tmp/a.c". I might end up pulling - // this out... - if (IsResolved() && rhs.IsResolved()) - { - // Both paths are resolved, no need to look further... - return false; - } - - FileSpec resolved_lhs(*this); + // Both paths are resolved, no need to look further... + return false; + } + + FileSpec resolved_lhs(*this); - // If "this" isn't resolved, resolve it - if (!IsResolved()) + // If "this" isn't resolved, resolve it + if (!IsResolved()) + { + if (resolved_lhs.ResolvePath()) { - if (resolved_lhs.ResolvePath()) - { - // This path wasn't resolved but now it is. Check if the resolved - // directory is the same as our unresolved directory, and if so, - // we can mark this object as resolved to avoid more future resolves - m_is_resolved = (m_directory == resolved_lhs.m_directory); - } - else - return false; + // This path wasn't resolved but now it is. Check if the resolved + // directory is the same as our unresolved directory, and if so, + // we can mark this object as resolved to avoid more future resolves + m_is_resolved = (m_directory == resolved_lhs.m_directory); } - - FileSpec resolved_rhs(rhs); - if (!rhs.IsResolved()) + else + return false; + } + + FileSpec resolved_rhs(rhs); + if (!rhs.IsResolved()) + { + if (resolved_rhs.ResolvePath()) { - if (resolved_rhs.ResolvePath()) - { - // rhs's path wasn't resolved but now it is. Check if the resolved - // directory is the same as rhs's unresolved directory, and if so, - // we can mark this object as resolved to avoid more future resolves - rhs.m_is_resolved = (rhs.m_directory == resolved_rhs.m_directory); - } - else - return false; + // rhs's path wasn't resolved but now it is. Check if the resolved + // directory is the same as rhs's unresolved directory, and if so, + // we can mark this object as resolved to avoid more future resolves + rhs.m_is_resolved = (rhs.m_directory == resolved_rhs.m_directory); } - - // If we reach this point in the code we were able to resolve both paths - // and since we only resolve the paths if the basenames are equal, then - // we can just check if both directories are equal... - return resolved_lhs.GetDirectory() == resolved_rhs.GetDirectory(); + else + return false; } - return false; + + // If we reach this point in the code we were able to resolve both paths + // and since we only resolve the paths if the basenames are equal, then + // we can just check if both directories are equal... + return ConstString::Equals(m_directory, rhs.m_directory, case_sensitive); } //------------------------------------------------------------------ @@ -507,6 +509,9 @@ { int result = 0; + // case sensitivity of compare + const bool case_sensitive = a.IsCaseSensitive() || b.IsCaseSensitive(); + // If full is true, then we must compare both the directory and filename. // If full is false, then if either directory is empty, then we match on @@ -516,32 +521,35 @@ if (full || (a.m_directory && b.m_directory)) { - result = ConstString::Compare(a.m_directory, b.m_directory); + result = ConstString::Compare(a.m_directory, b.m_directory, case_sensitive); if (result) return result; } - return ConstString::Compare (a.m_filename, b.m_filename); + return ConstString::Compare(a.m_filename, b.m_filename, case_sensitive); } bool FileSpec::Equal (const FileSpec& a, const FileSpec& b, bool full, bool remove_backups) { + // case sensitivity of equality test + const bool case_sensitive = a.IsCaseSensitive() || b.IsCaseSensitive(); + if (!full && (a.GetDirectory().IsEmpty() || b.GetDirectory().IsEmpty())) - return a.m_filename == b.m_filename; + return ConstString::Equals(a.m_filename, b.m_filename, case_sensitive); else if (remove_backups == false) return a == b; else { - if (a.m_filename != b.m_filename) + if (!ConstString::Equals(a.m_filename, b.m_filename, case_sensitive)) return false; - if (a.m_directory == b.m_directory) + if (ConstString::Equals(a.m_directory, b.m_directory, case_sensitive)) return true; ConstString a_without_dots; ConstString b_without_dots; RemoveBackupDots (a.m_directory, a_without_dots); RemoveBackupDots (b.m_directory, b_without_dots); - return a_without_dots == b_without_dots; + return ConstString::Equals(a_without_dots, b_without_dots, case_sensitive); } }