[llvm] [ProfileData] Bound binary sample profile primitive reads (PR #215476)
Sergey Shcherbinin via llvm-commits
llvm-commits at lists.llvm.org
Thu Aug 13 21:57:00 PDT 2026
https://github.com/SergeyShch01 updated https://github.com/llvm/llvm-project/pull/215476
>From 2f9a8e34ea30ba1591120ce6f7b3eeeba947b361 Mon Sep 17 00:00:00 2001
From: Sergey Shcherbinin <sscherbinin at nvidia.com>
Date: Tue, 4 Aug 2026 14:35:13 +0400
Subject: [PATCH] [ProfileData] Bound binary sample profile primitive reads
Fix pre-existing latent memory-safety issues that could cause out-of-bounds
reads or invalid pointer arithmetic on malformed, truncated, or
non-null-terminated buffers:
* unbounded ULEB128 decoding in readNumber() and Raw/Ext format probes;
* unbounded terminator searches in readString() and GCC format detection;
* unsafe bounds checking in readUnencodedNumber().
Use bounded decoding, memchr, overflow-safe size checks, and typed ULEB128
errors. Preserve the existing decodeULEB128 interface and avoid test-only
ABI exports.
Add functional and guard-page tests that detect boundary regressions
without requiring sanitizers.
---
llvm/include/llvm/Support/LEB128.h | 46 ++-
llvm/lib/ProfileData/SampleProfReader.cpp | 82 +++--
llvm/unittests/ProfileData/SampleProfTest.cpp | 326 ++++++++++++++++++
llvm/unittests/Support/LEB128Test.cpp | 23 ++
4 files changed, 441 insertions(+), 36 deletions(-)
diff --git a/llvm/include/llvm/Support/LEB128.h b/llvm/include/llvm/Support/LEB128.h
index bf45131582005..8d5788e6034dd 100644
--- a/llvm/include/llvm/Support/LEB128.h
+++ b/llvm/include/llvm/Support/LEB128.h
@@ -123,13 +123,31 @@ inline unsigned encodeULEB128(uint64_t Value, uint8_t *p,
return (unsigned)(p - orig_p);
}
-/// Utility function to decode a ULEB128 value.
+/// Identifies why ULEB128 decoding failed.
+enum class ULEB128DecodeError {
+ /// No decoding error has been reported.
+ None,
+ /// The encoding requires bytes beyond the supplied buffer.
+ UnexpectedEnd,
+ /// The encoded value does not fit in uint64_t.
+ TooBig,
+};
+
+/// Utility function to decode a ULEB128 value and report a typed error.
///
-/// If \p error is non-null, it will point to a static error message,
-/// if an error occurred. It will not be modified on success.
-inline uint64_t decodeULEB128(const uint8_t *p, unsigned *n = nullptr,
- const uint8_t *end = nullptr,
- const char **error = nullptr) {
+/// \p p is the first byte of the encoding.
+/// If \p n is non-null, it receives the number of bytes consumed.
+/// If \p end is non-null, decoding will not read at or beyond that address.
+/// If \p error is non-null, it will point to a static error message if an error
+/// occurred. It will not be modified on success.
+/// If \p errorCode is non-null, it will identify the decoding outcome. It is
+/// set to \c None on entry and only changed when decoding fails.
+inline uint64_t decodeULEB128(const uint8_t *p, unsigned *n, const uint8_t *end,
+ const char **error,
+ ULEB128DecodeError *errorCode) {
+ if (errorCode)
+ *errorCode = ULEB128DecodeError::None;
+
const uint8_t *orig_p = p;
uint64_t Value = 0;
unsigned Shift = 0;
@@ -137,6 +155,8 @@ inline uint64_t decodeULEB128(const uint8_t *p, unsigned *n = nullptr,
if (LLVM_UNLIKELY(p == end)) {
if (error)
*error = "malformed uleb128, extends past end";
+ if (errorCode)
+ *errorCode = ULEB128DecodeError::UnexpectedEnd;
Value = 0;
break;
}
@@ -146,6 +166,8 @@ inline uint64_t decodeULEB128(const uint8_t *p, unsigned *n = nullptr,
(Shift > 63 && Slice != 0))) {
if (error)
*error = "uleb128 too big for uint64";
+ if (errorCode)
+ *errorCode = ULEB128DecodeError::TooBig;
Value = 0;
break;
}
@@ -162,6 +184,18 @@ inline uint64_t decodeULEB128(const uint8_t *p, unsigned *n = nullptr,
return Value;
}
+/// Utility function to decode a ULEB128 value.
+///
+/// If \p n is non-null, it receives the number of bytes consumed on success.
+/// If \p end is non-null, decoding will not read at or beyond that address.
+/// If \p error is non-null, it will point to a static error message if an error
+/// occurred. It will not be modified on success.
+inline uint64_t decodeULEB128(const uint8_t *p, unsigned *n = nullptr,
+ const uint8_t *end = nullptr,
+ const char **error = nullptr) {
+ return decodeULEB128(p, n, end, error, nullptr);
+}
+
/// Utility function to decode a SLEB128 value.
///
/// If \p error is non-null, it will point to a static error message,
diff --git a/llvm/lib/ProfileData/SampleProfReader.cpp b/llvm/lib/ProfileData/SampleProfReader.cpp
index 8f50873c72d03..2dc5e931486f0 100644
--- a/llvm/lib/ProfileData/SampleProfReader.cpp
+++ b/llvm/lib/ProfileData/SampleProfReader.cpp
@@ -40,6 +40,7 @@
#include <algorithm>
#include <cstddef>
#include <cstdint>
+#include <cstring>
#include <limits>
#include <memory>
#include <system_error>
@@ -549,43 +550,57 @@ bool SampleProfileReaderText::hasFormat(const MemoryBuffer &Buffer) {
return result;
}
+/// Emit a reader diagnostic for \p ProfError and return its error code.
+static std::error_code diagnoseReaderError(const SampleProfileReader &Reader,
+ sampleprof_error ProfError) {
+ std::error_code EC = ProfError;
+ Reader.reportError(0, EC.message());
+ return EC;
+}
+
template <typename T> ErrorOr<T> SampleProfileReaderBinary::readNumber() {
+ if (Data >= End)
+ return diagnoseReaderError(*this, sampleprof_error::truncated);
+
unsigned NumBytesRead = 0;
- uint64_t Val = decodeULEB128(Data, &NumBytesRead);
+ ULEB128DecodeError DecodeError = ULEB128DecodeError::None;
+ uint64_t Val = decodeULEB128(Data, &NumBytesRead, End, nullptr, &DecodeError);
- if (Val > std::numeric_limits<T>::max()) {
- std::error_code EC = sampleprof_error::malformed;
- reportError(0, EC.message());
- return EC;
- } else if (Data + NumBytesRead > End) {
- std::error_code EC = sampleprof_error::truncated;
- reportError(0, EC.message());
- return EC;
+ // Preserve the distinction between incomplete input and an invalid value.
+ switch (DecodeError) {
+ case ULEB128DecodeError::None:
+ break;
+ case ULEB128DecodeError::UnexpectedEnd:
+ return diagnoseReaderError(*this, sampleprof_error::truncated);
+ case ULEB128DecodeError::TooBig:
+ return diagnoseReaderError(*this, sampleprof_error::malformed);
}
+ if (Val > std::numeric_limits<T>::max())
+ return diagnoseReaderError(*this, sampleprof_error::malformed);
+
Data += NumBytesRead;
return static_cast<T>(Val);
}
ErrorOr<StringRef> SampleProfileReaderBinary::readString() {
- StringRef Str(reinterpret_cast<const char *>(Data));
- if (Data + Str.size() + 1 > End) {
- std::error_code EC = sampleprof_error::truncated;
- reportError(0, EC.message());
- return EC;
- }
+ if (Data >= End)
+ return diagnoseReaderError(*this, sampleprof_error::truncated);
- Data += Str.size() + 1;
+ const auto *Terminator = static_cast<const uint8_t *>(
+ std::memchr(Data, 0, static_cast<size_t>(End - Data)));
+ if (!Terminator)
+ return diagnoseReaderError(*this, sampleprof_error::truncated);
+
+ StringRef Str(reinterpret_cast<const char *>(Data), Terminator - Data);
+ Data = Terminator + 1;
return Str;
}
template <typename T>
ErrorOr<T> SampleProfileReaderBinary::readUnencodedNumber() {
- if (Data + sizeof(T) > End) {
- std::error_code EC = sampleprof_error::truncated;
- reportError(0, EC.message());
- return EC;
- }
+ if (Data > End || static_cast<size_t>(End - Data) < sizeof(T))
+ return diagnoseReaderError(*this, sampleprof_error::truncated);
using namespace support;
T Val = endian::readNext<T, llvm::endianness::little>(Data);
@@ -1854,18 +1869,23 @@ std::error_code SampleProfileReaderBinary::readSummary() {
return sampleprof_error::success;
}
-bool SampleProfileReaderRawBinary::hasFormat(const MemoryBuffer &Buffer) {
+/// Return whether Buffer starts with ExpectedMagic without reading beyond it.
+static bool hasBinaryFormat(const MemoryBuffer &Buffer,
+ uint64_t ExpectedMagic) {
const uint8_t *Data =
reinterpret_cast<const uint8_t *>(Buffer.getBufferStart());
- uint64_t Magic = decodeULEB128(Data);
- return Magic == SPMagic();
+ const uint8_t *End = reinterpret_cast<const uint8_t *>(Buffer.getBufferEnd());
+ ULEB128DecodeError DecodeError = ULEB128DecodeError::None;
+ uint64_t Magic = decodeULEB128(Data, nullptr, End, nullptr, &DecodeError);
+ return DecodeError == ULEB128DecodeError::None && Magic == ExpectedMagic;
+}
+
+bool SampleProfileReaderRawBinary::hasFormat(const MemoryBuffer &Buffer) {
+ return hasBinaryFormat(Buffer, SPMagic());
}
bool SampleProfileReaderExtBinary::hasFormat(const MemoryBuffer &Buffer) {
- const uint8_t *Data =
- reinterpret_cast<const uint8_t *>(Buffer.getBufferStart());
- uint64_t Magic = decodeULEB128(Data);
- return Magic == SPMagic(SPF_Ext_Binary);
+ return hasBinaryFormat(Buffer, SPMagic(SPF_Ext_Binary));
}
std::error_code SampleProfileReaderGCC::skipNextWord() {
@@ -2108,8 +2128,10 @@ std::error_code SampleProfileReaderGCC::readImpl() {
}
bool SampleProfileReaderGCC::hasFormat(const MemoryBuffer &Buffer) {
- StringRef Magic(Buffer.getBufferStart());
- return Magic == "adcg*704";
+ StringRef Contents = Buffer.getBuffer();
+ // Preserve exact magic matching, including a magic-only eight-byte buffer.
+ return Contents.starts_with("adcg*704") &&
+ (Contents.size() == 8 || Contents[8] == '\0');
}
void SampleProfileReaderItaniumRemapper::applyRemapping(LLVMContext &Ctx) {
diff --git a/llvm/unittests/ProfileData/SampleProfTest.cpp b/llvm/unittests/ProfileData/SampleProfTest.cpp
index fece113cc6ba6..3d8560357e2f4 100644
--- a/llvm/unittests/ProfileData/SampleProfTest.cpp
+++ b/llvm/unittests/ProfileData/SampleProfTest.cpp
@@ -9,6 +9,7 @@
#include "llvm/ProfileData/SampleProf.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringRef.h"
+#include "llvm/Config/llvm-config.h"
#include "llvm/IR/DebugInfoMetadata.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Metadata.h"
@@ -16,17 +17,31 @@
#include "llvm/ProfileData/SampleProfReader.h"
#include "llvm/ProfileData/SampleProfWriter.h"
#include "llvm/Support/Casting.h"
+#include "llvm/Support/Error.h"
#include "llvm/Support/ErrorOr.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/LEB128.h"
+#include "llvm/Support/Memory.h"
#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/Process.h"
#include "llvm/Support/VirtualFileSystem.h"
#include "llvm/Support/raw_ostream.h"
+#include "llvm/Testing/Support/Error.h"
#include "llvm/Testing/Support/SupportHelpers.h"
#include "gtest/gtest.h"
+#include <algorithm>
+#include <memory>
#include <string>
+#include <system_error>
#include <vector>
+#if defined(_WIN32)
+#include "llvm/Support/Windows/WindowsSupport.h"
+#include "llvm/Support/WindowsError.h"
+#elif defined(LLVM_ON_UNIX) && !defined(__MVS__)
+#include <sys/mman.h>
+#endif
+
using namespace llvm;
using namespace sampleprof;
@@ -41,6 +56,115 @@ static ::testing::AssertionResult NoError(std::error_code EC) {
namespace {
+/// Expose string decoding for focused buffer-boundary tests.
+class StringDataTestReader final : public SampleProfileReaderRawBinary {
+public:
+ /// Construct a reader over the supplied test buffer.
+ StringDataTestReader(std::unique_ptr<MemoryBuffer> Buffer,
+ LLVMContext &Context)
+ : SampleProfileReaderRawBinary(std::move(Buffer), Context) {}
+
+ /// Decode one string using the production binary-reader path.
+ ErrorOr<StringRef> readCString() {
+ Data = reinterpret_cast<const uint8_t *>(Buffer->getBufferStart());
+ End = reinterpret_cast<const uint8_t *>(Buffer->getBufferEnd());
+ return readString();
+ }
+
+ /// Decode one string with the cursor at the logical buffer end.
+ ErrorOr<StringRef> readCStringAtEnd() {
+ Data = reinterpret_cast<const uint8_t *>(Buffer->getBufferEnd());
+ End = Data;
+ return readString();
+ }
+
+ /// Return whether the decoder consumed the complete test buffer.
+ bool atEnd() const { return Data == End; }
+};
+
+/// Expose ExtBinary header reading for fixed-width number boundary tests.
+class ExtBinaryDataTestReader final : public SampleProfileReaderExtBinary {
+public:
+ /// Construct a reader over the supplied test buffer.
+ ExtBinaryDataTestReader(std::unique_ptr<MemoryBuffer> Buffer,
+ LLVMContext &Context)
+ : SampleProfileReaderExtBinary(std::move(Buffer), Context) {}
+
+ /// Read the ExtBinary header using the production reader path.
+ std::error_code readProfileHeader() { return readHeader(); }
+};
+
+/// Create a non-owning MemoryBuffer view over \p Bytes.
+static std::unique_ptr<MemoryBuffer>
+createTestMemoryBuffer(ArrayRef<uint8_t> Bytes) {
+ return MemoryBuffer::getMemBuffer(
+ StringRef(reinterpret_cast<const char *>(Bytes.data()), Bytes.size()),
+ "binary-data", /*RequiresNullTerminator=*/false);
+}
+
+/// Own a buffer ending immediately before an inaccessible guard page.
+struct GuardedMemoryBuffer {
+ sys::OwningMemoryBlock Storage;
+ std::unique_ptr<MemoryBuffer> Buffer;
+};
+
+/// Return whether this host exposes an API for no-access memory pages.
+static constexpr bool guardPagesSupported() {
+#if defined(_WIN32) || (defined(LLVM_ON_UNIX) && !defined(__MVS__))
+ return true;
+#else
+ return false;
+#endif
+}
+
+/// Make \p GuardPage inaccessible so an out-of-bounds read faults immediately.
+static std::error_code protectGuardPage(sys::MemoryBlock GuardPage) {
+#if defined(_WIN32)
+ DWORD OldProtection;
+ if (!VirtualProtect(GuardPage.base(), GuardPage.allocatedSize(),
+ PAGE_NOACCESS, &OldProtection))
+ return mapLastWindowsError();
+ return std::error_code();
+#elif defined(LLVM_ON_UNIX) && !defined(__MVS__)
+ if (::mprotect(GuardPage.base(), GuardPage.allocatedSize(), PROT_NONE))
+ return errnoAsErrorCode();
+ return std::error_code();
+#else
+ return std::make_error_code(std::errc::not_supported);
+#endif
+}
+
+/// Copy \p Bytes to the end of a readable page followed by a no-access page.
+static Expected<GuardedMemoryBuffer>
+createGuardedMemoryBuffer(ArrayRef<uint8_t> Bytes) {
+ auto PageSizeOrErr = sys::Process::getPageSize();
+ if (!PageSizeOrErr)
+ return PageSizeOrErr.takeError();
+ const size_t PageSize = *PageSizeOrErr;
+ if (Bytes.empty() || Bytes.size() > PageSize)
+ return createStringError(std::errc::invalid_argument,
+ "test buffer must fit in one memory page");
+
+ std::error_code EC;
+ sys::MemoryBlock Allocation = sys::Memory::allocateMappedMemory(
+ 2 * PageSize, nullptr, sys::Memory::MF_READ | sys::Memory::MF_WRITE, EC);
+ if (EC)
+ return errorCodeToError(EC);
+
+ sys::OwningMemoryBlock Storage(Allocation);
+ auto *GuardStart = static_cast<uint8_t *>(Allocation.base()) + PageSize;
+ sys::MemoryBlock GuardPage(GuardStart, PageSize);
+ EC = protectGuardPage(GuardPage);
+ if (EC)
+ return errorCodeToError(EC);
+
+ auto *BufferStart = GuardStart - Bytes.size();
+ std::copy(Bytes.begin(), Bytes.end(), BufferStart);
+ auto Buffer =
+ createTestMemoryBuffer(ArrayRef<uint8_t>(BufferStart, Bytes.size()));
+ return GuardedMemoryBuffer{std::move(Storage), std::move(Buffer)};
+}
+
struct SampleProfTest : ::testing::Test {
LLVMContext Context;
std::unique_ptr<SampleProfileWriter> Writer;
@@ -177,6 +301,46 @@ struct SampleProfTest : ::testing::Test {
return Buffer;
}
+ // Read a binary header through the production reader path.
+ std::error_code readBinaryHeaderFromBuffer(ArrayRef<uint8_t> Encoded) {
+ auto Buffer = createTestMemoryBuffer(Encoded);
+ SampleProfileReaderRawBinary TestReader(std::move(Buffer), Context);
+ return TestReader.readHeader();
+ }
+
+ // Decode one string through the production reader path.
+ std::error_code readStringErrorFromBuffer(ArrayRef<uint8_t> Encoded) {
+ auto Buffer = createTestMemoryBuffer(Encoded);
+ StringDataTestReader TestReader(std::move(Buffer), Context);
+ return TestReader.readCString().getError();
+ }
+
+ // Decode one string with the cursor at the supplied buffer's end.
+ std::error_code readStringAtEndError(ArrayRef<uint8_t> Encoded) {
+ auto Buffer = createTestMemoryBuffer(Encoded);
+ StringDataTestReader TestReader(std::move(Buffer), Context);
+ return TestReader.readCStringAtEnd().getError();
+ }
+
+ // Build an ExtBinary header whose section-count field is truncated mid-value.
+ SmallVector<uint8_t, 32> writeTruncatedExtBinarySecCount() {
+ SmallVector<char, 32> Header;
+ raw_svector_ostream OS(Header);
+ encodeULEB128(SPMagic(SPF_Ext_Binary), OS);
+ encodeULEB128(103, OS);
+ SmallVector<uint8_t, 32> Encoded(Header.begin(), Header.end());
+ // Four bytes are not enough for the fixed-width section-count uint64_t.
+ Encoded.append({1, 2, 3, 4});
+ return Encoded;
+ }
+
+ // Read an ExtBinary header through the production reader path.
+ std::error_code readExtBinaryHeaderFromBuffer(ArrayRef<uint8_t> Encoded) {
+ auto Buffer = createTestMemoryBuffer(Encoded);
+ ExtBinaryDataTestReader TestReader(std::move(Buffer), Context);
+ return TestReader.readProfileHeader();
+ }
+
// Read the profile from an in-memory buffer, verify its payload, and
// return the format version.
ErrorOr<uint64_t> readVersionFromBuffer(ArrayRef<char> Buffer) {
@@ -470,6 +634,49 @@ struct SampleProfTest : ::testing::Test {
}
};
+/// Run buffer-boundary tests only where the host supports inaccessible pages.
+struct GuardedSampleProfTest : SampleProfTest {
+ /// Skip guard-page tests on hosts without the required memory protection.
+ void SetUp() override {
+ SampleProfTest::SetUp();
+ if (!guardPagesSupported())
+ GTEST_SKIP() << "guard pages are not supported on this host";
+ }
+
+ /// Read a binary header ending at a guard page.
+ Expected<std::error_code>
+ readBinaryHeaderFromGuardedBuffer(ArrayRef<uint8_t> Encoded) {
+ auto GuardedOrErr = createGuardedMemoryBuffer(Encoded);
+ if (!GuardedOrErr)
+ return GuardedOrErr.takeError();
+ GuardedMemoryBuffer Guarded = std::move(*GuardedOrErr);
+ SampleProfileReaderRawBinary TestReader(std::move(Guarded.Buffer), Context);
+ return TestReader.readHeader();
+ }
+
+ /// Decode one string ending at a guard page.
+ Expected<std::error_code>
+ readStringErrorFromGuardedBuffer(ArrayRef<uint8_t> Encoded) {
+ auto GuardedOrErr = createGuardedMemoryBuffer(Encoded);
+ if (!GuardedOrErr)
+ return GuardedOrErr.takeError();
+ GuardedMemoryBuffer Guarded = std::move(*GuardedOrErr);
+ StringDataTestReader TestReader(std::move(Guarded.Buffer), Context);
+ return TestReader.readCString().getError();
+ }
+
+ /// Read an ExtBinary header ending at a guard page.
+ Expected<std::error_code>
+ readExtBinaryHeaderFromGuardedBuffer(ArrayRef<uint8_t> Encoded) {
+ auto GuardedOrErr = createGuardedMemoryBuffer(Encoded);
+ if (!GuardedOrErr)
+ return GuardedOrErr.takeError();
+ GuardedMemoryBuffer Guarded = std::move(*GuardedOrErr);
+ ExtBinaryDataTestReader TestReader(std::move(Guarded.Buffer), Context);
+ return TestReader.readProfileHeader();
+ }
+};
+
TEST_F(SampleProfTest, roundtrip_text_profile) {
testRoundTrip(SampleProfileFormat::SPF_Text, false, false);
}
@@ -613,6 +820,125 @@ TEST(SampleProfSectionFlagTest, RejectsMismatchedSectionSpecificFlag) {
}
#endif
+// Verify that a non-terminated ULEB is reported as truncated.
+TEST_F(SampleProfTest, TruncatedULEBIsTruncated) {
+ const uint8_t Encoded[] = {0x80};
+ EXPECT_EQ(readBinaryHeaderFromBuffer(Encoded), sampleprof_error::truncated);
+}
+
+// Verify that a non-terminated ULEB at a guard-page boundary does not read past
+// the supplied MemoryBuffer.
+TEST_F(GuardedSampleProfTest, TruncatedULEBDoesNotReadPastBufferEnd) {
+ const uint8_t Encoded[] = {0x80};
+ auto ECOrErr = readBinaryHeaderFromGuardedBuffer(Encoded);
+ ASSERT_THAT_EXPECTED(ECOrErr, Succeeded());
+ EXPECT_EQ(*ECOrErr, sampleprof_error::truncated);
+}
+
+// Verify that an in-bounds ULEB value exceeding uint64_t is malformed rather
+// than silently decoded as zero.
+TEST_F(SampleProfTest, OversizedULEBIsMalformed) {
+ SmallVector<uint8_t, 10> Encoded(10, 0xff);
+ EXPECT_EQ(readBinaryHeaderFromBuffer(Encoded), sampleprof_error::malformed);
+}
+
+// Verify that format detection rejects a truncated magic value.
+TEST_F(SampleProfTest, TruncatedMagicIsRejected) {
+ const uint8_t Encoded[] = {0x80};
+ auto Buffer = createTestMemoryBuffer(Encoded);
+ EXPECT_FALSE(SampleProfileReaderRawBinary::hasFormat(*Buffer));
+ EXPECT_FALSE(SampleProfileReaderExtBinary::hasFormat(*Buffer));
+}
+
+// Verify that format detection rejects a truncated magic without reading into
+// the guard page beyond the supplied MemoryBuffer.
+TEST_F(GuardedSampleProfTest, TruncatedMagicDoesNotReadPastBufferEnd) {
+ const uint8_t Encoded[] = {0x80};
+ auto GuardedOrErr = createGuardedMemoryBuffer(Encoded);
+ ASSERT_THAT_EXPECTED(GuardedOrErr, Succeeded());
+ GuardedMemoryBuffer Guarded = std::move(*GuardedOrErr);
+ EXPECT_FALSE(SampleProfileReaderRawBinary::hasFormat(*Guarded.Buffer));
+ EXPECT_FALSE(SampleProfileReaderExtBinary::hasFormat(*Guarded.Buffer));
+}
+
+// Verify all accepted and rejected forms of the GCC format magic.
+TEST_F(SampleProfTest, GCCMagicDetection) {
+ const uint8_t Truncated[] = {'a'};
+ auto ShortBuffer = createTestMemoryBuffer(Truncated);
+ EXPECT_FALSE(SampleProfileReaderGCC::hasFormat(*ShortBuffer));
+
+ const uint8_t MagicOnly[] = {'a', 'd', 'c', 'g', '*', '7', '0', '4'};
+ auto MagicOnlyBuffer = createTestMemoryBuffer(MagicOnly);
+ EXPECT_TRUE(SampleProfileReaderGCC::hasFormat(*MagicOnlyBuffer));
+
+ const uint8_t Magic[] = {'a', 'd', 'c', 'g', '*', '7', '0', '4', 0};
+ auto MagicBuffer = createTestMemoryBuffer(Magic);
+ EXPECT_TRUE(SampleProfileReaderGCC::hasFormat(*MagicBuffer));
+
+ const uint8_t NonTerminated[] = {'a', 'd', 'c', 'g', '*', '7', '0', '4', 'x'};
+ auto NonTerminatedBuffer = createTestMemoryBuffer(NonTerminated);
+ EXPECT_FALSE(SampleProfileReaderGCC::hasFormat(*NonTerminatedBuffer));
+}
+
+// Verify that GCC magic detection does not read beyond a magic-only buffer.
+TEST_F(GuardedSampleProfTest, GCCMagicDoesNotReadPastBufferEnd) {
+ const uint8_t MagicOnly[] = {'a', 'd', 'c', 'g', '*', '7', '0', '4'};
+ auto GuardedOrErr = createGuardedMemoryBuffer(MagicOnly);
+ ASSERT_THAT_EXPECTED(GuardedOrErr, Succeeded());
+ GuardedMemoryBuffer Guarded = std::move(*GuardedOrErr);
+ EXPECT_TRUE(SampleProfileReaderGCC::hasFormat(*Guarded.Buffer));
+}
+
+// Verify that an unterminated string is reported as truncated.
+TEST_F(SampleProfTest, UnterminatedStringIsTruncated) {
+ const uint8_t Encoded[] = {'n', 'o'};
+ EXPECT_EQ(readStringErrorFromBuffer(Encoded), sampleprof_error::truncated);
+}
+
+// Verify that an unterminated string does not search into the guard page beyond
+// the supplied MemoryBuffer.
+TEST_F(GuardedSampleProfTest, UnterminatedStringDoesNotReadPastBufferEnd) {
+ const uint8_t Encoded[] = {'n', 'o'};
+ auto ECOrErr = readStringErrorFromGuardedBuffer(Encoded);
+ ASSERT_THAT_EXPECTED(ECOrErr, Succeeded());
+ EXPECT_EQ(*ECOrErr, sampleprof_error::truncated);
+}
+
+// Verify that readString accepts a terminator in the final buffer byte and
+// advances the input cursor exactly to End.
+TEST_F(SampleProfTest, ValidStringEndsAtBufferEnd) {
+ const uint8_t Encoded[] = {'o', 'k', 0};
+ auto Buffer = createTestMemoryBuffer(Encoded);
+ StringDataTestReader TestReader(std::move(Buffer), Context);
+
+ auto StringOrErr = TestReader.readCString();
+ ASSERT_TRUE(NoError(StringOrErr.getError()));
+ EXPECT_EQ(*StringOrErr, "ok");
+ EXPECT_TRUE(TestReader.atEnd());
+}
+
+// Verify that string decoding rejects a cursor already at the buffer end.
+TEST_F(SampleProfTest, StringReaderAtEndIsTruncated) {
+ const uint8_t Data[] = {0};
+ EXPECT_EQ(readStringAtEndError(Data), sampleprof_error::truncated);
+}
+
+// Verify that a short fixed-width ExtBinary section count is truncated.
+TEST_F(SampleProfTest, ShortUnencodedNumberIsTruncated) {
+ auto Encoded = writeTruncatedExtBinarySecCount();
+ EXPECT_EQ(readExtBinaryHeaderFromBuffer(Encoded),
+ sampleprof_error::truncated);
+}
+
+// Verify that a short fixed-width ExtBinary section count does not read into
+// the guard page.
+TEST_F(GuardedSampleProfTest, ShortUnencodedNumberDoesNotReadPastBufferEnd) {
+ auto Encoded = writeTruncatedExtBinarySecCount();
+ auto ECOrErr = readExtBinaryHeaderFromGuardedBuffer(Encoded);
+ ASSERT_THAT_EXPECTED(ECOrErr, Succeeded());
+ EXPECT_EQ(*ECOrErr, sampleprof_error::truncated);
+}
+
// Verify that requesting format version 103 results in a version 103 profile.
TEST_F(SampleProfTest, SampleProfileFormatVersion103) {
auto BufferOrErr = writeProfileToBuffer(103);
diff --git a/llvm/unittests/Support/LEB128Test.cpp b/llvm/unittests/Support/LEB128Test.cpp
index dc80f7c4e71ea..e47989d8cf4c9 100644
--- a/llvm/unittests/Support/LEB128Test.cpp
+++ b/llvm/unittests/Support/LEB128Test.cpp
@@ -179,6 +179,29 @@ TEST(LEB128Test, DecodeInvalidULEB128) {
#undef EXPECT_INVALID_ULEB128
}
+TEST(LEB128Test, DecodeULEB128ErrorCode) {
+ // Report an encoding that reaches the buffer end before its terminating byte.
+ const uint8_t Truncated[] = {0x80};
+ ULEB128DecodeError ErrorCode = ULEB128DecodeError::None;
+ EXPECT_EQ(0u, decodeULEB128(Truncated, nullptr, Truncated + 1, nullptr,
+ &ErrorCode));
+ EXPECT_EQ(ULEB128DecodeError::UnexpectedEnd, ErrorCode);
+
+ // Report an in-buffer encoding whose value does not fit in uint64_t.
+ const uint8_t TooBig[] = {0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x02};
+ ErrorCode = ULEB128DecodeError::None;
+ EXPECT_EQ(0u,
+ decodeULEB128(TooBig, nullptr, TooBig + 10, nullptr, &ErrorCode));
+ EXPECT_EQ(ULEB128DecodeError::TooBig, ErrorCode);
+
+ // Clear a previous error when decoding succeeds.
+ const uint8_t Valid[] = {0x01};
+ ErrorCode = ULEB128DecodeError::TooBig;
+ EXPECT_EQ(1u, decodeULEB128(Valid, nullptr, Valid + 1, nullptr, &ErrorCode));
+ EXPECT_EQ(ULEB128DecodeError::None, ErrorCode);
+}
+
TEST(LEB128Test, DecodeSLEB128) {
#define EXPECT_DECODE_SLEB128_EQ(EXPECTED, VALUE) \
do { \
More information about the llvm-commits
mailing list