cb424d7448
verify-patch-sanity.py validates every active recipe .patch has internally- consistent hunk line counts — catching the 'malformed patch at line N' failure at commit/CI/preflight time instead of hours into a cook. This cycle hit that class three times (qtwaylandscanner, sddm, xwayland), each only discovered when cookbook tried to apply the patch. Running it across the repo found 29 latent malformed patches (validated against GNU patch: e.g. relibc/P3-sysv-ipc reproduces 'malformed patch at line 22'). They were harmless only because they sit in vendored recipes (baked, not re- applied) — but would fail on any version-bump re-derivation. --fix recounts the hunk headers (body untouched) and repaired all 29. Wired into build-preflight.sh (Phase 1.0D) and redbear-ci.yml, with a unit test (test-patch-sanity.sh). Skips archived/legacy trees and unvalidatable formats (empty placeholders, bare-@@ git hunks).
303 lines
11 KiB
C++
303 lines
11 KiB
C++
//===-- LineTableTest.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 "Plugins/ObjectFile/ELF/ObjectFileELF.h"
|
|
#include "TestingSupport/SubsystemRAII.h"
|
|
#include "TestingSupport/TestUtilities.h"
|
|
#include "lldb/Core/PluginManager.h"
|
|
#include "lldb/Symbol/CompileUnit.h"
|
|
#include "lldb/Symbol/SymbolFile.h"
|
|
#include "gtest/gtest.h"
|
|
#include <memory>
|
|
|
|
using namespace lldb;
|
|
using namespace llvm;
|
|
using namespace lldb_private;
|
|
|
|
namespace {
|
|
|
|
// A fake symbol file class to allow us to create the line table "the right
|
|
// way". Pretty much all methods except for GetCompileUnitAtIndex and
|
|
// GetNumCompileUnits are stubbed out.
|
|
class FakeSymbolFile : public SymbolFile {
|
|
public:
|
|
/// LLVM RTTI support.
|
|
/// \{
|
|
bool isA(const void *ClassID) const override {
|
|
return ClassID == &ID || SymbolFile::isA(ClassID);
|
|
}
|
|
static bool classof(const SymbolFile *obj) { return obj->isA(&ID); }
|
|
/// \}
|
|
|
|
static void Initialize() {
|
|
PluginManager::RegisterPlugin("FakeSymbolFile", "", CreateInstance,
|
|
DebuggerInitialize);
|
|
}
|
|
static void Terminate() { PluginManager::UnregisterPlugin(CreateInstance); }
|
|
|
|
void InjectCompileUnit(std::unique_ptr<CompileUnit> cu_up) {
|
|
m_cu_sp = std::move(cu_up);
|
|
}
|
|
|
|
private:
|
|
/// LLVM RTTI support.
|
|
static char ID;
|
|
|
|
static SymbolFile *CreateInstance(ObjectFileSP objfile_sp) {
|
|
return new FakeSymbolFile(std::move(objfile_sp));
|
|
}
|
|
static void DebuggerInitialize(Debugger &) {}
|
|
|
|
StringRef GetPluginName() override { return "FakeSymbolFile"; }
|
|
uint32_t GetAbilities() override { return UINT32_MAX; }
|
|
uint32_t CalculateAbilities() override { return UINT32_MAX; }
|
|
uint32_t GetNumCompileUnits() override { return 1; }
|
|
CompUnitSP GetCompileUnitAtIndex(uint32_t) override { return m_cu_sp; }
|
|
Symtab *GetSymtab(bool can_create = true) override { return nullptr; }
|
|
LanguageType ParseLanguage(CompileUnit &) override { return eLanguageTypeC; }
|
|
size_t ParseFunctions(CompileUnit &) override { return 0; }
|
|
bool ParseLineTable(CompileUnit &) override { return true; }
|
|
bool ParseDebugMacros(CompileUnit &) override { return true; }
|
|
bool ParseSupportFiles(CompileUnit &, SupportFileList &) override {
|
|
return true;
|
|
}
|
|
size_t ParseTypes(CompileUnit &) override { return 0; }
|
|
bool ParseImportedModules(const SymbolContext &,
|
|
std::vector<SourceModule> &) override {
|
|
return false;
|
|
}
|
|
size_t ParseBlocksRecursive(Function &) override { return 0; }
|
|
size_t ParseVariablesForContext(const SymbolContext &) override { return 0; }
|
|
Type *ResolveTypeUID(user_id_t) override { return nullptr; }
|
|
std::optional<ArrayInfo>
|
|
GetDynamicArrayInfoForUID(user_id_t, const ExecutionContext *) override {
|
|
return std::nullopt;
|
|
}
|
|
bool CompleteType(CompilerType &) override { return true; }
|
|
uint32_t ResolveSymbolContext(const Address &, SymbolContextItem,
|
|
SymbolContext &) override {
|
|
return 0;
|
|
}
|
|
void GetTypes(SymbolContextScope *, TypeClass, TypeList &) override {}
|
|
Expected<TypeSystemSP> GetTypeSystemForLanguage(LanguageType) override {
|
|
return createStringError(std::errc::not_supported, "");
|
|
}
|
|
const ObjectFile *GetObjectFile() const override {
|
|
return m_objfile_sp.get();
|
|
}
|
|
ObjectFile *GetObjectFile() override { return m_objfile_sp.get(); }
|
|
ObjectFile *GetMainObjectFile() override { return m_objfile_sp.get(); }
|
|
void SectionFileAddressesChanged() override {}
|
|
void Dump(Stream &) override {}
|
|
uint64_t GetDebugInfoSize(bool) override { return 0; }
|
|
bool GetDebugInfoIndexWasLoadedFromCache() const override { return false; }
|
|
void SetDebugInfoIndexWasLoadedFromCache() override {}
|
|
bool GetDebugInfoIndexWasSavedToCache() const override { return false; }
|
|
void SetDebugInfoIndexWasSavedToCache() override {}
|
|
bool GetDebugInfoHadFrameVariableErrors() const override { return false; }
|
|
void SetDebugInfoHadFrameVariableErrors() override {}
|
|
TypeSP MakeType(user_id_t, ConstString, std::optional<uint64_t>,
|
|
SymbolContextScope *, user_id_t, Type::EncodingDataType,
|
|
const Declaration &, const CompilerType &, Type::ResolveState,
|
|
uint32_t) override {
|
|
return nullptr;
|
|
}
|
|
TypeSP CopyType(const TypeSP &) override { return nullptr; }
|
|
|
|
FakeSymbolFile(ObjectFileSP objfile_sp)
|
|
: m_objfile_sp(std::move(objfile_sp)) {}
|
|
|
|
ObjectFileSP m_objfile_sp;
|
|
CompUnitSP m_cu_sp;
|
|
};
|
|
|
|
struct FakeModuleFixture {
|
|
TestFile file;
|
|
ModuleSP module_sp;
|
|
SectionSP text_sp;
|
|
LineTable *line_table;
|
|
};
|
|
|
|
class LineTableTest : public testing::Test {
|
|
SubsystemRAII<ObjectFileELF, FakeSymbolFile> subsystems;
|
|
};
|
|
|
|
class LineSequenceBuilder {
|
|
public:
|
|
std::vector<LineTable::Sequence> Build() { return std::move(m_sequences); }
|
|
enum Terminal : bool { Terminal = true };
|
|
void Entry(addr_t addr, bool terminal = false) {
|
|
LineTable::AppendLineEntryToSequence(
|
|
m_sequence, addr, /*line=*/1, /*column=*/0,
|
|
/*file_idx=*/0,
|
|
/*is_start_of_statement=*/false, /*is_start_of_basic_block=*/false,
|
|
/*is_prologue_end=*/false, /*is_epilogue_begin=*/false, terminal);
|
|
if (terminal)
|
|
m_sequences.push_back(std::move(m_sequence));
|
|
}
|
|
|
|
private:
|
|
std::vector<LineTable::Sequence> m_sequences;
|
|
LineTable::Sequence m_sequence;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
char FakeSymbolFile::ID;
|
|
|
|
static llvm::Expected<FakeModuleFixture>
|
|
CreateFakeModule(std::vector<LineTable::Sequence> line_sequences) {
|
|
Expected<TestFile> file = TestFile::fromYaml(R"(
|
|
--- !ELF
|
|
FileHeader:
|
|
Class: ELFCLASS64
|
|
Data: ELFDATA2LSB
|
|
Type: ET_EXEC
|
|
Machine: EM_386
|
|
Sections:
|
|
- Name: .text
|
|
Type: SHT_PROGBITS
|
|
Flags: [ SHF_ALLOC, SHF_EXECINSTR ]
|
|
AddressAlign: 0x0010
|
|
Address: 0x0000
|
|
Size: 0x1000
|
|
)");
|
|
if (!file)
|
|
return file.takeError();
|
|
|
|
auto module_sp = std::make_shared<Module>(file->moduleSpec());
|
|
SectionSP text_sp =
|
|
module_sp->GetSectionList()->FindSectionByName(ConstString(".text"));
|
|
if (!text_sp)
|
|
return createStringError("No .text");
|
|
|
|
auto cu_up = std::make_unique<CompileUnit>(module_sp, /*user_data=*/nullptr,
|
|
/*support_file_sp=*/nullptr,
|
|
/*uid=*/0, eLanguageTypeC,
|
|
/*is_optimized=*/eLazyBoolNo);
|
|
LineTable *line_table = new LineTable(cu_up.get(), std::move(line_sequences));
|
|
cu_up->SetLineTable(line_table);
|
|
cast<FakeSymbolFile>(module_sp->GetSymbolFile())
|
|
->InjectCompileUnit(std::move(cu_up));
|
|
|
|
return FakeModuleFixture{std::move(*file), std::move(module_sp),
|
|
std::move(text_sp), line_table};
|
|
}
|
|
|
|
TEST_F(LineTableTest, lower_bound) {
|
|
LineSequenceBuilder builder;
|
|
builder.Entry(0);
|
|
builder.Entry(10);
|
|
builder.Entry(20, LineSequenceBuilder::Terminal);
|
|
builder.Entry(20); // Starts right after the previous sequence.
|
|
builder.Entry(30, LineSequenceBuilder::Terminal);
|
|
builder.Entry(40); // Gap after the previous sequence.
|
|
builder.Entry(50, LineSequenceBuilder::Terminal);
|
|
|
|
llvm::Expected<FakeModuleFixture> fixture = CreateFakeModule(builder.Build());
|
|
ASSERT_THAT_EXPECTED(fixture, llvm::Succeeded());
|
|
|
|
LineTable *table = fixture->line_table;
|
|
|
|
auto make_addr = [&](addr_t addr) { return Address(fixture->text_sp, addr); };
|
|
|
|
EXPECT_EQ(table->lower_bound(make_addr(0)), 0u);
|
|
EXPECT_EQ(table->lower_bound(make_addr(9)), 0u);
|
|
EXPECT_EQ(table->lower_bound(make_addr(10)), 1u);
|
|
EXPECT_EQ(table->lower_bound(make_addr(19)), 1u);
|
|
|
|
// Skips over the terminal entry.
|
|
EXPECT_EQ(table->lower_bound(make_addr(20)), 3u);
|
|
EXPECT_EQ(table->lower_bound(make_addr(29)), 3u);
|
|
|
|
// In case there's no "real" entry at this address, the function returns the
|
|
// first real entry.
|
|
EXPECT_EQ(table->lower_bound(make_addr(30)), 5u);
|
|
EXPECT_EQ(table->lower_bound(make_addr(40)), 5u);
|
|
|
|
// In a gap, return the first entry after the gap.
|
|
EXPECT_EQ(table->lower_bound(make_addr(39)), 5u);
|
|
|
|
// And if there's no such entry, return the size of the list.
|
|
EXPECT_EQ(table->lower_bound(make_addr(50)), table->GetSize());
|
|
EXPECT_EQ(table->lower_bound(make_addr(59)), table->GetSize());
|
|
}
|
|
|
|
TEST_F(LineTableTest, GetLineEntryIndexRange) {
|
|
LineSequenceBuilder builder;
|
|
builder.Entry(0);
|
|
builder.Entry(10);
|
|
builder.Entry(20, LineSequenceBuilder::Terminal);
|
|
|
|
llvm::Expected<FakeModuleFixture> fixture = CreateFakeModule(builder.Build());
|
|
ASSERT_THAT_EXPECTED(fixture, llvm::Succeeded());
|
|
|
|
LineTable *table = fixture->line_table;
|
|
|
|
auto make_range = [&](addr_t addr, addr_t size) {
|
|
return AddressRange(fixture->text_sp, addr, size);
|
|
};
|
|
|
|
EXPECT_THAT(table->GetLineEntryIndexRange(make_range(0, 10)),
|
|
testing::Pair(0, 1));
|
|
EXPECT_THAT(table->GetLineEntryIndexRange(make_range(0, 20)),
|
|
testing::Pair(0, 3)); // Includes the terminal entry.
|
|
// Partial overlap on one side.
|
|
EXPECT_THAT(table->GetLineEntryIndexRange(make_range(3, 7)),
|
|
testing::Pair(0, 1));
|
|
// On the other side
|
|
EXPECT_THAT(table->GetLineEntryIndexRange(make_range(0, 15)),
|
|
testing::Pair(0, 2));
|
|
// On both sides
|
|
EXPECT_THAT(table->GetLineEntryIndexRange(make_range(2, 3)),
|
|
testing::Pair(0, 1));
|
|
// Empty ranges
|
|
EXPECT_THAT(table->GetLineEntryIndexRange(make_range(0, 0)),
|
|
testing::Pair(0, 0));
|
|
EXPECT_THAT(table->GetLineEntryIndexRange(make_range(5, 0)),
|
|
testing::Pair(0, 0));
|
|
EXPECT_THAT(table->GetLineEntryIndexRange(make_range(10, 0)),
|
|
testing::Pair(1, 1));
|
|
}
|
|
|
|
TEST_F(LineTableTest, FindLineEntryByAddress) {
|
|
LineSequenceBuilder builder;
|
|
builder.Entry(0);
|
|
builder.Entry(10);
|
|
builder.Entry(20, LineSequenceBuilder::Terminal);
|
|
builder.Entry(20); // Starts right after the previous sequence.
|
|
builder.Entry(30, LineSequenceBuilder::Terminal);
|
|
builder.Entry(40); // Gap after the previous sequence.
|
|
builder.Entry(50, LineSequenceBuilder::Terminal);
|
|
|
|
llvm::Expected<FakeModuleFixture> fixture = CreateFakeModule(builder.Build());
|
|
ASSERT_THAT_EXPECTED(fixture, llvm::Succeeded());
|
|
|
|
LineTable *table = fixture->line_table;
|
|
|
|
auto find = [&](addr_t addr) -> std::tuple<addr_t, addr_t, bool> {
|
|
LineEntry entry;
|
|
if (!table->FindLineEntryByAddress(Address(fixture->text_sp, addr), entry))
|
|
return {LLDB_INVALID_ADDRESS, LLDB_INVALID_ADDRESS, false};
|
|
return {entry.range.GetBaseAddress().GetFileAddress(),
|
|
entry.range.GetByteSize(),
|
|
static_cast<bool>(entry.is_terminal_entry)};
|
|
};
|
|
|
|
EXPECT_THAT(find(0), testing::FieldsAre(0, 10, false));
|
|
EXPECT_THAT(find(9), testing::FieldsAre(0, 10, false));
|
|
EXPECT_THAT(find(10), testing::FieldsAre(10, 10, false));
|
|
EXPECT_THAT(find(19), testing::FieldsAre(10, 10, false));
|
|
EXPECT_THAT(find(20), testing::FieldsAre(20, 10, false));
|
|
EXPECT_THAT(find(30), testing::FieldsAre(LLDB_INVALID_ADDRESS,
|
|
LLDB_INVALID_ADDRESS, false));
|
|
EXPECT_THAT(find(40), testing::FieldsAre(40, 10, false));
|
|
EXPECT_THAT(find(50), testing::FieldsAre(LLDB_INVALID_ADDRESS,
|
|
LLDB_INVALID_ADDRESS, false));
|
|
}
|