From 221662d114e6894a4c1ccc40dc295db9f4854c2a Mon Sep 17 00:00:00 2001 From: rootkiller6788 Date: Fri, 21 Aug 2026 17:04:31 +0800 Subject: [PATCH] Fall back to level-1 compressor for unsupported compression levels CompressionOptions accepts any compression level, but InternalCompress only handles levels 1 and 2. For any other level the `end` pointer was left nullptr, so `end - dest` was undefined behaviour and the resulting write length was garbage, which crashed or corrupted the compressed output. Treat every unsupported level as level 1 instead, and add a regression test that verifies unsupported levels produce the level-1 output and round-trip correctly. --- snappy.cc | 25 +++++++++++++++---------- snappy_unittest.cc | 29 +++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/snappy.cc b/snappy.cc index 69cc209..9c30a9a 100644 --- a/snappy.cc +++ b/snappy.cc @@ -1886,7 +1886,9 @@ size_t Compress(Source* reader, Sink* writer) { static size_t InternalCompress(Source* reader, Sink* writer, CompressionOptions options, internal::WorkingMemory* wmem) { - assert(options.level == 1 || options.level == 2); + // Levels other than 1 and 2 are not implemented yet and are handled by the + // fallback below. + assert(options.level >= 1); size_t written = 0; size_t N = reader->Available(); assert(N <= 0xFFFFFFFFu); @@ -1939,15 +1941,18 @@ static size_t InternalCompress(Source* reader, Sink* writer, // Need a scratch buffer for the output, in case the byte sink doesn't // have room for us directly. char* dest = writer->GetAppendBuffer(max_output, wmem->GetScratchOutput()); - char* end = nullptr; - if (options.level == 1) { - end = internal::CompressFragment(fragment, fragment_size, dest, table, - table_size); - } else if (options.level == 2) { - end = internal::CompressFragmentDoubleHash( - fragment, fragment_size, dest, table, table_size >> 1, - table + (table_size >> 1), table_size >> 1); - } + char* end; + if (options.level == 2) { + end = internal::CompressFragmentDoubleHash( + fragment, fragment_size, dest, table, table_size >> 1, + table + (table_size >> 1), table_size >> 1); + } else { + // Level 1 is the default, and levels above 2 are not implemented yet. + // CompressionOptions does not reject them, so fall back to the level-1 + // compressor instead of leaving `end` uninitialized. + end = internal::CompressFragment(fragment, fragment_size, dest, table, + table_size); + } writer->Append(dest, end - dest); written += (end - dest); diff --git a/snappy_unittest.cc b/snappy_unittest.cc index 4f44cbf..c5f7523 100644 --- a/snappy_unittest.cc +++ b/snappy_unittest.cc @@ -647,6 +647,35 @@ TEST(Snappy, CompressionContextStaticWorkspace) { EXPECT_EQ(a, b); } +TEST(Snappy, UnsupportedCompressionLevels) { + // CompressionOptions accepts any level, but only levels 1 and 2 are + // implemented. Unsupported levels must fall back to the default (level 1) + // compressor instead of producing an invalid result. + std::string input = "some data to compress, and then some more data"; + input += input + input + input; + + std::string baseline; + size_t baseline_len = snappy::Compress(input.data(), input.size(), &baseline, + CompressionOptions{1}); + ASSERT_EQ(baseline_len, baseline.size()); + + for (int level = CompressionOptions::MaxCompressionLevel() + 1; level <= 9; + ++level) { + std::string compressed; + size_t compressed_len = + snappy::Compress(input.data(), input.size(), &compressed, + CompressionOptions{level}); + ASSERT_EQ(compressed_len, compressed.size()); + EXPECT_EQ(baseline, compressed) << "level=" << level; + + std::string uncompressed; + EXPECT_TRUE(snappy::Uncompress(compressed.data(), compressed.size(), + &uncompressed)) + << "level=" << level; + EXPECT_EQ(input, uncompressed) << "level=" << level; + } +} + TEST(Snappy, FourByteOffset) { // The new compressor cannot generate four-byte offsets since // it chops up the input into 32KB pieces. So we hand-emit the