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
10 KiB
C++
303 lines
10 KiB
C++
//===-- BinaryHolder.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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This program is a utility that aims to be a dropin replacement for
|
|
// Darwin's dsymutil.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "BinaryHolder.h"
|
|
#include "llvm/Object/MachO.h"
|
|
#include "llvm/Support/WithColor.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
|
|
namespace llvm {
|
|
namespace dsymutil {
|
|
|
|
static std::pair<StringRef, StringRef>
|
|
getArchiveAndObjectName(StringRef Filename) {
|
|
StringRef Archive = Filename.substr(0, Filename.rfind('('));
|
|
StringRef Object = Filename.substr(Archive.size() + 1).drop_back();
|
|
return {Archive, Object};
|
|
}
|
|
|
|
static bool isArchive(StringRef Filename) { return Filename.ends_with(")"); }
|
|
|
|
static std::vector<MemoryBufferRef>
|
|
getMachOFatMemoryBuffers(StringRef Filename, MemoryBuffer &Mem,
|
|
object::MachOUniversalBinary &Fat) {
|
|
std::vector<MemoryBufferRef> Buffers;
|
|
StringRef FatData = Fat.getData();
|
|
for (auto It = Fat.begin_objects(), End = Fat.end_objects(); It != End;
|
|
++It) {
|
|
StringRef ObjData = FatData.substr(It->getOffset(), It->getSize());
|
|
Buffers.emplace_back(ObjData, Filename);
|
|
}
|
|
return Buffers;
|
|
}
|
|
|
|
BinaryHolder::BinaryHolder(IntrusiveRefCntPtr<vfs::FileSystem> VFS,
|
|
BinaryHolder::Options Opts)
|
|
: VFS(VFS), Opts(Opts) {}
|
|
|
|
Error BinaryHolder::ArchiveEntry::load(IntrusiveRefCntPtr<vfs::FileSystem> VFS,
|
|
StringRef Filename,
|
|
TimestampTy Timestamp, Options Opts) {
|
|
StringRef ArchiveFilename = getArchiveAndObjectName(Filename).first;
|
|
|
|
// Try to load archive and force it to be memory mapped.
|
|
auto ErrOrBuff = (ArchiveFilename == "-")
|
|
? MemoryBuffer::getSTDIN()
|
|
: VFS->getBufferForFile(ArchiveFilename, -1, false);
|
|
if (auto Err = ErrOrBuff.getError())
|
|
return errorCodeToError(Err);
|
|
|
|
MemBuffer = std::move(*ErrOrBuff);
|
|
|
|
if (Opts.Verbose)
|
|
WithColor::note() << "loaded archive '" << ArchiveFilename << "'\n";
|
|
|
|
// Load one or more archive buffers, depending on whether we're dealing with
|
|
// a fat binary.
|
|
std::vector<MemoryBufferRef> ArchiveBuffers;
|
|
|
|
auto ErrOrFat =
|
|
object::MachOUniversalBinary::create(MemBuffer->getMemBufferRef());
|
|
if (!ErrOrFat) {
|
|
consumeError(ErrOrFat.takeError());
|
|
ArchiveBuffers.push_back(MemBuffer->getMemBufferRef());
|
|
} else {
|
|
FatBinary = std::move(*ErrOrFat);
|
|
FatBinaryName = std::string(ArchiveFilename);
|
|
ArchiveBuffers =
|
|
getMachOFatMemoryBuffers(FatBinaryName, *MemBuffer, *FatBinary);
|
|
}
|
|
|
|
// Finally, try to load the archives.
|
|
Archives.reserve(ArchiveBuffers.size());
|
|
for (auto MemRef : ArchiveBuffers) {
|
|
auto ErrOrArchive = object::Archive::create(MemRef);
|
|
if (!ErrOrArchive)
|
|
return ErrOrArchive.takeError();
|
|
Archives.push_back(std::move(*ErrOrArchive));
|
|
}
|
|
|
|
return Error::success();
|
|
}
|
|
|
|
Error BinaryHolder::ObjectEntry::load(IntrusiveRefCntPtr<vfs::FileSystem> VFS,
|
|
StringRef Filename, TimestampTy Timestamp,
|
|
Options Opts) {
|
|
// Try to load regular binary and force it to be memory mapped.
|
|
auto ErrOrBuff = (Filename == "-")
|
|
? MemoryBuffer::getSTDIN()
|
|
: VFS->getBufferForFile(Filename, -1, false);
|
|
if (auto Err = ErrOrBuff.getError())
|
|
return errorCodeToError(Err);
|
|
|
|
if (Opts.Warn && Filename != "-" && Timestamp != sys::TimePoint<>()) {
|
|
llvm::ErrorOr<vfs::Status> Stat = VFS->status(Filename);
|
|
if (!Stat)
|
|
return errorCodeToError(Stat.getError());
|
|
if (Timestamp != std::chrono::time_point_cast<std::chrono::seconds>(
|
|
Stat->getLastModificationTime()))
|
|
WithColor::warning() << Filename
|
|
<< ": timestamp mismatch between object file ("
|
|
<< Stat->getLastModificationTime()
|
|
<< ") and debug map (" << Timestamp << ")\n";
|
|
}
|
|
|
|
MemBuffer = std::move(*ErrOrBuff);
|
|
|
|
if (Opts.Verbose)
|
|
WithColor::note() << "loaded object.\n";
|
|
|
|
// Load one or more object buffers, depending on whether we're dealing with a
|
|
// fat binary.
|
|
std::vector<MemoryBufferRef> ObjectBuffers;
|
|
|
|
auto ErrOrFat =
|
|
object::MachOUniversalBinary::create(MemBuffer->getMemBufferRef());
|
|
if (!ErrOrFat) {
|
|
consumeError(ErrOrFat.takeError());
|
|
ObjectBuffers.push_back(MemBuffer->getMemBufferRef());
|
|
} else {
|
|
FatBinary = std::move(*ErrOrFat);
|
|
FatBinaryName = std::string(Filename);
|
|
ObjectBuffers =
|
|
getMachOFatMemoryBuffers(FatBinaryName, *MemBuffer, *FatBinary);
|
|
}
|
|
|
|
Objects.reserve(ObjectBuffers.size());
|
|
for (auto MemRef : ObjectBuffers) {
|
|
auto ErrOrObjectFile = object::ObjectFile::createObjectFile(MemRef);
|
|
if (!ErrOrObjectFile)
|
|
return ErrOrObjectFile.takeError();
|
|
Objects.push_back(std::move(*ErrOrObjectFile));
|
|
}
|
|
|
|
return Error::success();
|
|
}
|
|
|
|
std::vector<const object::ObjectFile *>
|
|
BinaryHolder::ObjectEntry::getObjects() const {
|
|
std::vector<const object::ObjectFile *> Result;
|
|
Result.reserve(Objects.size());
|
|
for (auto &Object : Objects) {
|
|
Result.push_back(Object.get());
|
|
}
|
|
return Result;
|
|
}
|
|
Expected<const object::ObjectFile &>
|
|
BinaryHolder::ObjectEntry::getObject(const Triple &T) const {
|
|
for (const auto &Obj : Objects) {
|
|
if (const auto *MachO = dyn_cast<object::MachOObjectFile>(Obj.get())) {
|
|
if (MachO->getArchTriple().str() == T.str())
|
|
return *MachO;
|
|
} else if (Obj->getArch() == T.getArch())
|
|
return *Obj;
|
|
}
|
|
return errorCodeToError(object::object_error::arch_not_found);
|
|
}
|
|
|
|
Expected<const BinaryHolder::ObjectEntry &>
|
|
BinaryHolder::ArchiveEntry::getObjectEntry(StringRef Filename,
|
|
TimestampTy Timestamp,
|
|
Options Opts) {
|
|
StringRef ArchiveFilename;
|
|
StringRef ObjectFilename;
|
|
std::tie(ArchiveFilename, ObjectFilename) = getArchiveAndObjectName(Filename);
|
|
KeyTy Key = {ObjectFilename, Timestamp};
|
|
|
|
// Try the cache first.
|
|
std::lock_guard<std::mutex> Lock(MemberCacheMutex);
|
|
if (auto It = MemberCache.find(Key); It != MemberCache.end())
|
|
return *It->second;
|
|
|
|
// Create a new ObjectEntry, but don't add it to the cache yet. Loading of
|
|
// the archive members might fail and we don't want to lock the whole archive
|
|
// during this operation.
|
|
auto OE = std::make_unique<ObjectEntry>();
|
|
|
|
for (const auto &Archive : Archives) {
|
|
Error Err = Error::success();
|
|
for (const auto &Child : Archive->children(Err)) {
|
|
if (auto NameOrErr = Child.getName()) {
|
|
if (*NameOrErr == ObjectFilename) {
|
|
auto ModTimeOrErr = Child.getLastModified();
|
|
if (!ModTimeOrErr)
|
|
return ModTimeOrErr.takeError();
|
|
|
|
if (Timestamp != sys::TimePoint<>() &&
|
|
Timestamp != std::chrono::time_point_cast<std::chrono::seconds>(
|
|
ModTimeOrErr.get())) {
|
|
if (Opts.Verbose)
|
|
WithColor::warning()
|
|
<< *NameOrErr
|
|
<< ": timestamp mismatch between archive member ("
|
|
<< ModTimeOrErr.get() << ") and debug map (" << Timestamp
|
|
<< ")\n";
|
|
continue;
|
|
}
|
|
|
|
if (Opts.Verbose)
|
|
WithColor::note() << "found member in archive.\n";
|
|
|
|
auto ErrOrMem = Child.getMemoryBufferRef();
|
|
if (!ErrOrMem)
|
|
return ErrOrMem.takeError();
|
|
|
|
auto ErrOrObjectFile =
|
|
object::ObjectFile::createObjectFile(*ErrOrMem);
|
|
if (!ErrOrObjectFile)
|
|
return ErrOrObjectFile.takeError();
|
|
|
|
OE->Objects.push_back(std::move(*ErrOrObjectFile));
|
|
}
|
|
}
|
|
}
|
|
if (Err)
|
|
return std::move(Err);
|
|
}
|
|
|
|
if (OE->Objects.empty())
|
|
return errorCodeToError(errc::no_such_file_or_directory);
|
|
|
|
return *(MemberCache[Key] = std::move(OE));
|
|
}
|
|
|
|
Expected<const BinaryHolder::ObjectEntry &>
|
|
BinaryHolder::getObjectEntry(StringRef Filename, TimestampTy Timestamp) {
|
|
if (Opts.Verbose)
|
|
WithColor::note() << "trying to open '" << Filename << "'\n";
|
|
|
|
// If this is an archive, we might have either the object or the archive
|
|
// cached. In this case we can load it without accessing the file system.
|
|
if (isArchive(Filename)) {
|
|
StringRef ArchiveFilename = getArchiveAndObjectName(Filename).first;
|
|
std::lock_guard<std::mutex> Lock(ArchiveCacheMutex);
|
|
ArchiveRefCounter[ArchiveFilename]++;
|
|
if (auto It = ArchiveCache.find(ArchiveFilename);
|
|
It != ArchiveCache.end()) {
|
|
return It->second->getObjectEntry(Filename, Timestamp, Opts);
|
|
} else {
|
|
auto AE = std::make_unique<ArchiveEntry>();
|
|
auto Err = AE->load(VFS, Filename, Timestamp, Opts);
|
|
if (Err) {
|
|
// Don't return the error here: maybe the file wasn't an archive.
|
|
llvm::consumeError(std::move(Err));
|
|
} else {
|
|
auto &Cache = ArchiveCache[ArchiveFilename];
|
|
Cache = std::move(AE);
|
|
return Cache->getObjectEntry(Filename, Timestamp, Opts);
|
|
}
|
|
}
|
|
}
|
|
|
|
// If this is an object, we might have it cached. If not we'll have to load
|
|
// it from the file system and cache it now.
|
|
std::lock_guard<std::mutex> Lock(ObjectCacheMutex);
|
|
ObjectRefCounter[Filename]++;
|
|
if (!ObjectCache.count(Filename)) {
|
|
auto OE = std::make_unique<ObjectEntry>();
|
|
auto Err = OE->load(VFS, Filename, Timestamp, Opts);
|
|
if (Err)
|
|
return std::move(Err);
|
|
ObjectCache[Filename] = std::move(OE);
|
|
}
|
|
|
|
return *ObjectCache[Filename];
|
|
}
|
|
|
|
void BinaryHolder::clear() {
|
|
std::lock_guard<std::mutex> ArchiveLock(ArchiveCacheMutex);
|
|
std::lock_guard<std::mutex> ObjectLock(ObjectCacheMutex);
|
|
ArchiveCache.clear();
|
|
ObjectCache.clear();
|
|
}
|
|
|
|
void BinaryHolder::eraseObjectEntry(StringRef Filename) {
|
|
if (Opts.Verbose)
|
|
WithColor::note() << "erasing '" << Filename << "' from cache\n";
|
|
|
|
if (isArchive(Filename)) {
|
|
StringRef ArchiveFilename = getArchiveAndObjectName(Filename).first;
|
|
std::lock_guard<std::mutex> Lock(ArchiveCacheMutex);
|
|
if (--ArchiveRefCounter[ArchiveFilename] == 0)
|
|
ArchiveCache.erase(ArchiveFilename);
|
|
return;
|
|
}
|
|
|
|
std::lock_guard<std::mutex> Lock(ObjectCacheMutex);
|
|
if (--ObjectRefCounter[Filename] == 0)
|
|
ObjectCache.erase(Filename);
|
|
}
|
|
|
|
} // namespace dsymutil
|
|
} // namespace llvm
|