Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package snappy for openSUSE:Factory checked in at 2026-09-18 22:03:24 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/snappy (Old) and /work/SRC/openSUSE:Factory/.snappy.new.383539 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "snappy" Fri Sep 18 22:03:24 2026 rev:16 rq:1378636 version:1.3.0 Changes: -------- --- /work/SRC/openSUSE:Factory/snappy/snappy.changes 2025-07-06 17:05:51.145843761 +0200 +++ /work/SRC/openSUSE:Factory/.snappy.new.383539/snappy.changes 2026-09-18 22:03:46.476643287 +0200 @@ -1,0 +2,12 @@ +Wed Sep 16 18:50:26 UTC 2026 - Antonio Teixeira <[email protected]> + +- Update to 1.3.0: + * Fixed a uint32_t overflow when decompressor accepted an input with incorrect format + * Significant RISC-V efficiency improvements + * New API on providing your own memory context + * Supporting compression levels (1-2) in C API + * Various other small fixes +- Refresh reenable-rtti.patch +- Disable LiteralLengthU32Overflow test in 32 bit architectures + +------------------------------------------------------------------- Old: ---- snappy-1.2.2.tar.gz New: ---- snappy-1.3.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ snappy.spec ++++++ --- /var/tmp/diff_new_pack.d6PrSs/_old 2026-09-18 22:03:47.282677068 +0200 +++ /var/tmp/diff_new_pack.d6PrSs/_new 2026-09-18 22:03:47.286677236 +0200 @@ -1,7 +1,7 @@ # # spec file for package snappy # -# Copyright (c) 2025 SUSE LLC +# Copyright (c) 2026 SUSE LLC and contributors # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,7 +18,7 @@ %define libname libsnappy1 Name: snappy -Version: 1.2.2 +Version: 1.3.0 Release: 0 Summary: A compressor/decompressor library favoring time License: BSD-3-Clause @@ -85,6 +85,9 @@ install -m 644 %{SOURCE1} %{buildroot}%{_libdir}/pkgconfig/%{name}.pc %check +%ifarch %arm %ix86 ppc s390 +export GTEST_FILTER='-Snappy.LiteralLengthU32Overflow' +%endif %ctest %ldconfig_scriptlets -n %{libname} ++++++ reenable-rtti.patch ++++++ --- /var/tmp/diff_new_pack.d6PrSs/_old 2026-09-18 22:03:47.404682181 +0200 +++ /var/tmp/diff_new_pack.d6PrSs/_new 2026-09-18 22:03:47.416682684 +0200 @@ -1,8 +1,8 @@ -Index: snappy-1.1.9/CMakeLists.txt +Index: b/CMakeLists.txt =================================================================== ---- snappy-1.1.9.orig/CMakeLists.txt -+++ snappy-1.1.9/CMakeLists.txt -@@ -53,8 +53,8 @@ +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -58,8 +58,8 @@ if(MSVC) add_definitions(-D_HAS_EXCEPTIONS=0) # Disable RTTI. @@ -10,10 +10,10 @@ - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /GR-") + # string(REGEX REPLACE "/GR" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /GR-") - else(MSVC) - # Use -Wall for clang and gcc. - if(NOT CMAKE_CXX_FLAGS MATCHES "-Wall") -@@ -83,8 +83,8 @@ + + # Support static MSVC runtime when building static library. + option(SNAPPY_MSVC_STATIC_RUNTIME "Link to static MSVC runtime (/MT or /MTd)" OFF) +@@ -103,8 +103,8 @@ else(MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions") # Disable RTTI. ++++++ snappy-1.2.2.tar.gz -> snappy-1.3.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/snappy-1.2.2/.github/workflows/riscv64-qemu-test.yaml new/snappy-1.3.0/.github/workflows/riscv64-qemu-test.yaml --- old/snappy-1.2.2/.github/workflows/riscv64-qemu-test.yaml 1970-01-01 01:00:00.000000000 +0100 +++ new/snappy-1.3.0/.github/workflows/riscv64-qemu-test.yaml 2026-09-14 18:32:01.000000000 +0200 @@ -0,0 +1,43 @@ +name: riscv64-qemu-test + +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-latest + env: + RISCV_CROSSCOMPILE: "ON" + riscv_gnu_toolchain_download_path: https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2025.07.03/riscv64-glibc-ubuntu-24.04-gcc-nightly-2025.07.03-nightly.tar.xz + RISCV_PATH: /opt/riscv + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install dependencies + run: | + sudo apt update + sudo apt install -y --no-install-recommends \ + qemu-user qemu-user-static \ + build-essential \ + cmake \ + git + sudo mkdir -p $RISCV_PATH + wget ${riscv_gnu_toolchain_download_path} -O riscv-toolchain.tar.xz + sudo tar -xvf riscv-toolchain.tar.xz -C $RISCV_PATH --strip-components=1 + sudo sed -i "s|libdir='/mnt/riscv/riscv64-unknown-linux-gnu/lib'|libdir='$RISCV_PATH/riscv64-unknown-linux-gnu/lib'|g" $RISCV_PATH/riscv64-unknown-linux-gnu/lib/libatomic.la + + - name: Build and Run Unit Tests + run: | + export PATH=$RISCV_PATH/bin:$PATH + export LD_LIBRARY_PATH="/opt/riscv/lib:$LD_LIBRARY_PATH" + export QEMU_LD_PREFIX=$RISCV_PATH/sysroot + mkdir build && cd build + cmake -DCMAKE_BUILD_TYPE=Release ../ + make -j$(nproc) + make test + + - name: Run Benchmark + run: ./build/snappy_benchmark + working-directory: ./ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/snappy-1.2.2/BUILD.bazel new/snappy-1.3.0/BUILD.bazel --- old/snappy-1.2.2/BUILD.bazel 2025-03-26 16:19:22.000000000 +0100 +++ new/snappy-1.3.0/BUILD.bazel 2026-09-14 18:32:01.000000000 +0200 @@ -26,10 +26,20 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +load("@bazel_skylib//rules:expand_template.bzl", "expand_template") +load("@bazel_skylib//rules:write_file.bzl", "write_file") +load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_cc//cc:cc_test.bzl", "cc_test") + package(default_visibility = ["//visibility:public"]) licenses(["notice"]) +exports_files([ + "COPYING", + "COPYING.notestdata", +]) + SNAPPY_VERSION = (1, 2, 2) config_setting( @@ -52,8 +62,10 @@ name = "snappy-stubs-internal", srcs = ["snappy-stubs-internal.cc"], hdrs = ["snappy-stubs-internal.h"], - deps = [ + implementation_deps = [ ":config", + ], + deps = [ ":snappy-stubs-public", ], ) @@ -75,8 +87,10 @@ "-Wno-sign-compare", ], }), - deps = [ + implementation_deps = [ ":config", + ], + deps = [ ":snappy-stubs-internal", ":snappy-stubs-public", ], @@ -133,10 +147,10 @@ ) # Generate a config.h similar to what cmake would produce. -genrule( +write_file( name = "config_h", - outs = ["config.h"], - cmd = """cat <<EOF >$@ + out = "config.h", + content = """\ #define HAVE_STDDEF_H 1 #define HAVE_STDINT_H 1 #ifdef __has_builtin @@ -193,19 +207,17 @@ # define SNAPPY_IS_BIG_ENDIAN 1 # endif #endif -EOF -""", +""".splitlines(), ) -genrule( +expand_template( name = "snappy_stubs_public_h", - srcs = ["snappy-stubs-public.h.in"], - outs = ["snappy-stubs-public.h"], - # Assume sys/uio.h is available on non-Windows. - # Set the version numbers. - cmd = ("""sed -e 's/$${HAVE_SYS_UIO_H_01}/!_WIN32/g' \ - -e 's/$${PROJECT_VERSION_MAJOR}/%d/g' \ - -e 's/$${PROJECT_VERSION_MINOR}/%d/g' \ - -e 's/$${PROJECT_VERSION_PATCH}/%d/g' \ - $< >$@""" % SNAPPY_VERSION), + out = "snappy-stubs-public.h", + substitutions = { + "${HAVE_SYS_UIO_H_01}": "!_WIN32", + "${PROJECT_VERSION_MAJOR}": str(SNAPPY_VERSION[0]), + "${PROJECT_VERSION_MINOR}": str(SNAPPY_VERSION[1]), + "${PROJECT_VERSION_PATCH}": str(SNAPPY_VERSION[2]), + }, + template = "snappy-stubs-public.h.in", ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/snappy-1.2.2/CMakeLists.txt new/snappy-1.3.0/CMakeLists.txt --- old/snappy-1.2.2/CMakeLists.txt 2025-03-26 16:19:22.000000000 +0100 +++ new/snappy-1.3.0/CMakeLists.txt 2026-09-14 18:32:01.000000000 +0200 @@ -27,6 +27,11 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. cmake_minimum_required(VERSION 3.10) + +if(POLICY CMP0091) + cmake_policy(SET CMP0091 NEW) +endif() + project(Snappy VERSION 1.2.2 LANGUAGES C CXX) # C++ standard can be overridden when this is used as a sub-project. @@ -55,6 +60,21 @@ # Disable RTTI. string(REGEX REPLACE "/GR" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /GR-") + + # Support static MSVC runtime when building static library. + option(SNAPPY_MSVC_STATIC_RUNTIME "Link to static MSVC runtime (/MT or /MTd)" OFF) + if(SNAPPY_MSVC_STATIC_RUNTIME) + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>") + foreach(flag_var + CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE + CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO + CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE + CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) + if(${flag_var} MATCHES "/MD") + string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") + endif() + endforeach() + endif() else(MSVC) # Use -Wall for clang and gcc. if(NOT CMAKE_CXX_FLAGS MATCHES "-Wall") @@ -155,10 +175,20 @@ return __builtin_expect(0, 1); }" HAVE_BUILTIN_EXPECT) +# Check if the built-in __builtin_ctz (count trailing zeros) is available. +# Require either a non-RISC-V target, or a RISC-V core that implements +# the Zbb bit-manipulation extension where ctz is guaranteed. check_cxx_source_compiles(" -int main() { - return __builtin_ctzll(0); -}" HAVE_BUILTIN_CTZ) +#ifdef __riscv + #ifdef __riscv_zbb + int main() { return __builtin_ctzll(0); } + #else + #error \"ZBB not enabled in current config\" + #endif +#else + int main() { return __builtin_ctzll(0); } +#endif +" HAVE_BUILTIN_CTZ) check_cxx_source_compiles(" int main() { @@ -216,6 +246,31 @@ return 0; }" SNAPPY_HAVE_NEON) +#check RVV 1.0 need __riscv_ prefix +check_cxx_source_compiles(" + #include <riscv_vector.h> + #include <stdint.h> + #include <stddef.h> + int main() { + uint8_t val = 3, dup[8]; + size_t vl = __riscv_vsetvl_e8m1(8); + vuint8m1_t v = __riscv_vmv_v_x_u8m1(val, vl); + return 0; + }" SNAPPY_RVV_1) + + +#check RVV 0.7.1 not __riscv_ prefix +check_cxx_source_compiles(" + #include <riscv_vector.h> + #include <stdint.h> + #include <stddef.h> + int main() { + uint8_t val = 3, dup[8]; + size_t vl = vsetvl_e8m1(8); + vuint8m1_t v = vmv_v_x_u8m1(val, vl); + return 0; + }" SNAPPY_RVV_0_7) + include(CheckSymbolExists) check_symbol_exists("mmap" "sys/mman.h" HAVE_FUNC_MMAP) check_symbol_exists("sysconf" "unistd.h" HAVE_FUNC_SYSCONF) @@ -251,6 +306,9 @@ "snappy-stubs-public.h.in" "${PROJECT_BINARY_DIR}/snappy-stubs-public.h") +# Must be included before CMAKE_INSTALL_INCLUDEDIR is used. +include(GNUInstallDirs) + add_library(snappy "") target_sources(snappy PRIVATE @@ -263,19 +321,19 @@ "${PROJECT_BINARY_DIR}/config.h" PUBLIC $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/snappy-c.h> - $<INSTALL_INTERFACE:include/snappy-c.h> + $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/snappy-c.h> $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/snappy-sinksource.h> - $<INSTALL_INTERFACE:include/snappy-sinksource.h> + $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/snappy-sinksource.h> $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/snappy.h> - $<INSTALL_INTERFACE:include/snappy.h> + $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/snappy.h> $<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/snappy-stubs-public.h> - $<INSTALL_INTERFACE:include/snappy-stubs-public.h> + $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/snappy-stubs-public.h> ) target_include_directories(snappy PUBLIC $<BUILD_INTERFACE:${PROJECT_BINARY_DIR}> $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}> - $<INSTALL_INTERFACE:include> + $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> ) set_target_properties(snappy PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR}) @@ -399,9 +457,6 @@ ) endif(SNAPPY_FUZZING_BUILD) -# Must be included before CMAKE_INSTALL_INCLUDEDIR is used. -include(GNUInstallDirs) - if(SNAPPY_INSTALL) install(TARGETS snappy EXPORT SnappyTargets diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/snappy-1.2.2/COPYING.notestdata new/snappy-1.3.0/COPYING.notestdata --- old/snappy-1.2.2/COPYING.notestdata 1970-01-01 01:00:00.000000000 +0100 +++ new/snappy-1.3.0/COPYING.notestdata 2026-09-14 18:32:01.000000000 +0200 @@ -0,0 +1,28 @@ +Copyright 2011, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/snappy-1.2.2/MODULE.bazel new/snappy-1.3.0/MODULE.bazel --- old/snappy-1.2.2/MODULE.bazel 2025-03-26 16:19:22.000000000 +0100 +++ new/snappy-1.3.0/MODULE.bazel 2026-09-14 18:32:01.000000000 +0200 @@ -4,20 +4,23 @@ compatibility_level = 1, ) +bazel_dep(name = "rules_cc", version = "0.2.4") +bazel_dep(name = "bazel_skylib", version = "1.8.2") + bazel_dep( name = "googletest", - version = "1.14.0.bcr.1", + version = "1.17.0.bcr.2", dev_dependency = True, repo_name = "com_google_googletest", ) bazel_dep( name = "google_benchmark", - version = "1.9.0", + version = "1.9.5", dev_dependency = True, repo_name = "com_google_benchmark", ) bazel_dep( name = "platforms", - version = "0.0.9", + version = "1.0.0", ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/snappy-1.2.2/README.md new/snappy-1.3.0/README.md --- old/snappy-1.2.2/README.md 2025-03-26 16:19:22.000000000 +0100 +++ new/snappy-1.3.0/README.md 2026-09-14 18:32:01.000000000 +0200 @@ -75,6 +75,10 @@ cd build && cmake ../ && make ``` +MSVC users must manually set `SNAPPY_HAVE_SSSE3`, `SNAPPY_HAVE_X86_CRC32`, +`SNAPPY_HAVE_BMI2`, `SNAPPY_HAVE_NEON_CRC32`, and `SNAPPY_HAVE_NEON` due to +MSVC's incorrect architecture detection, if using pre-`/arch:AVX2`. + Usage ===== @@ -140,10 +144,10 @@ 1. C++11 2. Clang (gcc and MSVC are best-effort). 3. Low level optimizations (e.g. assembly or equivalent intrinsics) for: - - [x86](https://en.wikipedia.org/wiki/X86) - - [x86-64](https://en.wikipedia.org/wiki/X86-64) - - ARMv7 (32-bit) - - ARMv8 (AArch64) + - [x86](https://en.wikipedia.org/wiki/X86) + - [x86-64](https://en.wikipedia.org/wiki/X86-64) + - ARMv7 (32-bit) + - ARMv8 (AArch64) 4. Supports only the Snappy compression scheme as described in [format_description.txt](format_description.txt). 5. CMake for building diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/snappy-1.2.2/cmake/config.h.in new/snappy-1.3.0/cmake/config.h.in --- old/snappy-1.2.2/cmake/config.h.in 2025-03-26 16:19:22.000000000 +0100 +++ new/snappy-1.3.0/cmake/config.h.in 2026-09-14 18:32:01.000000000 +0200 @@ -58,6 +58,12 @@ /* Define to 1 if you target processors with NEON and have <arm_neon.h>. */ #cmakedefine01 SNAPPY_HAVE_NEON +/* Define to 1 if you target processors with RVV1.0 and have <riscv_vector.h>. */ +#cmakedefine01 SNAPPY_RVV_1 + +/* Define to 1 if you target processors with RVV0.7 and have <riscv_vector.h>. */ +#cmakedefine01 SNAPPY_RVV_0_7 + /* Define to 1 if you have <arm_neon.h> and <arm_acle.h> and want to optimize compression speed by using __crc32cw from <arm_acle.h>. */ #cmakedefine01 SNAPPY_HAVE_NEON_CRC32 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/snappy-1.2.2/format_description.txt new/snappy-1.3.0/format_description.txt --- old/snappy-1.2.2/format_description.txt 2025-03-26 16:19:22.000000000 +0100 +++ new/snappy-1.3.0/format_description.txt 2026-09-14 18:32:01.000000000 +0200 @@ -22,7 +22,8 @@ where the lower 7 bits are data and the upper bit is set iff there are more bytes to be read. In other words, an uncompressed length of 64 would be stored as 0x40, and an uncompressed length of 2097150 (0x1FFFFE) -would be stored as 0xFE 0xFF 0x7F. +would be stored as 0xFE 0xFF 0x7F. Given the maximum, 5 bytes are sufficient for +any value. 2. The compressed stream itself diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/snappy-1.2.2/snappy-c.cc new/snappy-1.3.0/snappy-c.cc --- old/snappy-1.2.2/snappy-c.cc 2025-03-26 16:19:22.000000000 +0100 +++ new/snappy-1.3.0/snappy-c.cc 2026-09-14 18:32:01.000000000 +0200 @@ -39,6 +39,28 @@ return SNAPPY_BUFFER_TOO_SMALL; } snappy::RawCompress(input, input_length, compressed, compressed_length); + if (*compressed_length == 0) { + return SNAPPY_INVALID_INPUT; + } + return SNAPPY_OK; +} + +snappy_status snappy_compress_with_level(const char* input, size_t input_length, + int compression_level, + char* compressed, + size_t* compressed_length) { + if (*compressed_length < snappy_max_compressed_length(input_length)) { + return SNAPPY_BUFFER_TOO_SMALL; + } + if (compression_level < SNAPPY_MIN_COMPRESSION_LEVEL || + compression_level > SNAPPY_MAX_COMPRESSION_LEVEL) { + return SNAPPY_INVALID_INPUT; + } + snappy::RawCompress(input, input_length, compressed, compressed_length, + snappy::CompressionOptions{/*level=*/compression_level}); + if (*compressed_length == 0) { + return SNAPPY_INVALID_INPUT; + } return SNAPPY_OK; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/snappy-1.2.2/snappy-c.h new/snappy-1.3.0/snappy-c.h --- old/snappy-1.2.2/snappy-c.h 2025-03-26 16:19:22.000000000 +0100 +++ new/snappy-1.3.0/snappy-c.h 2026-09-14 18:32:01.000000000 +0200 @@ -57,7 +57,11 @@ * If it is not at least equal to "snappy_max_compressed_length(input_length)", * SNAPPY_BUFFER_TOO_SMALL is returned. After successful compression, * <compressed_length> contains the true length of the compressed output, - * and SNAPPY_OK is returned. + * and SNAPPY_OK is returned. Compression level is 1. For using other + * compression levels, call snappy_compress_with_level. + * + * An <input_length> of 2^32 or more returns SNAPPY_INVALID_INPUT with + * <compressed_length> set to 0 and nothing written. * * Example: * size_t output_length = snappy_max_compressed_length(input_length); @@ -68,10 +72,20 @@ * } * free(output); */ -snappy_status snappy_compress(const char* input, - size_t input_length, - char* compressed, - size_t* compressed_length); +snappy_status snappy_compress(const char* input, size_t input_length, + char* compressed, size_t* compressed_length); + +/* + * Same as above but uses compression_level which must be between + * SNAPPY_MIN_COMPRESSION_LEVEL and SNAPPY_MAX_COMPRESSION_LEVEL. + */ +#define SNAPPY_MIN_COMPRESSION_LEVEL 1 +#define SNAPPY_MAX_COMPRESSION_LEVEL 2 + +snappy_status snappy_compress_with_level(const char* input, size_t input_length, + int compression_level, + char* compressed, + size_t* compressed_length); /* * Given data in "compressed[0..compressed_length-1]" generated by diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/snappy-1.2.2/snappy-internal.h new/snappy-1.3.0/snappy-internal.h --- old/snappy-1.2.2/snappy-internal.h 2025-03-26 16:19:22.000000000 +0100 +++ new/snappy-1.3.0/snappy-internal.h 2026-09-14 18:32:01.000000000 +0200 @@ -46,6 +46,19 @@ #include <arm_neon.h> #endif +#if SNAPPY_RVV_1 || SNAPPY_RVV_0_7 +#define SNAPPY_HAVE_RVV 1 +#include <riscv_vector.h> +#else +#define SNAPPY_HAVE_RVV 0 +#endif + +#if SNAPPY_RVV_1 || SNAPPY_RVV_0_7 +#define VSETVL_E8M2 __riscv_vsetvl_e8m2 +#define VLE8_V_U8M2 __riscv_vle8_v_u8m2 +#define VSE8_V_U8M2 __riscv_vse8_v_u8m2 +#endif + #if SNAPPY_HAVE_SSSE3 || SNAPPY_HAVE_NEON #define SNAPPY_HAVE_VECTOR_BYTE_SHUFFLE 1 #else @@ -110,6 +123,8 @@ } inline V128 V128_DupChar(char c) { return vdupq_n_u8(c); } + + #endif #endif // SNAPPY_HAVE_VECTOR_BYTE_SHUFFLE @@ -118,8 +133,16 @@ class WorkingMemory { public: explicit WorkingMemory(size_t input_size); + + // Non-allocating: lays out the scratch space in the caller-provided + // buffer, which must be at least RequiredSize(input_size) bytes, aligned + // at least as strictly as uint16_t, and must outlive "*this". + WorkingMemory(size_t input_size, char* buffer); ~WorkingMemory(); + // The buffer size required by the non-allocating constructor above. + static size_t RequiredSize(size_t input_size); + // Allocates and clears a hash table using memory in "*this", // stores the number of buckets in "*table_size" and returns a pointer to // the base of the hash table. @@ -130,6 +153,7 @@ private: char* mem_; // the allocated memory, never nullptr size_t size_; // the size of the allocated memory, never 0 + bool owns_mem_; uint16_t* table_; // the pointer to the hashtable char* input_; // the pointer to the input scratch buffer char* output_; // the pointer to the output scratch buffer @@ -172,9 +196,10 @@ // loading from s2 + n. // // Separate implementation for 64-bit, little-endian cpus. +// riscv and little-endian cpu choose this routinue can be done faster too. #if !SNAPPY_IS_BIG_ENDIAN && \ (defined(__x86_64__) || defined(_M_X64) || defined(ARCH_PPC) || \ - defined(ARCH_ARM)) + defined(__aarch64__) || (defined(__riscv) && (__riscv_xlen == 64))) static inline std::pair<size_t, bool> FindMatchLength(const char* s1, const char* s2, const char* s2_limit, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/snappy-1.2.2/snappy-stubs-internal.h new/snappy-1.3.0/snappy-stubs-internal.h --- old/snappy-1.2.2/snappy-stubs-internal.h 2025-03-26 16:19:22.000000000 +0100 +++ new/snappy-1.3.0/snappy-stubs-internal.h 2026-09-14 18:32:01.000000000 +0200 @@ -304,6 +304,7 @@ void operator=(const Bits&); }; +// In RISC-V, CLZ is supported by instructions from the ZBB bit-manipulation extension. #if HAVE_BUILTIN_CTZ inline int Bits::Log2FloorNonZero(uint32_t n) { @@ -393,6 +394,7 @@ #endif // End portable versions. +// In RISC-V, CLZ is supported by instructions from the ZBB bit-manipulation extension. #if HAVE_BUILTIN_CTZ inline int Bits::FindLSBSetNonZero64(uint64_t n) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/snappy-1.2.2/snappy-test.cc new/snappy-1.3.0/snappy-test.cc --- old/snappy-1.2.2/snappy-test.cc 2025-03-26 16:19:22.000000000 +0100 +++ new/snappy-1.3.0/snappy-test.cc 2026-09-14 18:32:01.000000000 +0200 @@ -464,7 +464,8 @@ // Make sure we're at the end-of-compressed-data point. This means // if we call inflate with Z_FINISH we won't consume any input or // write any output - Bytef dummyin, dummyout; + Bytef dummyout; + Bytef dummyin = 0; uLongf dummylen = 0; if ( UncompressChunkOrAll(&dummyout, &dummylen, &dummyin, 0, Z_FINISH) != Z_OK ) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/snappy-1.2.2/snappy.cc new/snappy-1.3.0/snappy.cc --- old/snappy-1.2.2/snappy.cc 2025-03-26 16:19:22.000000000 +0100 +++ new/snappy-1.3.0/snappy.cc 2026-09-14 18:32:01.000000000 +0200 @@ -74,7 +74,7 @@ #include <cstdint> #include <cstdio> #include <cstring> -#include <functional> +#include <limits> #include <memory> #include <string> #include <utility> @@ -195,6 +195,12 @@ } // namespace size_t MaxCompressedLength(size_t source_bytes) { + // Avoid integer overflow that could cause undersized buffer allocations. + // Return std::numeric_limits<size_t>::max() to force a controlled allocation + // failure. + if (source_bytes > (std::numeric_limits<size_t>::max() - 32) / 7 * 6) { + return std::numeric_limits<size_t>::max(); + } // Compressed data can be defined as: // compressed := item* literal* // item := literal* copy @@ -281,6 +287,8 @@ // 4, 5, 0, 1, 2, 3, 4, 5, 0, 1}. These byte index sequences are generated by // calling MakePatternMaskBytes(0, 6, index_sequence<16>()) and // MakePatternMaskBytes(16, 6, index_sequence<16>()) respectively. + + template <size_t... indexes> inline constexpr std::array<char, sizeof...(indexes)> MakePatternMaskBytes( int index_offset, int pattern_size, index_sequence<indexes...>) { @@ -298,7 +306,6 @@ MakePatternMaskBytes(index_offset, pattern_sizes_minus_one + 1, make_index_sequence</*indexes=*/sizeof(V128)>())...}; } - // This is an array of shuffle control masks that can be used as the source // operand for PSHUFB to permute the contents of the destination XMM register // into a repeating byte pattern. @@ -494,7 +501,6 @@ LoadPatternAndReshuffleMask(src, pattern_size); V128 pattern = pattern_and_reshuffle_mask.first; V128 reshuffle_mask = pattern_and_reshuffle_mask.second; - // There is at least one, and at most four 16-byte blocks. Writing four // conditionals instead of a loop allows FDO to layout the code with // respect to the actual probabilities of each length. @@ -521,7 +527,6 @@ LoadPatternAndReshuffleMask(src, pattern_size); V128 pattern = pattern_and_reshuffle_mask.first; V128 reshuffle_mask = pattern_and_reshuffle_mask.second; - // This code path is relatively cold however so we save code size // by avoiding unrolling and vectorizing. // @@ -752,19 +757,36 @@ } // namespace namespace internal { -WorkingMemory::WorkingMemory(size_t input_size) { +size_t WorkingMemory::RequiredSize(size_t input_size) { const size_t max_fragment_size = std::min(input_size, kBlockSize); const size_t table_size = CalculateTableSize(max_fragment_size); - size_ = table_size * sizeof(*table_) + max_fragment_size + - MaxCompressedLength(max_fragment_size); - mem_ = std::allocator<char>().allocate(size_); + return table_size * sizeof(uint16_t) + max_fragment_size + + MaxCompressedLength(max_fragment_size); +} + +WorkingMemory::WorkingMemory(size_t input_size) + : WorkingMemory(input_size, + std::allocator<char>().allocate(RequiredSize(input_size))) { + owns_mem_ = true; +} + +WorkingMemory::WorkingMemory(size_t input_size, char* buffer) { + assert(buffer != nullptr); + assert(reinterpret_cast<uintptr_t>(buffer) % alignof(uint16_t) == 0); + const size_t max_fragment_size = std::min(input_size, kBlockSize); + const size_t table_size = CalculateTableSize(max_fragment_size); + mem_ = buffer; + size_ = RequiredSize(input_size); + owns_mem_ = false; table_ = reinterpret_cast<uint16_t*>(mem_); input_ = mem_ + table_size * sizeof(*table_); output_ = input_ + max_fragment_size; } WorkingMemory::~WorkingMemory() { - std::allocator<char>().deallocate(mem_, size_); + if (owns_mem_) { + std::allocator<char>().deallocate(mem_, size_); + } } uint16_t* WorkingMemory::GetHashTable(size_t fragment_size, @@ -1227,7 +1249,7 @@ void MemCopy64(char* dst, const void* src, size_t size) { // Always copy this many bytes. If that's below size then copy the full 64. constexpr int kShortMemCopy = 32; - + (void)kShortMemCopy; assert(size <= 64); assert(std::less_equal<const void*>()(static_cast<const char*>(src) + size, dst) || @@ -1246,6 +1268,27 @@ data = _mm256_lddqu_si256(static_cast<const __m256i *>(src) + 1); _mm256_storeu_si256(reinterpret_cast<__m256i *>(dst) + 1, data); } + // RVV acceleration available on RISC-V when compiled with -march=rv64gcv +#elif defined(__riscv) && SNAPPY_HAVE_RVV + // Cast pointers to the type we will operate on. + unsigned char* dst_ptr = reinterpret_cast<unsigned char*>(dst); + const unsigned char* src_ptr = reinterpret_cast<const unsigned char*>(src); + size_t remaining_bytes = size; + // Loop as long as there are bytes remaining to be copied. + while (remaining_bytes > 0) { + // Set vector configuration: e8 (8-bit elements), m2 (LMUL=2). + // Use e8m2 configuration to maximize throughput. + size_t vl = VSETVL_E8M2(remaining_bytes); + // Load data from the current source pointer. + vuint8m2_t vec = VLE8_V_U8M2(src_ptr, vl); + // Store data to the current destination pointer. + VSE8_V_U8M2(dst_ptr, vec, vl); + // Update pointers and the remaining count. + src_ptr += vl; + dst_ptr += vl; + remaining_bytes -= vl; + } + #else std::memmove(dst, src, kShortMemCopy); // Profiling shows that nearly all copies are short. @@ -1345,19 +1388,51 @@ return tag_type; } +SNAPPY_ATTRIBUTE_ALWAYS_INLINE +inline size_t AdvanceToNextTagRVOptimized(const uint8_t** ip_p, size_t* tag) { + const uint8_t*& ip = *ip_p; + // This section is crucial for the throughput of the decompression loop. + // The latency of an iteration is fundamentally constrained by the data chain on ip: + // ip -> c = *tag -> literal_len = c >> 2, tag_type = c & 3 + // -> literal_advance = literal_len + 2, copy_advance = tag_type + 1 + // -> next_ip = ip + literal_advance OR ip + copy_advance (literal vs copy) + // -> *tag = byte at (next_ip - 1); ip = next_ip + // + // Base RISC-V has no x86-style cmov and no AArch64 csinc on the same shape; this + // computes both candidate advances and both load offsets, then selects with + // (is_literal ? ... : ...). With the Zicond extension (czero.eqz / czero.nez), those + // selections typically lower to branchless conditional-zero ops instead of a + // hard-to-predict literal/copy branch, which is why this form tends to win there. + const size_t literal_len = *tag >> 2; + const size_t tag_type = *tag & 3; + const bool is_literal = (tag_type == 0); + const size_t copy_advance = tag_type + 1; + const size_t literal_advance = literal_len + 2; + const uint8_t* next_ip = is_literal ? (ip + literal_advance) : (ip + copy_advance); + *tag = is_literal ? ip[literal_advance - 1] : ip[copy_advance - 1]; + ip = next_ip; + return tag_type; +} + // Extract the offset for copy-1 and copy-2 returns 0 for literals or copy-4. inline uint32_t ExtractOffset(uint32_t val, size_t tag_type) { - // For x86 non-static storage works better. For ARM static storage is better. + // For Arm non-static storage works better. For x86 static storage is better. // TODO: Once the array is recognized as a register, improve the // readability for x86. #if defined(__x86_64__) - constexpr uint64_t kExtractMasksCombined = 0x0000FFFF00FF0000ull; + static constexpr uint64_t kExtractMasksCombined = 0x0000FFFF00FF0000ull; uint16_t result; memcpy(&result, reinterpret_cast<const char*>(&kExtractMasksCombined) + 2 * tag_type, sizeof(result)); return val & result; -#elif defined(__aarch64__) + // For AArch64 and RISC-V, use a bit-twiddling trick to extract the mask from a + // single combined constant instead of a lookup table. The constant packs multiple + // 16-bit masks based on tag_type (see implementation below). The code calculates + // the shift amount from tag_type, right-shifts the constant to move the desired + // mask to the LSB position, then extracts it with & 0xFFFF. This branchless + // approach is often more performant on modern CPUs. +#elif defined(__aarch64__) || (defined(__riscv) && (__riscv_xlen == 64)) constexpr uint64_t kExtractMasksCombined = 0x0000FFFF00FF0000ull; return val & static_cast<uint32_t>( (kExtractMasksCombined >> (tag_type * 16)) & 0xFFFF); @@ -1421,6 +1496,11 @@ // We never need more than 16 bits. Doing a Load16 allows the compiler // to elide the masking operation in ExtractOffset. next = LittleEndian::Load16(old_ip); +#elif defined(__riscv) + size_t tag_type = AdvanceToNextTagRVOptimized(&ip, &tag); + // We never need more than 16 bits. Doing a Load16 allows the compiler + // to elide the masking operation in ExtractOffset. + next = LittleEndian::Load16(old_ip); #else size_t tag_type = AdvanceToNextTagX86Optimized(&ip, &tag); next = LittleEndian::Load32(old_ip); @@ -1624,9 +1704,13 @@ if (SNAPPY_PREDICT_FALSE(literal_length >= 61)) { // Long literal. const size_t literal_length_length = literal_length - 60; + // NOTE: literal_length might be equal 2^32 (i.e. ExtractLowBytes + // returns 0xFFFFFFFF); this is implicitly invalid stream (since + // uncompressed length is capped with 0xFFFFFFFF); for performance we + // do not check for this case here. literal_length = ExtractLowBytes(LittleEndian::Load32(ip), literal_length_length) + - 1; + size_t{1}; ip += literal_length_length; } @@ -1779,13 +1863,13 @@ Writer* writer, uint32_t compressed_len, uint32_t uncompressed_len) { int token = 0; - Report(token, "snappy_uncompress", compressed_len, uncompressed_len); writer->SetExpectedLength(uncompressed_len); // Process the entire input decompressor->DecompressAllTags(writer); writer->Flush(); + Report(token, "snappy_uncompress", compressed_len, uncompressed_len); return (decompressor->eof() && writer->CheckLength()); } @@ -1798,20 +1882,21 @@ return Compress(reader, writer, CompressionOptions{}); } -size_t Compress(Source* reader, Sink* writer, CompressionOptions options) { +static size_t InternalCompress(Source* reader, Sink* writer, + CompressionOptions options, + internal::WorkingMemory* wmem) { assert(options.level == 1 || options.level == 2); - int token = 0; size_t written = 0; size_t N = reader->Available(); - assert(N <= 0xFFFFFFFFu); - const size_t uncompressed_size = N; + // The uncompressed length is a 32-bit varint in the stream format. + if (static_cast<uint64_t>(N) > std::numeric_limits<uint32_t>::max()) { + return 0; + } char ulength[Varint::kMax32]; char* p = Varint::Encode32(ulength, N); writer->Append(ulength, p - ulength); written += (p - ulength); - internal::WorkingMemory wmem(N); - while (N > 0) { // Get next block to compress (without copying if possible) size_t fragment_size; @@ -1826,7 +1911,7 @@ pending_advance = num_to_read; fragment_size = num_to_read; } else { - char* scratch = wmem.GetScratchInput(); + char* scratch = wmem->GetScratchInput(); std::memcpy(scratch, fragment, bytes_read); reader->Skip(bytes_read); @@ -1845,7 +1930,7 @@ // Get encoding table for compression int table_size; - uint16_t* table = wmem.GetHashTable(num_to_read, &table_size); + uint16_t* table = wmem->GetHashTable(num_to_read, &table_size); // Compress input_fragment and append to dest int max_output = MaxCompressedLength(num_to_read); @@ -1855,27 +1940,89 @@ // scratch_output[] region is big enough for this iteration. // 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* 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); - } + 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); + } + writer->Append(dest, end - dest); written += (end - dest); N -= num_to_read; reader->Skip(pending_advance); } - - Report(token, "snappy_compress", written, uncompressed_size); return written; } +size_t Compress(Source* reader, Sink* writer, CompressionOptions options) { + internal::WorkingMemory wmem(reader->Available()); + return InternalCompress(reader, writer, options, &wmem); +} + +size_t Compress(Source* reader, Sink* writer, CompressionOptions options, + CompressionContext* ctx) { + assert(ctx != nullptr); + assert(ctx->working_memory_ != nullptr); + return InternalCompress(reader, writer, options, ctx->working_memory_); +} + +CompressionContext::CompressionContext() + : working_memory_(new internal::WorkingMemory(kBlockSize)), + owns_working_memory_(true) {} + +size_t CompressionContext::WorkspaceSize() { + return sizeof(internal::WorkingMemory) + + internal::WorkingMemory::RequiredSize(kBlockSize); +} + +CompressionContext::CompressionContext(void* workspace, size_t workspace_size) + : owns_working_memory_(false) { + assert(workspace != nullptr); + assert(workspace_size >= WorkspaceSize()); + assert(reinterpret_cast<uintptr_t>(workspace) % + alignof(internal::WorkingMemory) == + 0); + (void)workspace_size; + char* base = static_cast<char*>(workspace); + working_memory_ = new (base) internal::WorkingMemory( + kBlockSize, base + sizeof(internal::WorkingMemory)); +} + +void CompressionContext::Reset() { + if (working_memory_ == nullptr) return; + if (owns_working_memory_) { + delete working_memory_; + } else { + working_memory_->~WorkingMemory(); + } + working_memory_ = nullptr; +} + +CompressionContext::~CompressionContext() { Reset(); } + +CompressionContext::CompressionContext(CompressionContext&& other) noexcept + : working_memory_(other.working_memory_), + owns_working_memory_(other.owns_working_memory_) { + other.working_memory_ = nullptr; +} + +CompressionContext& CompressionContext::operator=( + CompressionContext&& other) noexcept { + if (this != &other) { + Reset(); + working_memory_ = other.working_memory_; + owns_working_memory_ = other.owns_working_memory_; + other.working_memory_ = nullptr; + } + return *this; +} + // ----------------------------------------------------------------------- // IOVec interfaces // ----------------------------------------------------------------------- @@ -2338,6 +2485,17 @@ *compressed_length = writer.CurrentDestination() - compressed; } +void RawCompress(const char* input, size_t input_length, char* compressed, + size_t* compressed_length, CompressionOptions options, + CompressionContext* ctx) { + ByteArraySource reader(input, input_length); + UncheckedByteArraySink writer(compressed); + Compress(&reader, &writer, options, ctx); + + // Compute how many bytes were added + *compressed_length = (writer.CurrentDestination() - compressed); +} + size_t Compress(const char* input, size_t input_length, std::string* compressed) { return Compress(input, input_length, compressed, CompressionOptions{}); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/snappy-1.2.2/snappy.h new/snappy-1.3.0/snappy.h --- old/snappy-1.2.2/snappy.h 2025-03-26 16:19:22.000000000 +0100 +++ new/snappy-1.3.0/snappy.h 2026-09-14 18:32:01.000000000 +0200 @@ -50,6 +50,10 @@ class Source; class Sink; +namespace internal { +class WorkingMemory; +} // end namespace internal + struct CompressionOptions { // Compression level. // Level 1 is the fastest @@ -64,25 +68,78 @@ // faster decompression speeds than snappy:1 and zstd:-3. int level = DefaultCompressionLevel(); - constexpr CompressionOptions() = default; - constexpr CompressionOptions(int compression_level) - : level(compression_level) {} + constexpr CompressionOptions() = default; + constexpr CompressionOptions(int compression_level) + : level(compression_level) {} + static constexpr int MinCompressionLevel() { return 1; } static constexpr int MaxCompressionLevel() { return 2; } static constexpr int DefaultCompressionLevel() { return 1; } }; + // Scratch memory for compression, reusable across compressions. Callers that + // compress frequently, or that need to avoid large heap allocations can + // allocate a CompressionContext once and pass it to Compress()/RawCompress() + // to reuse the working memory across calls. + // + // The context is sized for the largest block and works for inputs of any + // size. A context may be used by any number of sequential compressions, but + // must not be used from multiple threads concurrently. A moved-from context + // may only be destroyed or assigned to. + class CompressionContext { + public: + // Allocates the working memory on the heap. + CompressionContext(); + + // Constructs a context whose working memory is placed in the + // caller-provided "workspace" instead of being heap-allocated; the + // library performs no allocation at all. + // + // REQUIRES: "workspace" points to at least "workspace_size" bytes with + // "workspace_size >= WorkspaceSize()", is suitably aligned for any + // object type (as if returned by malloc), and outlives "*this". + CompressionContext(void* workspace, size_t workspace_size); + + ~CompressionContext(); + + CompressionContext(CompressionContext&& other) noexcept; + CompressionContext& operator=(CompressionContext&& other) noexcept; + + CompressionContext(const CompressionContext&) = delete; + CompressionContext& operator=(const CompressionContext&) = delete; + + // The workspace size required by the non-allocating constructor above. + static size_t WorkspaceSize(); + + private: + friend size_t Compress(Source* reader, Sink* writer, + CompressionOptions options, CompressionContext* ctx); + + // Destroys the working memory as appropriate for how it was created + // (delete if heap-allocated, in-place destruction if placement-constructed + // in a caller-provided workspace). + void Reset(); + + internal::WorkingMemory* working_memory_; + bool owns_working_memory_; + }; + // ------------------------------------------------------------------------ // Generic compression/decompression routines. // ------------------------------------------------------------------------ // Compress the bytes read from "*reader" and append to "*writer". Return the - // number of bytes written. + // number of bytes written, or zero if "*reader" has 2^32 or more bytes. // First version is to preserve ABI. size_t Compress(Source* reader, Sink* writer); size_t Compress(Source* reader, Sink* writer, CompressionOptions options); + // Same as the above, but uses the working memory of "*ctx" instead of + // allocating it internally. See CompressionContext. + size_t Compress(Source* reader, Sink* writer, CompressionOptions options, + CompressionContext* ctx); + // Find the uncompressed length of the given stream, as given by the header. // Note that the true length could deviate from this; the stream could e.g. // be truncated. @@ -97,7 +154,8 @@ // ------------------------------------------------------------------------ // Sets "*compressed" to the compressed version of "input[0..input_length-1]". - // Original contents of *compressed are lost. + // Original contents of *compressed are lost. Returns zero and writes nothing + // if "input_length" is 2^32 or more. // // REQUIRES: "input[]" is not an alias of "*compressed". // First version is to preserve ABI. @@ -109,7 +167,8 @@ // Same as `Compress` above but taking an `iovec` array as input. Note that // this function preprocesses the inputs to compute the sum of // `iov[0..iov_cnt-1].iov_len` before reading. To avoid this, use - // `RawCompressFromIOVec` below. + // `RawCompressFromIOVec` below. Returns zero and writes nothing if that sum + // is 2^32 or more. // First version is to preserve ABI. size_t CompressFromIOVec(const struct iovec* iov, size_t iov_cnt, std::string* compressed); @@ -150,6 +209,9 @@ // Takes the data stored in "input[0..input_length]" and stores // it in the array pointed to by "compressed". // + // "*compressed_length" is set to the length of the compressed output, or to + // zero, with nothing written, if "input_length" is 2^32 or more. + // // "*compressed_length" is set to the length of the compressed output. // // Example: @@ -158,14 +220,23 @@ // RawCompress(input, input_length, output, &output_length); // ... Process(output, output_length) ... // delete [] output; + // First version is to preserve ABI. void RawCompress(const char* input, size_t input_length, char* compressed, size_t* compressed_length); void RawCompress(const char* input, size_t input_length, char* compressed, size_t* compressed_length, CompressionOptions options); + // Same as the above, but uses the working memory of "*ctx" instead of + // allocating it internally. See CompressionContext. + void RawCompress(const char* input, size_t input_length, char* compressed, + size_t* compressed_length, CompressionOptions options, + CompressionContext* ctx); // Same as `RawCompress` above but taking an `iovec` array as input. Note that // `uncompressed_length` is the total number of bytes to be read from the - // elements of `iov` (_not_ the number of elements in `iov`). + // elements of `iov` (_not_ the number of elements in `iov`). Sets + // "*compressed_length" to zero and writes nothing if `uncompressed_length` + // is 2^32 or more. + // First version is to preserve ABI. void RawCompressFromIOVec(const struct iovec* iov, size_t uncompressed_length, char* compressed, size_t* compressed_length); void RawCompressFromIOVec(const struct iovec* iov, size_t uncompressed_length, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/snappy-1.2.2/snappy_benchmark.cc new/snappy-1.3.0/snappy_benchmark.cc --- old/snappy-1.2.2/snappy_benchmark.cc 2025-03-26 16:19:22.000000000 +0100 +++ new/snappy-1.3.0/snappy_benchmark.cc 2026-09-14 18:32:01.000000000 +0200 @@ -31,10 +31,12 @@ #include <string> #include <vector> +#include "snappy-test.h" + #include "benchmark/benchmark.h" + #include "snappy-internal.h" #include "snappy-sinksource.h" -#include "snappy-test.h" #include "snappy.h" #include "snappy_test_data.h" @@ -192,8 +194,9 @@ char* dst = new char[snappy::MaxCompressedLength(contents.size())]; size_t zsize = 0; for (auto s : state) { - snappy::RawCompressFromIOVec(iov, contents.size(), dst, &zsize, - snappy::CompressionOptions{/*level=*/level}); + snappy::RawCompressFromIOVec( + iov, contents.size(), dst, &zsize, + snappy::CompressionOptions{/*level=*/static_cast<int>(level)}); benchmark::DoNotOptimize(iov); } state.SetBytesProcessed(static_cast<int64_t>(state.iterations()) * @@ -226,7 +229,7 @@ // Uncompress into an iovec containing ten entries. const int kNumEntries = 10; struct iovec iov[kNumEntries]; - char* dst = new char[contents.size()]; + char *dst = new char[contents.size()]; size_t used_so_far = 0; for (int i = 0; i < kNumEntries; ++i) { iov[i].iov_base = dst + used_so_far; @@ -304,8 +307,9 @@ size_t zsize = 0; for (auto s : state) { - snappy::RawCompress(contents.data(), contents.size(), dst, &zsize, - snappy::CompressionOptions{/*level=*/level}); + snappy::RawCompress( + contents.data(), contents.size(), dst, &zsize, + snappy::CompressionOptions{/*level=*/static_cast<int>(level)}); benchmark::DoNotOptimize(dst); } state.SetBytesProcessed(static_cast<int64_t>(state.iterations()) * @@ -340,8 +344,9 @@ size_t zsize = 0; for (auto s : state) { for (int i = 0; i < num_files; ++i) { - snappy::RawCompress(contents[i].data(), contents[i].size(), dst[i], - &zsize, snappy::CompressionOptions{/*level=*/level}); + snappy::RawCompress( + contents[i].data(), contents[i].size(), dst[i], &zsize, + snappy::CompressionOptions{/*level=*/static_cast<int>(level)}); benchmark::DoNotOptimize(dst); } } @@ -377,8 +382,9 @@ size_t zsize = 0; for (auto s : state) { for (size_t i = 0; i < contents.size(); ++i) { - snappy::RawCompress(contents[i].data(), contents[i].size(), dst[i], - &zsize, snappy::CompressionOptions{/*level=*/level}); + snappy::RawCompress( + contents[i].data(), contents[i].size(), dst[i], &zsize, + snappy::CompressionOptions{/*level=*/static_cast<int>(level)}); benchmark::DoNotOptimize(dst); } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/snappy-1.2.2/snappy_unittest.cc new/snappy-1.3.0/snappy_unittest.cc --- old/snappy-1.2.2/snappy_unittest.cc 2025-03-26 16:19:22.000000000 +0100 +++ new/snappy-1.3.0/snappy_unittest.cc 2026-09-14 18:32:01.000000000 +0200 @@ -28,6 +28,7 @@ #include <algorithm> #include <cinttypes> +#include <cstdint> #include <cmath> #include <cstdlib> #include <random> @@ -543,6 +544,174 @@ } } +TEST(Snappy, CompressionContext) { + std::minstd_rand0 rng(snappy::GetFlag(FLAGS_test_random_seed)); + std::uniform_int_distribution<int> uniform_byte(0, 255); + + // A single context, reused across every compression below. + CompressionContext ctx; + + const size_t sizes[] = {0, + 1, + 100, + kBlockSize - 1, + kBlockSize, + kBlockSize + 1, + 2 * kBlockSize, + (1 << 20) + 17}; + for (int level = CompressionOptions::MinCompressionLevel(); + level <= CompressionOptions::MaxCompressionLevel(); ++level) { + CompressionOptions options(level); + for (size_t len : sizes) { + for (bool compressible : {true, false}) { + std::string input; + input.reserve(len); + while (input.size() < len) { + input.push_back(compressible + ? static_cast<char>('a' + input.size() % 4) + : static_cast<char>(uniform_byte(rng))); + } + + std::string plain(MaxCompressedLength(len), '\0'); + size_t plain_len = 0; + RawCompress(input.data(), input.size(), &plain[0], &plain_len, options); + plain.resize(plain_len); + + std::string with_context(MaxCompressedLength(len), '\0'); + size_t with_context_len = 0; + RawCompress(input.data(), input.size(), &with_context[0], + &with_context_len, options, &ctx); + with_context.resize(with_context_len); + + // Compressing with a reused context must produce output identical to + // the context-free API. + EXPECT_EQ(plain, with_context) << "level=" << level << " len=" << len + << " compressible=" << compressible; + + std::string uncompressed; + EXPECT_TRUE(Uncompress(with_context, &uncompressed)); + EXPECT_EQ(input, uncompressed); + } + } + } +} + +TEST(Snappy, CompressionContextStaticWorkspace) { + // The library performs no allocation for a context constructed over a + // caller-provided workspace. + std::vector<char> workspace(CompressionContext::WorkspaceSize()); + CompressionContext static_ctx(workspace.data(), workspace.size()); + CompressionContext heap_ctx; + + const size_t sizes[] = {0, 1, kBlockSize - 1, kBlockSize + 1, + 2 * kBlockSize + 17}; + for (size_t len : sizes) { + std::string input; + input.reserve(len); + while (input.size() < len) { + input.push_back(static_cast<char>('a' + input.size() % 7)); + } + + std::string with_static(MaxCompressedLength(len), '\0'); + size_t with_static_len = 0; + RawCompress(input.data(), input.size(), &with_static[0], &with_static_len, + CompressionOptions{}, &static_ctx); + with_static.resize(with_static_len); + + std::string with_heap(MaxCompressedLength(len), '\0'); + size_t with_heap_len = 0; + RawCompress(input.data(), input.size(), &with_heap[0], &with_heap_len, + CompressionOptions{}, &heap_ctx); + with_heap.resize(with_heap_len); + + EXPECT_EQ(with_static, with_heap) << "len=" << len; + + std::string uncompressed; + EXPECT_TRUE(Uncompress(with_static, &uncompressed)); + EXPECT_EQ(input, uncompressed); + } + + // Both context flavors keep working after being moved. + CompressionContext moved_static(std::move(static_ctx)); + CompressionContext moved_heap = std::move(heap_ctx); + const std::string input = "the quick brown fox jumps over the lazy dog"; + std::string a(MaxCompressedLength(input.size()), '\0'); + std::string b(MaxCompressedLength(input.size()), '\0'); + size_t a_len = 0; + size_t b_len = 0; + RawCompress(input.data(), input.size(), &a[0], &a_len, CompressionOptions{}, + &moved_static); + RawCompress(input.data(), input.size(), &b[0], &b_len, CompressionOptions{}, + &moved_heap); + a.resize(a_len); + b.resize(b_len); + EXPECT_EQ(a, b); +} + +// An input of 2^32 bytes or more cannot be expressed by the stream format and +// must be refused rather than compressed under a truncated length. +#if SIZE_MAX > 0xFFFFFFFFu + +// Reports an arbitrary number of bytes available without materializing them. +class OversizedSource : public Source { + public: + explicit OversizedSource(uint64_t total) + : left_(total), buf_(1 << 16, 'a') {} + size_t Available() const override { return static_cast<size_t>(left_); } + const char* Peek(size_t* len) override { + *len = static_cast<size_t>(std::min<uint64_t>(left_, buf_.size())); + return buf_.data(); + } + void Skip(size_t n) override { left_ -= n; } + + private: + uint64_t left_; + std::string buf_; +}; + +// Counts every appended byte and keeps the first few, which is where the +// uncompressed-length varint lives. +class CountingSink : public Sink { + public: + void Append(const char* data, size_t n) override { + for (size_t i = 0; i < n && head_.size() < 8; ++i) head_.push_back(data[i]); + total_ += n; + } + + std::string head_; + uint64_t total_ = 0; +}; + +// Decodes the uncompressed-length varint a compressed stream starts with. +uint32_t DeclaredLength(const std::string& stream) { + uint32_t result = 0; + int shift = 0; + for (size_t i = 0; i < stream.size(); ++i) { + const unsigned char c = static_cast<unsigned char>(stream[i]); + result |= static_cast<uint32_t>(c & 0x7f) << shift; + if (c < 128) break; + shift += 7; + } + return result; +} + +TEST(Snappy, RefusesInputLongerThanTheFormatCanExpress) { + OversizedSource too_big(uint64_t{1} << 32); + CountingSink refused; + EXPECT_EQ(0u, Compress(&too_big, &refused)); + EXPECT_EQ(0u, refused.total_); + EXPECT_TRUE(refused.head_.empty()); + + // One byte below that is the largest input the format can express, and it + // still compresses to a stream whose header names its real length. + OversizedSource largest((uint64_t{1} << 32) - 1); + CountingSink accepted; + EXPECT_GT(Compress(&largest, &accepted), 0u); + EXPECT_EQ(0xFFFFFFFFu, DeclaredLength(accepted.head_)); +} + +#endif // SIZE_MAX > 0xFFFFFFFFu + 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 @@ -790,6 +959,61 @@ EXPECT_FALSE(snappy::IsValidCompressedBuffer(compressed, 4)); } +// A 4-byte extended literal length of 0xffffffff decodes as length = +// 0xffffffff + 1. In correct (64-bit or overflow-checked) arithmetic this is +// 4294967296, which exceeds the source buffer and must be rejected. In +// vulnerable 32-bit unsigned arithmetic the +1 wraps to 0, causing the +// decoder to skip the literal entirely and continue, silently producing +// wrong output that happens to match the preamble length (65536). +// +// Payload structure: +// Bytes 0-2: Varint 0x80 0x80 0x04 → expected length 65536 +// Bytes 3-4: Literal tag 0x00 + data 0x44 → 1-byte literal +// Bytes 5-784: 260× copy2 (0xfe 0x01 0x00) → 64 bytes from offset 1 +// Bytes 785-786: Literal tag 0x00 + data 0x46 → 1-byte literal +// Bytes 787: 0xfc → literal tag with 4-byte length prefix (m=63) +// Bytes 788-791: 0xff 0xff 0xff 0xff → length-1 = 0xffffffff → +1 wraps +// Bytes 792+: 763× copy2 + 1× copy2(len=62) to fill remaining output +TEST(Snappy, LiteralLengthU32Overflow) { + std::string compressed; + // Varint: expected output length 65536 + compressed.push_back('\x80'); + compressed.push_back('\x80'); + compressed.push_back('\x04'); + // 1-byte literal (0x44) + AppendLiteral(&compressed, "D"); + // 260 copy2 elements: each copies 64 bytes from offset 1 + // Output after this section: 1 + 260*64 = 16641 + for (int i = 0; i < 260; i++) { + AppendCopy(&compressed, 1, 64); + } + // 1-byte literal (0x46) + AppendLiteral(&compressed, "F"); + // Output so far (if wrapping): 16642 + + // Poison literal: tag 0xfc (m=63 → 4-byte extended length), length bytes + // 0xffffffff. Correct length = 0xffffffff + 1 = 4294967296. + // Wrapping length = 0. + compressed.push_back('\xfc'); // literal tag, 4 extra length bytes + compressed.push_back('\xff'); + compressed.push_back('\xff'); + compressed.push_back('\xff'); + compressed.push_back('\xff'); + + // Remaining copies to fill output to 65536 if the literal is skipped: + // 65536 - 16642 = 48894 = 763*64 + 62 + for (int i = 0; i < 763; i++) { + AppendCopy(&compressed, 1, 64); + } + AppendCopy(&compressed, 1, 62); + + std::string uncompressed; + EXPECT_FALSE(snappy::Uncompress(compressed.data(), compressed.size(), + &uncompressed)); + EXPECT_FALSE(snappy::IsValidCompressedBuffer(compressed.data(), + compressed.size())); +} + int TestFindMatchLength(const char* s1, const char *s2, unsigned length) { uint64_t data; std::pair<size_t, bool> p =
