diff --git a/lldb/include/lldb/Breakpoint/BreakpointResolverFileLine.h b/lldb/include/lldb/Breakpoint/BreakpointResolverFileLine.h --- a/lldb/include/lldb/Breakpoint/BreakpointResolverFileLine.h +++ b/lldb/include/lldb/Breakpoint/BreakpointResolverFileLine.h @@ -56,7 +56,7 @@ CopyForBreakpoint(lldb::BreakpointSP &breakpoint) override; protected: - void FilterContexts(SymbolContextList &sc_list, bool is_relative); + void FilterContexts(SymbolContextList &sc_list); friend class Breakpoint; SourceLocationSpec m_location_spec; diff --git a/lldb/include/lldb/Core/FileSpecList.h b/lldb/include/lldb/Core/FileSpecList.h --- a/lldb/include/lldb/Core/FileSpecList.h +++ b/lldb/include/lldb/Core/FileSpecList.h @@ -116,6 +116,32 @@ /// else UINT32_MAX is returned. size_t FindFileIndex(size_t idx, const FileSpec &file, bool full) const; + /// Find a compatible file index. + /// + /// Find the index of a compatible file in the file spec list that matches \a + /// file starting \a idx entries into the file spec list. A file is considered + /// compatible if: + /// - The file matches exactly (only filename if \a file has no directory) + /// - If \a file is relative and any file in the list has this same suffix + /// - If any file in the list is relative and the relative path is a suffix + /// of \a file + /// + /// This is used to implement better matching for setting breakpoints in + /// source files where an IDE might specify a full path when setting the + /// breakpoint and debug info contains relative paths, if a user specifies + /// a relative path when setting a breakpoint. + /// + /// \param[in] idx + /// An index into the file list. + /// + /// \param[in] file + /// The file specification to search for. + /// + /// \return + /// The index of the file that matches \a file if it is found, + /// else UINT32_MAX is returned. + size_t FindCompatibleIndex(size_t idx, const FileSpec &file) const; + /// Get file at index. /// /// Gets a file from the file list. If \a idx is not a valid index, an empty diff --git a/lldb/source/Breakpoint/BreakpointResolverFileLine.cpp b/lldb/source/Breakpoint/BreakpointResolverFileLine.cpp --- a/lldb/source/Breakpoint/BreakpointResolverFileLine.cpp +++ b/lldb/source/Breakpoint/BreakpointResolverFileLine.cpp @@ -119,34 +119,14 @@ // handling inlined functions -- in this case we need to make sure we look at // the declaration line of the inlined function, NOT the function it was // inlined into. -void BreakpointResolverFileLine::FilterContexts(SymbolContextList &sc_list, - bool is_relative) { +void BreakpointResolverFileLine::FilterContexts(SymbolContextList &sc_list) { if (m_location_spec.GetExactMatch()) return; // Nothing to do. Contexts are precise. - llvm::StringRef relative_path; - if (is_relative) - relative_path = m_location_spec.GetFileSpec().GetDirectory().GetStringRef(); - Log *log = GetLog(LLDBLog::Breakpoints); for(uint32_t i = 0; i < sc_list.GetSize(); ++i) { SymbolContext sc; sc_list.GetContextAtIndex(i, sc); - if (is_relative) { - // If the path was relative, make sure any matches match as long as the - // relative parts of the path match the path from support files - auto sc_dir = sc.line_entry.file.GetDirectory().GetStringRef(); - if (!sc_dir.endswith(relative_path)) { - // We had a relative path specified and the relative directory doesn't - // match so remove this one - LLDB_LOG(log, "removing not matching relative path {0} since it " - "doesn't end with {1}", sc_dir, relative_path); - sc_list.RemoveContextAtIndex(i); - --i; - continue; - } - } - if (!sc.block) continue; @@ -230,29 +210,17 @@ const uint32_t line = m_location_spec.GetLine().value_or(0); const llvm::Optional column = m_location_spec.GetColumn(); - // We'll create a new SourceLocationSpec that can take into account the - // relative path case, and we'll use it to resolve the symbol context - // of the CUs. - FileSpec search_file_spec = m_location_spec.GetFileSpec(); - const bool is_relative = search_file_spec.IsRelative(); - if (is_relative) - search_file_spec.ClearDirectory(); - SourceLocationSpec search_location_spec( - search_file_spec, m_location_spec.GetLine().value_or(0), - m_location_spec.GetColumn(), m_location_spec.GetCheckInlines(), - m_location_spec.GetExactMatch()); - const size_t num_comp_units = context.module_sp->GetNumCompileUnits(); for (size_t i = 0; i < num_comp_units; i++) { CompUnitSP cu_sp(context.module_sp->GetCompileUnitAtIndex(i)); if (cu_sp) { if (filter.CompUnitPasses(*cu_sp)) - cu_sp->ResolveSymbolContext(search_location_spec, - eSymbolContextEverything, sc_list); + cu_sp->ResolveSymbolContext(m_location_spec, eSymbolContextEverything, + sc_list); } } - FilterContexts(sc_list, is_relative); + FilterContexts(sc_list); StreamString s; s.Printf("for %s:%d ", 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 @@ -81,6 +81,58 @@ return UINT32_MAX; } +size_t FileSpecList::FindCompatibleIndex(size_t start_idx, + const FileSpec &file_spec) const { + const size_t num_files = m_files.size(); + if (start_idx >= num_files) + return UINT32_MAX; + + const bool file_spec_relative = file_spec.IsRelative(); + const bool file_spec_case_sensitive = file_spec.IsCaseSensitive(); + // When looking for files, we will compare only the filename if the directory + // argument is empty in file_spec + const bool full = !file_spec.GetDirectory().IsEmpty(); + + for (size_t idx = start_idx; idx < num_files; ++idx) { + const FileSpec &curr_file = m_files[idx]; + + // Always start by matching the filename first + if (!curr_file.FileEquals(file_spec)) + continue; + + // Only compare the full name if the we were asked to and if the current + // file entry has the a directory. If it doesn't have a directory then we + // only compare the filename. + if (FileSpec::Equal(curr_file, file_spec, full)) { + return idx; + } else if (curr_file.IsRelative() || file_spec_relative) { + llvm::StringRef curr_file_dir = curr_file.GetDirectory().GetStringRef(); + if (curr_file_dir.empty()) + return idx; // Basename match only for this file in the list + + // Check if we have a relative path in our file list, or if "file_spec" is + // relative, if so, check if either ends with the other. + llvm::StringRef file_spec_dir = file_spec.GetDirectory().GetStringRef(); + // We have a relative path in our file list, it matches if the + // specified path ends with this path, but we must ensure the full + // component matches (we don't want "foo/bar.cpp" to match "oo/bar.cpp"). + auto is_suffix = [](llvm::StringRef a, llvm::StringRef b, + bool case_sensitive) -> bool { + if (case_sensitive ? a.consume_back(b) : a.consume_back_insensitive(b)) + return a.empty() || a.endswith("/"); + return false; + }; + const bool case_sensitive = + file_spec_case_sensitive || curr_file.IsCaseSensitive(); + if (is_suffix(curr_file_dir, file_spec_dir, case_sensitive) || + is_suffix(file_spec_dir, curr_file_dir, case_sensitive)) + return idx; + } + } + + // We didn't find the file, return an invalid index + return UINT32_MAX; +} // Returns the FileSpec object at index "idx". If "idx" is out of range, then // an empty FileSpec object will be returned. const FileSpec &FileSpecList::GetFileSpecAtIndex(size_t idx) const { diff --git a/lldb/source/Symbol/CompileUnit.cpp b/lldb/source/Symbol/CompileUnit.cpp --- a/lldb/source/Symbol/CompileUnit.cpp +++ b/lldb/source/Symbol/CompileUnit.cpp @@ -216,10 +216,11 @@ return m_variables; } -std::vector FindFileIndexes(const FileSpecList &files, const FileSpec &file) { +std::vector FindFileIndexes(const FileSpecList &files, + const FileSpec &file) { std::vector result; uint32_t idx = -1; - while ((idx = files.FindFileIndex(idx + 1, file, /*full=*/true)) != + while ((idx = files.FindCompatibleIndex(idx + 1, file)) != UINT32_MAX) result.push_back(idx); return result; @@ -230,7 +231,8 @@ LineEntry *line_entry_ptr) { if (!file_spec_ptr) file_spec_ptr = &GetPrimaryFile(); - std::vector file_indexes = FindFileIndexes(GetSupportFiles(), *file_spec_ptr); + std::vector file_indexes = FindFileIndexes(GetSupportFiles(), + *file_spec_ptr); if (file_indexes.empty()) return UINT32_MAX; @@ -255,7 +257,6 @@ // First find all of the file indexes that match our "file_spec". If // "file_spec" has an empty directory, then only compare the basenames when // finding file indexes - std::vector file_indexes; bool file_spec_matches_cu_file_spec = FileSpec::Match(file_spec, this->GetPrimaryFile()); @@ -276,13 +277,8 @@ return; } - uint32_t file_idx = - GetSupportFiles().FindFileIndex(0, file_spec, true); - while (file_idx != UINT32_MAX) { - file_indexes.push_back(file_idx); - file_idx = GetSupportFiles().FindFileIndex(file_idx + 1, file_spec, true); - } - + std::vector file_indexes = FindFileIndexes(GetSupportFiles(), + file_spec); const size_t num_file_indexes = file_indexes.size(); if (num_file_indexes == 0) return; diff --git a/lldb/test/API/functionalities/breakpoint/breakpoint_command/TestBreakpointCommand.py b/lldb/test/API/functionalities/breakpoint/breakpoint_command/TestBreakpointCommand.py --- a/lldb/test/API/functionalities/breakpoint/breakpoint_command/TestBreakpointCommand.py +++ b/lldb/test/API/functionalities/breakpoint/breakpoint_command/TestBreakpointCommand.py @@ -8,9 +8,11 @@ from lldbsuite.test.decorators import * from lldbsuite.test.lldbtest import * from lldbsuite.test import lldbutil +import os import side_effect + class BreakpointCommandTestCase(TestBase): NO_DEBUG_INFO_TESTCASE = True @@ -31,6 +33,77 @@ self.build() self.breakpoint_commands_on_creation() + @skipIf(oslist=["windows"]) + @no_debug_info_test + def test_breakpoints_with_relative_path_line_tables(self): + """ + Test that we can set breakpoints using a full or partial path when + line tables in the debug information has relative paths where the + relative path is either fully contained in the specified path, or if + the specified path also a relative path that is shorter than the + path in the debug info. + + The "relative.yaml" contains a line table that is: + + Line table for a/b/c/main.cpp in `a.out + 0x0000000100003f94: a/b/c/main.cpp:1 + 0x0000000100003fb0: a/b/c/main.cpp:2:3 + 0x0000000100003fb8: a/b/c/main.cpp:2:3 + + So the line table contains relative paths. We should be able to set + breakpoints with any source path that matches this path which + includes paths that are longer than "a/b/c/main.cpp", but also any + relative path that is shorter than this while all specified relative + path components still match. + """ + src_dir = self.getSourceDir() + yaml_path = os.path.join(src_dir, "relative.yaml") + yaml_base, ext = os.path.splitext(yaml_path) + obj_path = self.getBuildArtifact("a.out") + self.yaml2obj(yaml_path, obj_path) + + # Create a target with the object file we just created from YAML + target = self.dbg.CreateTarget(obj_path) + # We now have debug information with line table paths that start are + # "./a/b/c/main.cpp". + + # Make sure that all of the following paths create a breakpoint + # successfully. We have paths that are longer than our path, and also + # that are shorter where all relative directories still match. + valid_paths = [ + "/x/a/b/c/main.cpp", + "/x/y/a/b/c/main.cpp", + "./x/y/a/b/c/main.cpp", + "x/y/a/b/c/main.cpp", + "./y/a/b/c/main.cpp", + "y/a/b/c/main.cpp", + "./a/b/c/main.cpp", + "a/b/c/main.cpp", + "./b/c/main.cpp", + "b/c/main.cpp", + "./c/main.cpp", + "c/main.cpp", + "./main.cpp", + "main.cpp", + ] + for path in valid_paths: + bkpt = target.BreakpointCreateByLocation(path, 2) + self.assertTrue(bkpt.GetNumLocations() > 0, + 'Couldn\'t resolve breakpoint using full path "%s" in executate "%s" with ' + 'debug info that has relative path with matching suffix' % (path, self.getBuildArtifact("a.out"))) + invalid_paths = [ + "/x/b/c/main.cpp", + "/x/c/main.cpp", + "/x/main.cpp", + "./x/y/a/d/c/main.cpp", + ] + for path in invalid_paths: + bkpt = target.BreakpointCreateByLocation(path, 2) + self.assertTrue(bkpt.GetNumLocations() == 0, + 'Incorrectly resolved a breakpoint using full path "%s" with ' + 'debug info that has relative path with matching suffix' % (path)) + + def setUp(self): # Call super's setUp(). TestBase.setUp(self) @@ -307,8 +380,8 @@ for id in bp_ids: self.expect("breakpoint command list {0}".format(id), patterns=["bktptcmd.function"]) - - + + def test_breakpoint_delete_disabled(self): """Test 'break delete --disabled' works""" diff --git a/lldb/test/API/functionalities/breakpoint/breakpoint_command/relative.yaml b/lldb/test/API/functionalities/breakpoint/breakpoint_command/relative.yaml new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/breakpoint/breakpoint_command/relative.yaml @@ -0,0 +1,445 @@ +--- !mach-o +FileHeader: + magic: 0xFEEDFACF + cputype: 0x100000C + cpusubtype: 0x0 + filetype: 0xA + ncmds: 7 + sizeofcmds: 1240 + flags: 0x0 + reserved: 0x0 +LoadCommands: + - cmd: LC_UUID + cmdsize: 24 + uuid: 030921EA-6D76-3A68-B515-386B9AF6D568 + - cmd: LC_BUILD_VERSION + cmdsize: 24 + platform: 1 + minos: 786432 + sdk: 787200 + ntools: 0 + - cmd: LC_SYMTAB + cmdsize: 24 + symoff: 4096 + nsyms: 2 + stroff: 4128 + strsize: 28 + - cmd: LC_SEGMENT_64 + cmdsize: 72 + segname: __PAGEZERO + vmaddr: 0 + vmsize: 4294967296 + fileoff: 0 + filesize: 0 + maxprot: 0 + initprot: 0 + nsects: 0 + flags: 0 + - cmd: LC_SEGMENT_64 + cmdsize: 232 + segname: __TEXT + vmaddr: 4294967296 + vmsize: 16384 + fileoff: 0 + filesize: 0 + maxprot: 5 + initprot: 5 + nsects: 2 + flags: 0 + Sections: + - sectname: __text + segname: __TEXT + addr: 0x100003F94 + size: 36 + offset: 0x0 + align: 2 + reloff: 0x0 + nreloc: 0 + flags: 0x80000400 + reserved1: 0x0 + reserved2: 0x0 + reserved3: 0x0 + content: CFFAEDFE0C000001000000000A00000007000000D804000000000000000000001B000000 + - sectname: __unwind_info + segname: __TEXT + addr: 0x100003FB8 + size: 72 + offset: 0x0 + align: 2 + reloff: 0x0 + nreloc: 0 + flags: 0x0 + reserved1: 0x0 + reserved2: 0x0 + reserved3: 0x0 + content: CFFAEDFE0C000001000000000A00000007000000D804000000000000000000001B00000018000000030921EA6D763A68B515386B9AF6D56832000000180000000100000000000C00 + - cmd: LC_SEGMENT_64 + cmdsize: 72 + segname: __LINKEDIT + vmaddr: 4294983680 + vmsize: 4096 + fileoff: 4096 + filesize: 60 + maxprot: 1 + initprot: 1 + nsects: 0 + flags: 0 + - cmd: LC_SEGMENT_64 + cmdsize: 792 + segname: __DWARF + vmaddr: 4294987776 + vmsize: 4096 + fileoff: 8192 + filesize: 796 + maxprot: 7 + initprot: 3 + nsects: 9 + flags: 0 + Sections: + - sectname: __debug_line + segname: __DWARF + addr: 0x100005000 + size: 64 + offset: 0x2000 + align: 0 + reloff: 0x0 + nreloc: 0 + flags: 0x0 + reserved1: 0x0 + reserved2: 0x0 + reserved3: 0x0 + - sectname: __debug_aranges + segname: __DWARF + addr: 0x100005040 + size: 48 + offset: 0x2040 + align: 0 + reloff: 0x0 + nreloc: 0 + flags: 0x0 + reserved1: 0x0 + reserved2: 0x0 + reserved3: 0x0 + - sectname: __debug_info + segname: __DWARF + addr: 0x100005070 + size: 148 + offset: 0x2070 + align: 0 + reloff: 0x0 + nreloc: 0 + flags: 0x0 + reserved1: 0x0 + reserved2: 0x0 + reserved3: 0x0 + - sectname: __debug_abbrev + segname: __DWARF + addr: 0x100005104 + size: 90 + offset: 0x2104 + align: 0 + reloff: 0x0 + nreloc: 0 + flags: 0x0 + reserved1: 0x0 + reserved2: 0x0 + reserved3: 0x0 + - sectname: __debug_str + segname: __DWARF + addr: 0x10000515E + size: 200 + offset: 0x215E + align: 0 + reloff: 0x0 + nreloc: 0 + flags: 0x0 + reserved1: 0x0 + reserved2: 0x0 + reserved3: 0x0 + - sectname: __apple_names + segname: __DWARF + addr: 0x100005226 + size: 60 + offset: 0x2226 + align: 0 + reloff: 0x0 + nreloc: 0 + flags: 0x0 + reserved1: 0x0 + reserved2: 0x0 + reserved3: 0x0 + content: 485341480100000001000000010000000C000000000000000100000001000600000000006A7F9A7C2C000000AB000000010000003200000000000000 + - sectname: __apple_namespac + segname: __DWARF + addr: 0x100005262 + size: 36 + offset: 0x2262 + align: 0 + reloff: 0x0 + nreloc: 0 + flags: 0x0 + reserved1: 0x0 + reserved2: 0x0 + reserved3: 0x0 + content: 485341480100000001000000000000000C000000000000000100000001000600FFFFFFFF + - sectname: __apple_types + segname: __DWARF + addr: 0x100005286 + size: 114 + offset: 0x2286 + align: 0 + reloff: 0x0 + nreloc: 0 + flags: 0x0 + reserved1: 0x0 + reserved2: 0x0 + reserved3: 0x0 + content: 48534148010000000200000002000000180000000000000004000000010006000300050005000B000600060000000000010000003080880B6320957C440000005B000000BF0000000100000076000000240000A4283A0C00000000C3000000010000008C00000024000057D77B9300000000 + - sectname: __apple_objc + segname: __DWARF + addr: 0x1000052F8 + size: 36 + offset: 0x22F8 + align: 0 + reloff: 0x0 + nreloc: 0 + flags: 0x0 + reserved1: 0x0 + reserved2: 0x0 + reserved3: 0x0 + content: 485341480100000001000000000000000C000000000000000100000001000600FFFFFFFF +LinkEditData: + NameList: + - n_strx: 2 + n_type: 0xF + n_sect: 1 + n_desc: 16 + n_value: 4294967296 + - n_strx: 22 + n_type: 0xF + n_sect: 1 + n_desc: 0 + n_value: 4294983572 + StringTable: + - '' + - '' + - __mh_execute_header + - _main +DWARF: + debug_str: + - '' + - 'Apple clang version 13.1.6 (clang-1316.0.21.2)' + - main.cpp + - '/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk' + - MacOSX.sdk + - './a/b/c' + - main + - argc + - argv + - envp + - int + - char + debug_abbrev: + - ID: 0 + Table: + - Code: 0x1 + Tag: DW_TAG_compile_unit + Children: DW_CHILDREN_yes + Attributes: + - Attribute: DW_AT_producer + Form: DW_FORM_strp + - Attribute: DW_AT_language + Form: DW_FORM_data2 + - Attribute: DW_AT_name + Form: DW_FORM_strp + - Attribute: DW_AT_LLVM_sysroot + Form: DW_FORM_strp + - Attribute: DW_AT_APPLE_sdk + Form: DW_FORM_strp + - Attribute: DW_AT_stmt_list + Form: DW_FORM_sec_offset + - Attribute: DW_AT_comp_dir + Form: DW_FORM_strp + - Attribute: DW_AT_low_pc + Form: DW_FORM_addr + - Attribute: DW_AT_high_pc + Form: DW_FORM_data4 + - Code: 0x2 + Tag: DW_TAG_subprogram + Children: DW_CHILDREN_yes + Attributes: + - Attribute: DW_AT_low_pc + Form: DW_FORM_addr + - Attribute: DW_AT_high_pc + Form: DW_FORM_data4 + - Attribute: DW_AT_APPLE_omit_frame_ptr + Form: DW_FORM_flag_present + - Attribute: DW_AT_frame_base + Form: DW_FORM_exprloc + - Attribute: DW_AT_name + Form: DW_FORM_strp + - Attribute: DW_AT_decl_file + Form: DW_FORM_data1 + - Attribute: DW_AT_decl_line + Form: DW_FORM_data1 + - Attribute: DW_AT_type + Form: DW_FORM_ref_addr + - Attribute: DW_AT_external + Form: DW_FORM_flag_present + - Code: 0x3 + Tag: DW_TAG_formal_parameter + Children: DW_CHILDREN_no + Attributes: + - Attribute: DW_AT_location + Form: DW_FORM_exprloc + - Attribute: DW_AT_name + Form: DW_FORM_strp + - Attribute: DW_AT_decl_file + Form: DW_FORM_data1 + - Attribute: DW_AT_decl_line + Form: DW_FORM_data1 + - Attribute: DW_AT_type + Form: DW_FORM_ref_addr + - Code: 0x4 + Tag: DW_TAG_base_type + Children: DW_CHILDREN_no + Attributes: + - Attribute: DW_AT_name + Form: DW_FORM_strp + - Attribute: DW_AT_encoding + Form: DW_FORM_data1 + - Attribute: DW_AT_byte_size + Form: DW_FORM_data1 + - Code: 0x5 + Tag: DW_TAG_pointer_type + Children: DW_CHILDREN_no + Attributes: + - Attribute: DW_AT_type + Form: DW_FORM_ref_addr + - Code: 0x6 + Tag: DW_TAG_const_type + Children: DW_CHILDREN_no + Attributes: + - Attribute: DW_AT_type + Form: DW_FORM_ref_addr + debug_aranges: + - Length: 0x2C + Version: 2 + CuOffset: 0x0 + AddressSize: 0x8 + Descriptors: + - Address: 0x100003F94 + Length: 0x24 + debug_info: + - Length: 0x90 + Version: 4 + AbbrevTableID: 0 + AbbrOffset: 0x0 + AddrSize: 8 + Entries: + - AbbrCode: 0x1 + Values: + - Value: 0x1 + - Value: 0x1A + - Value: 0x30 + - Value: 0x39 + - Value: 0x98 + - Value: 0x0 + - Value: 0xA3 + - Value: 0x100003F94 + - Value: 0x24 + - AbbrCode: 0x2 + Values: + - Value: 0x100003F94 + - Value: 0x24 + - Value: 0x1 + - Value: 0x1 + BlockData: [ 0x6F ] + - Value: 0xAB + - Value: 0x1 + - Value: 0x1 + - Value: 0x76 + - Value: 0x1 + - AbbrCode: 0x3 + Values: + - Value: 0x2 + BlockData: [ 0x91, 0x18 ] + - Value: 0xB0 + - Value: 0x1 + - Value: 0x1 + - Value: 0x76 + - AbbrCode: 0x3 + Values: + - Value: 0x2 + BlockData: [ 0x91, 0x10 ] + - Value: 0xB5 + - Value: 0x1 + - Value: 0x1 + - Value: 0x7D + - AbbrCode: 0x3 + Values: + - Value: 0x2 + BlockData: [ 0x91, 0x8 ] + - Value: 0xBA + - Value: 0x1 + - Value: 0x1 + - Value: 0x7D + - AbbrCode: 0x0 + - AbbrCode: 0x4 + Values: + - Value: 0xBF + - Value: 0x5 + - Value: 0x4 + - AbbrCode: 0x5 + Values: + - Value: 0x82 + - AbbrCode: 0x5 + Values: + - Value: 0x87 + - AbbrCode: 0x6 + Values: + - Value: 0x8C + - AbbrCode: 0x4 + Values: + - Value: 0xC3 + - Value: 0x6 + - Value: 0x1 + - AbbrCode: 0x0 + debug_line: + - Length: 60 + Version: 4 + PrologueLength: 32 + MinInstLength: 1 + MaxOpsPerInst: 1 + DefaultIsStmt: 1 + LineBase: 251 + LineRange: 14 + OpcodeBase: 13 + StandardOpcodeLengths: [ 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1 ] + Files: + - Name: main.cpp + DirIdx: 0 + ModTime: 0 + Length: 0 + Opcodes: + - Opcode: DW_LNS_extended_op + ExtLen: 9 + SubOpcode: DW_LNE_set_address + Data: 4294983572 + - Opcode: DW_LNS_copy + Data: 0 + - Opcode: DW_LNS_set_column + Data: 3 + - Opcode: DW_LNS_set_prologue_end + Data: 0 + - Opcode: DW_LNS_const_add_pc + Data: 0 + - Opcode: 0xAD + Data: 0 + - Opcode: DW_LNS_advance_pc + Data: 8 + - Opcode: DW_LNS_extended_op + ExtLen: 1 + SubOpcode: DW_LNE_end_sequence + Data: 0 +... diff --git a/lldb/unittests/Core/CMakeLists.txt b/lldb/unittests/Core/CMakeLists.txt --- a/lldb/unittests/Core/CMakeLists.txt +++ b/lldb/unittests/Core/CMakeLists.txt @@ -2,6 +2,7 @@ CommunicationTest.cpp DiagnosticEventTest.cpp DumpDataExtractorTest.cpp + FileSpecListTest.cpp FormatEntityTest.cpp MangledTest.cpp ModuleSpecTest.cpp diff --git a/lldb/unittests/Core/FileSpecListTest.cpp b/lldb/unittests/Core/FileSpecListTest.cpp new file mode 100644 --- /dev/null +++ b/lldb/unittests/Core/FileSpecListTest.cpp @@ -0,0 +1,125 @@ +//===-- FileSpecListTest.cpp ----------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "gtest/gtest.h" + +#include "lldb/Core/FileSpecList.h" + +using namespace lldb_private; + +static FileSpec PosixSpec(llvm::StringRef path) { + return FileSpec(path, FileSpec::Style::posix); +} + +static FileSpec WindowsSpec(llvm::StringRef path) { + return FileSpec(path, FileSpec::Style::windows); +} + +TEST(FileSpecListTest, RelativePathMatchesPosix) { + + const FileSpec fullpath = PosixSpec("/build/src/main.cpp"); + const FileSpec relative = PosixSpec("./src/main.cpp"); + const FileSpec basename = PosixSpec("./main.cpp"); + const FileSpec full_wrong = PosixSpec("/other/wrong/main.cpp"); + const FileSpec rel_wrong = PosixSpec("./wrong/main.cpp"); + // Make sure these don't match "src/main.cpp" as we want to match full + // directories only + const FileSpec rel2_wrong = PosixSpec("asrc/main.cpp"); + const FileSpec rel3_wrong = PosixSpec("rc/main.cpp"); + + FileSpecList files; + files.Append(fullpath); + files.Append(relative); + files.Append(basename); + files.Append(full_wrong); + files.Append(rel_wrong); + files.Append(rel2_wrong); + files.Append(rel3_wrong); + + // Make sure the full path only matches the first entry + EXPECT_EQ((size_t)0, files.FindCompatibleIndex(0, fullpath)); + EXPECT_EQ((size_t)1, files.FindCompatibleIndex(1, fullpath)); + EXPECT_EQ((size_t)2, files.FindCompatibleIndex(2, fullpath)); + EXPECT_EQ((size_t)UINT32_MAX, files.FindCompatibleIndex(3, fullpath)); + // Make sure the relative path matches the all of the entries that contain + // the relative path + EXPECT_EQ((size_t)0, files.FindCompatibleIndex(0, relative)); + EXPECT_EQ((size_t)1, files.FindCompatibleIndex(1, relative)); + EXPECT_EQ((size_t)2, files.FindCompatibleIndex(2, relative)); + EXPECT_EQ((size_t)UINT32_MAX, files.FindCompatibleIndex(3, relative)); + + // Make sure looking file a file using the basename matches all entries + EXPECT_EQ((size_t)0, files.FindCompatibleIndex(0, basename)); + EXPECT_EQ((size_t)1, files.FindCompatibleIndex(1, basename)); + EXPECT_EQ((size_t)2, files.FindCompatibleIndex(2, basename)); + EXPECT_EQ((size_t)3, files.FindCompatibleIndex(3, basename)); + EXPECT_EQ((size_t)4, files.FindCompatibleIndex(4, basename)); + EXPECT_EQ((size_t)5, files.FindCompatibleIndex(5, basename)); + EXPECT_EQ((size_t)6, files.FindCompatibleIndex(6, basename)); + + // Make sure that paths that have a common suffix don't return values that + // don't match on directory delimiters. + EXPECT_EQ((size_t)2, files.FindCompatibleIndex(0, rel2_wrong)); + EXPECT_EQ((size_t)5, files.FindCompatibleIndex(3, rel2_wrong)); + EXPECT_EQ((size_t)UINT32_MAX, files.FindCompatibleIndex(6, rel2_wrong)); + + EXPECT_EQ((size_t)2, files.FindCompatibleIndex(0, rel3_wrong)); + EXPECT_EQ((size_t)6, files.FindCompatibleIndex(3, rel3_wrong)); +} + +TEST(FileSpecListTest, RelativePathMatchesWindows) { + + const FileSpec fullpath = WindowsSpec(R"(C:\build\src\main.cpp)"); + const FileSpec relative = WindowsSpec(R"(.\src\main.cpp)"); + const FileSpec basename = WindowsSpec(R"(.\main.cpp)"); + const FileSpec full_wrong = WindowsSpec(R"(\other\wrong\main.cpp)"); + const FileSpec rel_wrong = WindowsSpec(R"(.\wrong\main.cpp)"); + // Make sure these don't match "src\main.cpp" as we want to match full + // directories only + const FileSpec rel2_wrong = WindowsSpec(R"(asrc\main.cpp)"); + const FileSpec rel3_wrong = WindowsSpec(R"("rc\main.cpp)"); + + FileSpecList files; + files.Append(fullpath); + files.Append(relative); + files.Append(basename); + files.Append(full_wrong); + files.Append(rel_wrong); + files.Append(rel2_wrong); + files.Append(rel3_wrong); + + // Make sure the full path only matches the first entry + EXPECT_EQ((size_t)0, files.FindCompatibleIndex(0, fullpath)); + EXPECT_EQ((size_t)1, files.FindCompatibleIndex(1, fullpath)); + EXPECT_EQ((size_t)2, files.FindCompatibleIndex(2, fullpath)); + EXPECT_EQ((size_t)UINT32_MAX, files.FindCompatibleIndex(3, fullpath)); + // Make sure the relative path matches the all of the entries that contain + // the relative path + EXPECT_EQ((size_t)0, files.FindCompatibleIndex(0, relative)); + EXPECT_EQ((size_t)1, files.FindCompatibleIndex(1, relative)); + EXPECT_EQ((size_t)2, files.FindCompatibleIndex(2, relative)); + EXPECT_EQ((size_t)UINT32_MAX, files.FindCompatibleIndex(3, relative)); + + // Make sure looking file a file using the basename matches all entries + EXPECT_EQ((size_t)0, files.FindCompatibleIndex(0, basename)); + EXPECT_EQ((size_t)1, files.FindCompatibleIndex(1, basename)); + EXPECT_EQ((size_t)2, files.FindCompatibleIndex(2, basename)); + EXPECT_EQ((size_t)3, files.FindCompatibleIndex(3, basename)); + EXPECT_EQ((size_t)4, files.FindCompatibleIndex(4, basename)); + EXPECT_EQ((size_t)5, files.FindCompatibleIndex(5, basename)); + EXPECT_EQ((size_t)6, files.FindCompatibleIndex(6, basename)); + + // Make sure that paths that have a common suffix don't return values that + // don't match on directory delimiters. + EXPECT_EQ((size_t)2, files.FindCompatibleIndex(0, rel2_wrong)); + EXPECT_EQ((size_t)5, files.FindCompatibleIndex(3, rel2_wrong)); + EXPECT_EQ((size_t)UINT32_MAX, files.FindCompatibleIndex(6, rel2_wrong)); + + EXPECT_EQ((size_t)2, files.FindCompatibleIndex(0, rel3_wrong)); + EXPECT_EQ((size_t)6, files.FindCompatibleIndex(3, rel3_wrong)); +}