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).
236 lines
6.8 KiB
C++
236 lines
6.8 KiB
C++
//===- StringTableBuilder.cpp - String table building utility -------------===//
|
|
//
|
|
// 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 "llvm/MC/StringTableBuilder.h"
|
|
#include "llvm/ADT/ArrayRef.h"
|
|
#include "llvm/ADT/CachedHashString.h"
|
|
#include "llvm/ADT/SmallString.h"
|
|
#include "llvm/ADT/StringRef.h"
|
|
#include "llvm/BinaryFormat/COFF.h"
|
|
#include "llvm/Support/Endian.h"
|
|
#include "llvm/Support/MathExtras.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
#include <cassert>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
using namespace llvm;
|
|
|
|
StringTableBuilder::~StringTableBuilder() = default;
|
|
|
|
void StringTableBuilder::initSize() {
|
|
// Account for leading bytes in table so that offsets returned from add are
|
|
// correct.
|
|
switch (K) {
|
|
case RAW:
|
|
case DWARF:
|
|
Size = 0;
|
|
break;
|
|
case MachOLinked:
|
|
case MachO64Linked:
|
|
Size = 2;
|
|
break;
|
|
case MachO:
|
|
case MachO64:
|
|
case ELF:
|
|
case DXContainer:
|
|
// Start the table with a NUL byte.
|
|
Size = 1;
|
|
break;
|
|
case XCOFF:
|
|
case WinCOFF:
|
|
// Make room to write the table size later.
|
|
Size = 4;
|
|
break;
|
|
}
|
|
}
|
|
|
|
StringTableBuilder::StringTableBuilder(Kind K, Align Alignment)
|
|
: K(K), Alignment(Alignment) {
|
|
initSize();
|
|
}
|
|
|
|
void StringTableBuilder::write(raw_ostream &OS) const {
|
|
assert(isFinalized());
|
|
SmallString<0> Data;
|
|
Data.resize(getSize());
|
|
write((uint8_t *)Data.data());
|
|
OS << Data;
|
|
}
|
|
|
|
using StringPair = std::pair<CachedHashStringRef, size_t>;
|
|
|
|
void StringTableBuilder::write(uint8_t *Buf) const {
|
|
assert(isFinalized());
|
|
for (const StringPair &P : StringIndexMap) {
|
|
StringRef Data = P.first.val();
|
|
if (!Data.empty())
|
|
memcpy(Buf + P.second, Data.data(), Data.size());
|
|
}
|
|
// The COFF formats store the size of the string table in the first 4 bytes.
|
|
// For Windows, the format is little-endian; for AIX, it is big-endian.
|
|
if (K == WinCOFF)
|
|
support::endian::write32le(Buf, Size);
|
|
else if (K == XCOFF)
|
|
support::endian::write32be(Buf, Size);
|
|
}
|
|
|
|
// Returns the character at Pos from end of a string.
|
|
static int charTailAt(StringPair *P, size_t Pos) {
|
|
StringRef S = P->first.val();
|
|
if (Pos >= S.size())
|
|
return -1;
|
|
return (unsigned char)S[S.size() - Pos - 1];
|
|
}
|
|
|
|
// Three-way radix quicksort. This is much faster than std::sort with strcmp
|
|
// because it does not compare characters that we already know the same.
|
|
static void multikeySort(MutableArrayRef<StringPair *> Vec, int Pos) {
|
|
tailcall:
|
|
if (Vec.size() <= 1)
|
|
return;
|
|
|
|
// Partition items so that items in [0, I) are greater than the pivot,
|
|
// [I, J) are the same as the pivot, and [J, Vec.size()) are less than
|
|
// the pivot.
|
|
int Pivot = charTailAt(Vec[0], Pos);
|
|
size_t I = 0;
|
|
size_t J = Vec.size();
|
|
for (size_t K = 1; K < J;) {
|
|
int C = charTailAt(Vec[K], Pos);
|
|
if (C > Pivot)
|
|
std::swap(Vec[I++], Vec[K++]);
|
|
else if (C < Pivot)
|
|
std::swap(Vec[--J], Vec[K]);
|
|
else
|
|
K++;
|
|
}
|
|
|
|
multikeySort(Vec.slice(0, I), Pos);
|
|
multikeySort(Vec.slice(J), Pos);
|
|
|
|
// multikeySort(Vec.slice(I, J - I), Pos + 1), but with
|
|
// tail call optimization.
|
|
if (Pivot != -1) {
|
|
Vec = Vec.slice(I, J - I);
|
|
++Pos;
|
|
goto tailcall;
|
|
}
|
|
}
|
|
|
|
void StringTableBuilder::finalize() {
|
|
assert(K != DWARF);
|
|
finalizeStringTable(/*Optimize=*/true);
|
|
}
|
|
|
|
void StringTableBuilder::finalizeInOrder() {
|
|
finalizeStringTable(/*Optimize=*/false);
|
|
}
|
|
|
|
void StringTableBuilder::finalizeStringTable(bool Optimize) {
|
|
Finalized = true;
|
|
|
|
if (Optimize && StringIndexMap.size()) {
|
|
std::vector<StringPair *> Strings;
|
|
Strings.reserve(StringIndexMap.size());
|
|
for (StringPair &P : StringIndexMap)
|
|
Strings.push_back(&P);
|
|
|
|
size_t RangeBegin = 0;
|
|
MutableArrayRef<StringPair *> StringsRef(Strings);
|
|
if (StringPriorityMap.size()) {
|
|
llvm::sort(Strings,
|
|
[&](const StringPair *LHS, const StringPair *RHS) -> bool {
|
|
return StringPriorityMap.lookup(LHS->first) >
|
|
StringPriorityMap.lookup(RHS->first);
|
|
});
|
|
uint8_t RangePriority = StringPriorityMap.lookup(Strings[0]->first);
|
|
for (size_t I = 1, E = Strings.size(); I != E && RangePriority; ++I) {
|
|
uint8_t Priority = StringPriorityMap.lookup(Strings[I]->first);
|
|
if (Priority != RangePriority) {
|
|
multikeySort(StringsRef.slice(RangeBegin, I - RangeBegin), 0);
|
|
RangePriority = Priority;
|
|
RangeBegin = I;
|
|
}
|
|
}
|
|
}
|
|
multikeySort(StringsRef.slice(RangeBegin), 0);
|
|
initSize();
|
|
|
|
StringRef Previous;
|
|
for (StringPair *P : Strings) {
|
|
StringRef S = P->first.val();
|
|
if (Previous.ends_with(S)) {
|
|
size_t Pos = Size - S.size() - (K != RAW);
|
|
if (isAligned(Alignment, Pos)) {
|
|
P->second = Pos;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
Size = alignTo(Size, Alignment);
|
|
P->second = Size;
|
|
|
|
Size += S.size();
|
|
if (K != RAW)
|
|
++Size;
|
|
Previous = S;
|
|
}
|
|
}
|
|
|
|
if (K == MachO || K == MachOLinked || K == DXContainer)
|
|
Size = alignTo(Size, 4); // Pad to multiple of 4.
|
|
if (K == MachO64 || K == MachO64Linked)
|
|
Size = alignTo(Size, 8); // Pad to multiple of 8.
|
|
|
|
// According to ld64 the string table of a final linked Mach-O binary starts
|
|
// with " ", i.e. the first byte is ' ' and the second byte is zero. In
|
|
// 'initSize()' we reserved the first two bytes for holding this string.
|
|
if (K == MachOLinked || K == MachO64Linked)
|
|
StringIndexMap[CachedHashStringRef(" ")] = 0;
|
|
|
|
// The first byte in an ELF string table must be null, according to the ELF
|
|
// specification. In 'initSize()' we reserved the first byte to hold null for
|
|
// this purpose and here we actually add the string to allow 'getOffset()' to
|
|
// be called on an empty string.
|
|
if (K == ELF)
|
|
StringIndexMap[CachedHashStringRef("")] = 0;
|
|
}
|
|
|
|
void StringTableBuilder::clear() {
|
|
Finalized = false;
|
|
StringIndexMap.clear();
|
|
}
|
|
|
|
size_t StringTableBuilder::getOffset(CachedHashStringRef S) const {
|
|
assert(isFinalized());
|
|
auto I = StringIndexMap.find(S);
|
|
assert(I != StringIndexMap.end() && "String is not in table!");
|
|
return I->second;
|
|
}
|
|
|
|
size_t StringTableBuilder::add(CachedHashStringRef S, uint8_t Priority) {
|
|
if (K == WinCOFF)
|
|
assert(S.size() > COFF::NameSize && "Short string in COFF string table!");
|
|
|
|
assert(!isFinalized());
|
|
if (Priority)
|
|
StringPriorityMap[S] = std::max(Priority, StringPriorityMap[S]);
|
|
|
|
auto P = StringIndexMap.try_emplace(S);
|
|
if (P.second) {
|
|
size_t Start = alignTo(Size, Alignment);
|
|
P.first->second = Start;
|
|
Size = Start + S.size() + (K != RAW);
|
|
}
|
|
return P.first->second;
|
|
}
|