Merge branch 'unstable' into stream-load-integrity

This commit is contained in:
Sergey Georgiev 2026-05-15 09:42:31 +03:00
commit 084b7af633
114 changed files with 23532 additions and 453 deletions

View file

@ -1,165 +0,0 @@
name: Post-Release Automation
on:
release:
types: [published]
jobs:
extract-release-info:
if: github.repository == 'redis/redis'
runs-on: ubuntu-latest
outputs:
tag_name: ${{ steps.release-info.outputs.tag_name }}
release_type: ${{ steps.release-info.outputs.release_type }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Extract and validate release information
id: release-info
env:
TAG_NAME: ${{ github.event.release.tag_name }}
GH_TOKEN: ${{ github.token }}
run: |
echo "tag_name=${TAG_NAME}" >> $GITHUB_OUTPUT
echo "Release tag: ${TAG_NAME}"
LATEST_TAG=$(gh release view --json tagName --jq '.tagName')
echo "Latest release tag(from gh release): ${LATEST_TAG}"
if [[ "${TAG_NAME}" == "${LATEST_TAG}" ]]; then
echo "release_type=latest" >> $GITHUB_OUTPUT
echo "Detected latest release: ${TAG_NAME}"
else
echo "release_type=non-latest" >> $GITHUB_OUTPUT
echo "Detected non-latest release: ${TAG_NAME} (latest is ${LATEST_TAG})"
fi
create-tarball:
needs: extract-release-info
runs-on: ubuntu-latest
env:
TAG_NAME: ${{ needs.extract-release-info.outputs.tag_name }}
outputs:
sha256: ${{ steps.checksum.outputs.sha256 }}
size_mb: ${{ steps.size.outputs.size_mb }}
size_warning: ${{ steps.size.outputs.size_warning }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
ref: ${{ env.TAG_NAME }}
fetch-depth: 0
- name: Create tarball
run: ./utils/releasetools/01_create_tarball.sh "$TAG_NAME"
- name: Verify tarball size
id: size
run: |
TARBALL="/tmp/redis-${TAG_NAME}.tar.gz"
SIZE_MB=$(du -m "$TARBALL" | cut -f1)
echo "Tarball size: ${SIZE_MB} MB"
echo "size_mb=${SIZE_MB}" >> $GITHUB_OUTPUT
if [ "$SIZE_MB" -lt 3 ] || [ "$SIZE_MB" -gt 5 ]; then
echo "::warning::Tarball size ${SIZE_MB} MB is outside expected range (3-5 MB)"
echo "size_warning=true" >> $GITHUB_OUTPUT
else
echo "size_warning=false" >> $GITHUB_OUTPUT
fi
- name: Calculate SHA256 checksum
id: checksum
run: |
TARBALL="/tmp/redis-${TAG_NAME}.tar.gz"
SHA256=$(shasum -a 256 "$TARBALL" | cut -d' ' -f1)
echo "SHA256: $SHA256"
echo "sha256=$SHA256" >> $GITHUB_OUTPUT
- name: Upload tarball as artifact
uses: actions/upload-artifact@v6
with:
name: redis-${{ env.TAG_NAME }}-tarball
path: /tmp/redis-${{ env.TAG_NAME }}.tar.gz
compression-level: 0
# approval-gate:
# needs: [extract-release-info, create-tarball]
# if: needs.extract-release-info.outputs.release_type == 'latest'
# runs-on: ubuntu-latest
# steps:
# - name: Approval gate
# run: |
# echo "Latest release detected. Manual approval required for production deployment."
# # TODO: Implement approval workflow
# # This could use GitHub Environments with required reviewers
# # or a manual approval step
# upload-tarball:
# needs: [extract-release-info, create-tarball, approval-gate]
# if: always() && !cancelled() && needs.create-tarball.result == 'success' && (needs.approval-gate.result == 'success' || needs.approval-gate.result == 'skipped')
# runs-on: ubuntu-latest
# steps:
# - name: Upload tarball
# run: |
# echo "TODO: Implement tarball upload"
# # This will require:
# # - SSH credentials/keys for upload to download.redis.io
# # - Adaptation of utils/releasetools/02_upload_tarball.sh for CI environment
# test-release-tarball:
# needs: upload-tarball
# runs-on: ubuntu-latest
# steps:
# - name: Test release tarball
# run: |
# echo "TODO: Implement release testing using utils/releasetools/03_test_release.sh"
# # This will:
# # - Download the uploaded tarball
# # - Extract and build Redis
# update-release-hashes:
# needs: test-release-tarball
# runs-on: ubuntu-latest
# steps:
# - name: Update release hashes
# run: |
# echo "TODO: Implement hash update using utils/releasetools/04_release_hash.sh"
# # This will require:
# # - Access to redis-hashes repository
# # - Git credentials for committing and pushing
summary-and-notify:
needs: [extract-release-info, create-tarball] # update-release-hashes
if: always() && github.repository == 'redis/redis'
runs-on: ubuntu-latest
env:
TAG_NAME: ${{ needs.extract-release-info.outputs.tag_name }}
RELEASE_TYPE: ${{ needs.extract-release-info.outputs.release_type }}
SHA256: ${{ needs.create-tarball.outputs.sha256 }}
SIZE_MB: ${{ needs.create-tarball.outputs.size_mb }}
SIZE_WARNING: ${{ needs.create-tarball.outputs.size_warning }}
steps:
- name: Summary
run: |
{
echo "## Post-Release Automation Summary"
echo ""
echo "- **Release Tag:** ${TAG_NAME}"
echo "- **Release Type:** ${RELEASE_TYPE}"
echo "- **Tarball SHA256:** ${SHA256}"
echo "- **Tarball Size:** ${SIZE_MB} MB"
if [ "${SIZE_WARNING}" == "true" ]; then
echo ""
echo "> [!WARNING]"
echo "> Tarball size is outside expected range, check the logs for details."
fi
} >> $GITHUB_STEP_SUMMARY
# - name: Send Slack notification
# run: |
# echo "TODO: Implement Slack notification"
# # This will require:
# # - Slack webhook URL or bot token (stored in secrets)
# # - Determine appropriate channel (e.g., #releases, #redis-releases)
# # - Craft message with release information and workflow status

1
.gitignore vendored
View file

@ -30,6 +30,7 @@ deps/lua/src/luac
deps/lua/src/liblua.a
deps/hdr_histogram/libhdrhistogram.a
deps/fpconv/libfpconv.a
deps/tre/libtre.a
tests/tls/*
.make-*
.prerequisites

View file

@ -53,7 +53,7 @@ Redis excels in various applications, including:
- **Distributed Session Store:** Offers flexible session data modeling (string, JSON, hash).
- **Data Structure Server:** Provides low-level data structures (strings, lists, sets, hashes, sorted sets, JSON, etc.) with high-level semantics (counters, queues, leaderboards, rate limiters) and supports transactions & scripting.
- **NoSQL Data Store:** Key-value, document, and time series data storage.
- **Search and Query Engine:** Indexing for hash/JSON documents, supporting vector search, full-text search, geospatial queries, ranking, and aggregations via Redis Query Engine.
- **Search and Query Engine:** Indexing for hash/JSON documents, supporting vector search, full-text search, geospatial queries, ranking, and aggregations via Redis Search.
- **Event Store & Message Broker:** Implements queues (lists), priority queues (sorted sets), event deduplication (sets), streams, and pub/sub with probabilistic stream processing capabilities.
- **Vector Store for GenAI:** Integrates with AI applications (e.g. LangGraph, mem0) for short-term memory, long-term memory, LLM response caching (semantic caching), and retrieval augmented generation (RAG).
- **Real-Time Analytics:** Powers personalization, recommendations, fraud detection, and risk assessment.
@ -172,9 +172,10 @@ Redis provides a variety of data types, processing engines, and capabilities to
**Important:** Features marked with an asterisk (\*) require Redis to be compiled with the `BUILD_WITH_MODULES=yes` flag when [building Redis from source](#build-redis-from-source)
- [**String:**](https://redis.io/docs/latest/develop/data-types/strings) Sequences of bytes, including text, serialized objects, and binary arrays used for caching, counters, and bitwise operations.
- [**JSON:**](https://redis.io/docs/latest/develop/data-types/json/) Nested JSON documents that are indexed and searchable using JSONPath expressions and with [Redis Query Engine](https://redis.io/docs/latest/develop/interact/search-and-query/)
- [**JSON:**](https://redis.io/docs/latest/develop/data-types/json/) Nested JSON documents that are indexed and searchable using JSONPath expressions and with [Redis Search](https://redis.io/docs/latest/develop/ai/search-and-query/)
- [**Array:**](https://redis.io/docs/latest/develop/data-types/arrays/) Sparse, index-addressable collection of string values
- [**Hash:**](https://redis.io/docs/latest/develop/data-types/hashes/) Field-value maps used to represent basic objects and store groupings of key-value pairs with support for [hash field expiration (TTL)](https://redis.io/docs/latest/develop/data-types/hashes/#field-expiration)
- [**Redis Query Engine:**](https://redis.io/docs/latest/develop/interact/search-and-query/) Use Redis as a document database, a vector database, a secondary index, and a search engine. Define indexes for hash and JSON documents and then use a rich query language for vector search, full-text search, geospatial queries, and aggregations.
- [**Redis Search:**](https://redis.io/docs/latest/develop/ai/search-and-query/) Use Redis as a document database, a vector database, a secondary index, and a search engine. Define indexes for hash and JSON documents and then use a rich query language for vector search, full-text search, geospatial queries, and aggregations.
- [**List:**](https://redis.io/docs/latest/develop/data-types/lists/) Linked lists of string values used as stacks, queues, and for queue management.
- [**Set:**](https://redis.io/docs/latest/develop/data-types/sets/) Unordered collection of unique strings used for tracking unique items, relations, and common set operations (intersections, unions, differences).
- [**Sorted set:**](https://redis.io/docs/latest/develop/data-types/sorted-sets/) Collection of unique strings ordered by an associated score used for leaderboards and rate limiters.

8
deps/Makefile vendored
View file

@ -59,6 +59,7 @@ distclean:
-(cd jemalloc && [ -f Makefile ] && $(MAKE) distclean) > /dev/null || true
-(cd hdr_histogram && $(MAKE) clean) > /dev/null || true
-(cd fpconv && $(MAKE) clean) > /dev/null || true
-(cd tre && $(MAKE) clean) > /dev/null || true
-(cd xxhash && $(MAKE) clean) > /dev/null || true
-(rm -f .make-*)
@ -94,6 +95,13 @@ fpconv: .make-prerequisites
.PHONY: fpconv
tre: .make-prerequisites
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR)
cd tre && $(MAKE) CFLAGS="$(DEPS_CFLAGS)" LDFLAGS="$(DEPS_LDFLAGS)"
.PHONY: tre
XXHASH_CFLAGS = -fPIC $(DEPS_CFLAGS)
xxhash: .make-prerequisites
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR)

29
deps/tre/LICENSE vendored Normal file
View file

@ -0,0 +1,29 @@
This is the license, copyright notice, and disclaimer for TRE, a regex
matching package (library and tools) with support for approximate
matching.
Copyright (c) 2001-2009 Ville Laurikari <vl@iki.fi>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. 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.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER 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
HOLDER 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.

79
deps/tre/Makefile vendored Normal file
View file

@ -0,0 +1,79 @@
STD= -std=c99
WARN= -Wall
OPT= -Os
ifeq ($(SANITIZER),address)
CFLAGS+=-fsanitize=address -fno-sanitize-recover=all -fno-omit-frame-pointer
LDFLAGS+=-fsanitize=address
else
ifeq ($(SANITIZER),undefined)
CFLAGS+=-fsanitize=undefined -fno-sanitize-recover=all -fno-omit-frame-pointer
LDFLAGS+=-fsanitize=undefined
else
ifeq ($(SANITIZER),thread)
CFLAGS+=-fsanitize=thread -fno-sanitize-recover=all -fno-omit-frame-pointer
LDFLAGS+=-fsanitize=thread
else
ifeq ($(SANITIZER),memory)
CFLAGS+=-fsanitize=memory -fsanitize-memory-track-origins=2 -fno-sanitize-recover=all -fno-omit-frame-pointer
LDFLAGS+=-fsanitize=memory
endif
endif
endif
endif
R_CFLAGS= $(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) -DTRE_REGEX_T_FIELD=value -Ilocal_includes -Ilib
R_LDFLAGS= $(LDFLAGS)
DEBUG= -g
R_CC=$(CC) $(R_CFLAGS)
R_LD=$(CC) $(R_LDFLAGS)
AR= ar
ARFLAGS= rcs
TRE_OBJ=lib/regcomp.o lib/regerror.o lib/regexec.o lib/tre-ast.o lib/tre-compile.o \
lib/tre-filter.o lib/tre-match-backtrack.o lib/tre-match-parallel.o \
lib/tre-mem.o lib/tre-parse.o lib/tre-stack.o lib/xmalloc.o
TRE_TESTS=tests/retest tests/test-str-source tests/test-literal-opt tests/test-malformed-regn
libtre.a: $(TRE_OBJ)
$(AR) $(ARFLAGS) $@ $+
check: $(TRE_TESTS)
@set -e; \
for test in $(TRE_TESTS); do \
echo "TEST $$test"; \
./$$test; \
done
tests/retest: tests/retest.c libtre.a
$(R_LD) $(R_CFLAGS) -DHAVE_REGNEXEC -DHAVE_REGNCOMP -o $@ $< libtre.a
tests/test-str-source: tests/test-str-source.c libtre.a
$(R_LD) $(R_CFLAGS) -o $@ $< libtre.a
tests/test-literal-opt: tests/test-literal-opt.c libtre.a
$(R_LD) $(R_CFLAGS) -o $@ $< libtre.a
tests/test-malformed-regn: tests/test-malformed-regn.c libtre.a
$(R_LD) $(R_CFLAGS) -o $@ $< libtre.a
lib/regcomp.o: lib/regcomp.c local_includes/tre.h local_includes/tre-config.h lib/tre-internal.h lib/xmalloc.h
lib/regerror.o: lib/regerror.c local_includes/tre.h
lib/regexec.o: lib/regexec.c local_includes/tre.h lib/tre-internal.h lib/xmalloc.h
lib/tre-ast.o: lib/tre-ast.c lib/tre-ast.h lib/tre-internal.h
lib/tre-compile.o: lib/tre-compile.c lib/tre-compile.h lib/tre-internal.h lib/tre-mem.h lib/tre-parse.h lib/tre-stack.h lib/xmalloc.h
lib/tre-filter.o: lib/tre-filter.c lib/tre-filter.h lib/tre-internal.h
lib/tre-match-backtrack.o: lib/tre-match-backtrack.c lib/tre-internal.h lib/tre-match-utils.h lib/tre-mem.h lib/tre-stack.h
lib/tre-match-parallel.o: lib/tre-match-parallel.c lib/tre-internal.h lib/tre-match-utils.h lib/tre-mem.h
lib/tre-mem.o: lib/tre-mem.c lib/tre-mem.h
lib/tre-parse.o: lib/tre-parse.c lib/tre-ast.h lib/tre-compile.h lib/tre-filter.h lib/tre-internal.h lib/tre-mem.h lib/tre-parse.h lib/tre-stack.h lib/xmalloc.h
lib/tre-stack.o: lib/tre-stack.c lib/tre-internal.h lib/tre-stack.h
lib/xmalloc.o: lib/xmalloc.c lib/xmalloc.h
.c.o:
$(R_CC) -c -o $@ $<
clean:
rm -f $(TRE_OBJ) libtre.a $(TRE_TESTS)

276
deps/tre/README.md vendored Normal file
View file

@ -0,0 +1,276 @@
Introduction
============
TRE is a lightweight, robust, and efficient POSIX compliant regexp
matching library with some exciting features such as approximate
(fuzzy) matching.
The matching algorithm used in TRE uses linear worst-case time in
the length of the text being searched, and quadratic worst-case
time in the length of the used regular expression.
In other words, the time complexity of the algorithm is O(M^2N), where
M is the length of the regular expression and N is the length of the
text. The used space is also quadratic on the length of the regex,
but does not depend on the searched string. This quadratic behaviour
occurs only on pathological cases which are probably very rare in
practice.
Hacking
=======
Here's how to work with this code.
Prerequisites
-------------
You will need the following tools installed on your system:
- autoconf
- automake
- gettext (including autopoint)
- libtool
- zip (optional)
Building
--------
First, prepare the tree. Change to the root of the source directory
and run
./utils/autogen.sh
This will regenerate various things using the prerequisite tools so
that you end up with a buildable tree.
After this, you can run the configure script and build TRE as usual:
./configure
make
make check
make install
Building a source code package
------------------------------
In a prepared tree, this command creates a source code tarball:
./configure && make dist
Alternatively, you can run
./utils/build-sources.sh
which builds the source code packages and puts them in the `dist`
subdirectory. This script needs a working `zip` command.
Features
========
TRE is not just yet another regexp matcher. TRE has some features
which are not there in most free POSIX compatible implementations.
Most of these features are not present in non-free implementations
either, for that matter.
Approximate matching
--------------------
Approximate pattern matching allows matches to be approximate, that
is, allows the matches to be close to the searched pattern under some
measure of closeness. TRE uses the edit-distance measure (also known
as the Levenshtein distance) where characters can be inserted,
deleted, or substituted in the searched text in order to get an exact
match.
Each insertion, deletion, or substitution adds the distance, or cost,
of the match. TRE can report the matches which have a cost lower than
some given threshold value. TRE can also be used to search for
matches with the lowest cost.
TRE includes a version of the agrep (approximate grep) command line
tool for approximate regexp matching in the style of grep. Unlike
other agrep implementations (like the one by Sun Wu and Udi Manber
from University of Arizona) TRE agrep allows full regexps of any
length, any number of errors, and non-uniform costs for insertion,
deletion and substitution.
Strict standard conformance
---------------------------
POSIX defines the behaviour of regexp functions precisely. TRE
attempts to conform to these specifications as strictly as possible.
TRE always returns the correct matches for subpatterns, for example.
Very few other implementations do this correctly. In fact, the only
other implementations besides TRE that I am aware of (free or not)
that get it right are Rx by Tom Lord, Regex++ by John Maddock, and the
AT&T ast regex by Glenn Fowler and Doug McIlroy.
The standard TRE tries to conform to is the IEEE Std 1003.1-2001, or
Open Group Base Specifications Issue 6, commonly referred to as
“POSIX”. The relevant parts are the base specifications on regular
expressions (and the rationale) and the description of the `regcomp()`
API.
For an excellent survey on POSIX regexp matchers, see the testregex
pages by Glenn Fowler of AT&T Labs Research.
Predictable matching speed
--------------------------
Because of the matching algorithm used in TRE, the maximum time
consumed by any `regexec()` call is always directly proportional to
the length of the searched string. There is one exception: if back
references are used, the matching may take time that grows
exponentially with the length of the string. This is because matching
back references is an NP complete problem, and almost certainly
requires exponential time to match in the worst case.
Predictable and modest memory consumption
-----------------------------------------
A `regexec()` call never allocates memory from the heap. TRE allocates
all the memory it needs during a `regcomp()` call, and some temporary
working space from the stack frame for the duration of the `regexec()`
call. The amount of temporary space needed is constant during
matching and does not depend on the searched string. For regexps of
reasonable size TRE needs less than 50K of dynamically allocated
memory during the `regcomp()` call, less than 20K for the compiled
pattern buffer, and less than two kilobytes of temporary working space
from the stack frame during a `regexec()` call. There is no time /
memory tradeoff. TRE is also small in code size; statically linking
with TRE increases the executable size less than 30K (gcc-3.2, x86,
GNU/Linux).
Wide character and multibyte character set support
--------------------------------------------------
TRE supports multibyte character sets. This makes it possible to use
regexps seamlessly with, for example, Japanese locales. TRE also
provides a wide character API.
Binary pattern and data support
-------------------------------
TRE provides APIs which allow binary zero characters both in regexps
and searched strings. The standard API cannot be easily used to, for
example, search for printable words from binary data (although it is
possible with some hacking). Searching for patterns which contain
binary zeroes embedded is not possible at all with the standard API.
Completely thread safe
----------------------
TRE is completely thread safe. All the exported functions are
re-entrant, and a single compiled regexp object can be used
simultaneously in multiple contexts; e.g. in `main()` and a signal
handler, or in many threads of a multithreaded application.
Portable
--------
TRE is portable across multiple platforms. Below is a table of
platforms and compilers used to develop and test TRE:
<table>
<tr><th>Platform</th> <th>Compiler</th></tr>
<tr><td>FreeBSD 14.1</td> <td>Clang 18</td></tr>
<tr><td>Ubuntu 22.04</td> <td>GCC 11</td></tr>
<tr><td>macOS 14.6</td> <td>Clang 14</td></tr>
<tr><td>Windows 11</td> <td>Microsoft Visual Studio 2022</td></tr>
</table>
TRE should compile without changes on most modern POSIX-like
platforms, and be easily portable to any platform with a hosted C
implementation.
Depending on the platform, you may need to install libutf8 to get
wide character and multibyte character set support.
Free
----
TRE is released under a license which is essentially the same as the
“2 clause” BSD-style license used in NetBSD. See the file LICENSE for
details.
Roadmap
-------
There are currently two features, both related to collating elements,
missing from 100% POSIX compliance. These are:
* Support for collating elements (e.g. `[[.\<X>.]]`, where `\<X>` is a
collating element). It is not possible to support multi-character
collating elements portably, since POSIX does not define a way to
determine whether a character sequence is a multi-character
collating element or not.
* Support for equivalence classes, for example `[[=\<X>=]]`, where
`\<X>` is a collating element. An equivalence class matches any
character which has the same primary collation weight as `\<X>`.
Again, POSIX provides no portable mechanism for determining the
primary collation weight of a collating element.
Note that other portable regexp implementations don't support
collating elements either. The single exception is Regex++, which
comes with its own database for collating elements for different
locales. Support for collating elements and equivalence classes has
not been widely requested and is not very high on the TODO list at the
moment.
These are other features I'm planning to implement real soon now:
* All the missing GNU extensions enabled in GNU regex, such as
`[[:<:]]` and `[[:>:]]`.
* A `REG_SHORTEST` `regexec()` flag for returning the shortest match
instead of the longest match.
* Perl-compatible syntax:
* `[:^class:]`
Matches anything but the characters in class. Note that
`[^[:class:]]` works already, this would be just a convenience
shorthand.
* `\A`
Match only at beginning of string.
* `\Z`
Match only at end of string, or before newline at the end.
* `\z`
Match only at end of string.
* `\l`
Lowercase next char (think vi).
* `\u`
Uppercase next char (think vi).
* `\L`
Lowercase till `\E` (think vi).
* `\U`
Uppercase till `\E` (think vi).
* `(?=pattern)`
Zero-width positive look-ahead assertions.
* `(?!pattern)`
Zero-width negative look-ahead assertions.
* `(?<=pattern)`
Zero-width positive look-behind assertions.
* `(?<!pattern)`
Zero-width negative look-behind assertions.
Documentation especially for the nonstandard features of TRE, such as
approximate matching, is a work in progress (with “progress” loosely
defined...) If you want to find an extension to use, reading the
`include/tre/tre.h` header might provide some additional hints if you
are comfortable with C source code.

188
deps/tre/lib/regcomp.c vendored Normal file
View file

@ -0,0 +1,188 @@
/*
tre_regcomp.c - TRE POSIX compatible regex compilation functions.
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* HAVE_CONFIG_H */
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include "tre-internal.h"
#include "xmalloc.h"
int
tre_regncomp(regex_t *preg, const char *regex, size_t n, int cflags)
{
int ret;
if (n > TRE_MAX_RE)
return REG_ESPACE;
#if TRE_WCHAR
tre_char_t *wregex;
size_t wlen;
wregex = xmalloc(sizeof(tre_char_t) * (n + 1));
if (wregex == NULL)
return REG_ESPACE;
/* If the current locale uses the standard single byte encoding of
characters, we don't do a multibyte string conversion. If we did,
many applications which use the default locale would break since
the default "C" locale uses the 7-bit ASCII character set, and
all characters with the eighth bit set would be considered invalid. */
#if TRE_MULTIBYTE
if (TRE_MB_CUR_MAX == 1)
#endif /* TRE_MULTIBYTE */
{
size_t i;
const unsigned char *str = (const unsigned char *)regex;
tre_char_t *wstr = wregex;
for (i = 0; i < n; i++)
*(wstr++) = *(str++);
wlen = n;
}
#if TRE_MULTIBYTE
else
{
size_t consumed;
tre_char_t *wcptr = wregex;
#ifdef HAVE_MBSTATE_T
mbstate_t state;
memset(&state, '\0', sizeof(state));
#endif /* HAVE_MBSTATE_T */
while (n > 0)
{
consumed = tre_mbrtowc(wcptr, regex, n, &state);
switch (consumed)
{
case 0:
if (*regex == '\0')
consumed = 1;
else
{
xfree(wregex);
return REG_BADPAT;
}
break;
case -1:
DPRINT(("mbrtowc: error %d: %s.\n", errno, strerror(errno)));
xfree(wregex);
return REG_BADPAT;
case -2:
/* The last character wasn't complete. Let's not call it a
fatal error. */
consumed = n;
break;
}
regex += consumed;
n -= consumed;
wcptr++;
}
wlen = wcptr - wregex;
}
#endif /* TRE_MULTIBYTE */
wregex[wlen] = L'\0';
ret = tre_compile(preg, wregex, wlen, cflags);
xfree(wregex);
#else /* !TRE_WCHAR */
ret = tre_compile(preg, (const tre_char_t *)regex, n, cflags);
#endif /* !TRE_WCHAR */
return ret;
}
/* this version takes bytes literally, to be used with raw vectors */
int
tre_regncompb(regex_t *preg, const char *regex, size_t n, int cflags)
{
int ret;
if (n > TRE_MAX_RE)
return REG_ESPACE;
#if TRE_WCHAR /* wide chars = we need to convert it all to the wide format */
tre_char_t *wregex;
size_t i;
wregex = xmalloc(sizeof(tre_char_t) * n);
if (wregex == NULL)
return REG_ESPACE;
for (i = 0; i < n; i++)
wregex[i] = (tre_char_t) ((unsigned char) regex[i]);
ret = tre_compile(preg, wregex, n, cflags | REG_USEBYTES);
xfree(wregex);
#else /* !TRE_WCHAR */
ret = tre_compile(preg, (const tre_char_t *)regex, n, cflags | REG_USEBYTES);
#endif /* !TRE_WCHAR */
return ret;
}
int
tre_regcomp(regex_t *preg, const char *regex, int cflags)
{
size_t n = regex ? strlen(regex) : 0;
if (n > TRE_MAX_RE)
return REG_ESPACE;
return tre_regncomp(preg, regex, n, cflags);
}
int
tre_regcompb(regex_t *preg, const char *regex, int cflags)
{
int ret;
tre_char_t *wregex;
size_t i, n = regex ? strlen(regex) : 0;
const unsigned char *str = (const unsigned char *)regex;
tre_char_t *wstr;
if (n > TRE_MAX_RE)
return REG_ESPACE;
wregex = xmalloc(sizeof(tre_char_t) * (n + 1));
if (wregex == NULL) return REG_ESPACE;
wstr = wregex;
for (i = 0; i < n; i++)
*(wstr++) = *(str++);
wregex[n] = L'\0';
ret = tre_compile(preg, wregex, n, cflags | REG_USEBYTES);
xfree(wregex);
return ret;
}
#ifdef TRE_WCHAR
int
tre_regwncomp(regex_t *preg, const wchar_t *regex, size_t n, int cflags)
{
if (n > TRE_MAX_RE)
return REG_ESPACE;
return tre_compile(preg, regex, n, cflags);
}
int
tre_regwcomp(regex_t *preg, const wchar_t *regex, int cflags)
{
size_t n = regex ? wcslen(regex) : 0;
if (n > TRE_MAX_RE)
return REG_ESPACE;
return tre_compile(preg, regex, n, cflags);
}
#endif /* TRE_WCHAR */
void
tre_regfree(regex_t *preg)
{
tre_free(preg);
}
/* EOF */

86
deps/tre/lib/regerror.c vendored Normal file
View file

@ -0,0 +1,86 @@
/*
tre_regerror.c - POSIX tre_regerror() implementation for TRE.
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* HAVE_CONFIG_H */
#include <string.h>
#ifdef HAVE_WCHAR_H
#include <wchar.h>
#endif /* HAVE_WCHAR_H */
#ifdef HAVE_WCTYPE_H
#include <wctype.h>
#endif /* HAVE_WCTYPE_H */
#include "tre-internal.h"
#ifdef HAVE_GETTEXT
#include <libintl.h>
#else
#define dgettext(p, s) s
#define gettext(s) s
#endif
#define _(String) dgettext(PACKAGE, String)
#define gettext_noop(String) String
#define xstr(s) str(s)
#define str(s) #s
/* Error message strings for error codes listed in `tre.h'. This list
needs to be in sync with the codes listed there, naturally. */
static const char *tre_error_messages[] =
{ gettext_noop("No error"), /* REG_OK */
gettext_noop("No match"), /* REG_NOMATCH */
gettext_noop("Invalid regexp"), /* REG_BADPAT */
gettext_noop("Unknown collating element"), /* REG_ECOLLATE */
gettext_noop("Unknown character class name"), /* REG_ECTYPE */
gettext_noop("Trailing backslash"), /* REG_EESCAPE */
gettext_noop("Invalid back reference"), /* REG_ESUBREG */
gettext_noop("Missing ']'"), /* REG_EBRACK */
gettext_noop("Missing ')'"), /* REG_EPAREN */
gettext_noop("Missing '}'"), /* REG_EBRACE */
gettext_noop("Invalid contents of {}"), /* REG_BADBR */
gettext_noop("Invalid character range"), /* REG_ERANGE */
gettext_noop("Out of memory"), /* REG_ESPACE */
gettext_noop("Invalid use of repetition operators"), /* REG_BADRPT */
gettext_noop("Maximum repetition in {} larger than " xstr(RE_DUP_MAX)), /* REG_BADMAX */
};
size_t
tre_regerror(int errcode, const regex_t *preg, char *errbuf, size_t errbuf_size)
{
const char *err;
size_t err_len;
/*LINTED*/(void)&preg;
if (errcode >= 0
&& errcode < (int)(sizeof(tre_error_messages)
/ sizeof(*tre_error_messages)))
err = gettext(tre_error_messages[errcode]);
else
err = gettext("Unknown error");
err_len = strlen(err) + 1;
if (errbuf_size > 0 && errbuf != NULL)
{
if (err_len > errbuf_size)
{
strncpy(errbuf, err, errbuf_size - 1);
errbuf[errbuf_size - 1] = '\0';
}
else
{
strcpy(errbuf, err);
}
}
return err_len;
}
/* EOF */

584
deps/tre/lib/regexec.c vendored Normal file
View file

@ -0,0 +1,584 @@
/*
tre_regexec.c - TRE POSIX compatible matching functions (and more).
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* HAVE_CONFIG_H */
#ifdef TRE_USE_ALLOCA
/* AIX requires this to be the first thing in the file. */
#ifndef __GNUC__
# if HAVE_ALLOCA_H
# include <alloca.h>
# else
# ifdef _AIX
#pragma alloca
# else
# ifndef alloca /* predefined by HP cc +Olibcalls */
char *alloca ();
# endif
# endif
# endif
#endif
#endif /* TRE_USE_ALLOCA */
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_WCHAR_H
#include <wchar.h>
#endif /* HAVE_WCHAR_H */
#ifdef HAVE_WCTYPE_H
#include <wctype.h>
#endif /* HAVE_WCTYPE_H */
#ifndef TRE_WCHAR
#include <ctype.h>
#endif /* !TRE_WCHAR */
#ifdef HAVE_MALLOC_H
#include <malloc.h>
#endif /* HAVE_MALLOC_H */
#include <limits.h>
#include "tre-internal.h"
#include "xmalloc.h"
/* Literal alternatives are grouped by the first byte so the matcher can
* reach the relevant candidates in O(1). In nocase mode the lookup uses the
* same folded byte mapping that was applied at compile time. */
static void
tre_litopt_candidate_range(const tre_literal_opt_t *opt, unsigned char first_byte,
size_t *start, size_t *end)
{
unsigned char key = opt->nocase ? opt->fold_map[first_byte] : first_byte;
*start = opt->start_offsets[key];
*end = opt->start_offsets[key + 1];
}
static int
tre_litopt_bytes_equal(const unsigned char *haystack,
const unsigned char *needle, size_t len,
const unsigned char *fold_map)
{
size_t i;
if (fold_map == NULL)
return memcmp(haystack, needle, len) == 0;
for (i = 0; i < len; i++)
if (fold_map[haystack[i]] != needle[i])
return 0;
return 1;
}
static int
tre_litopt_contains_case(const unsigned char *haystack, size_t hay_len,
const unsigned char *needle, size_t needle_len,
int *match_end_ofs)
{
const unsigned char *p;
size_t remaining;
if (needle_len > hay_len)
return 0;
p = haystack;
remaining = hay_len;
while (remaining >= needle_len)
{
p = memchr(p, needle[0], remaining - needle_len + 1);
if (p == NULL)
return 0;
if (memcmp(p, needle, needle_len) == 0)
{
if (match_end_ofs != NULL)
*match_end_ofs = (int)(p - haystack + needle_len);
return 1;
}
remaining = hay_len - (size_t)(p - haystack) - 1;
p++;
}
return 0;
}
/* Nocase substring matching is still byte-oriented, but scanning once and
* only checking literals that share the same folded first byte avoids the
* old O(haystack * literals) restart pattern. */
static int
tre_litopt_contains_nocase(const tre_literal_opt_t *opt,
const unsigned char *haystack, size_t hay_len,
int *match_end_ofs)
{
size_t i, start, end, j;
for (i = 0; i < hay_len; i++)
{
tre_litopt_candidate_range(opt, haystack[i], &start, &end);
for (j = start; j < end; j++)
{
const tre_literal_opt_literal_t *lit = &opt->literals[j];
if (lit->len <= hay_len - i
&& tre_litopt_bytes_equal(haystack + i, lit->data, lit->len,
opt->fold_map))
{
if (match_end_ofs != NULL)
*match_end_ofs = (int)(i + lit->len);
return 1;
}
}
}
return 0;
}
static reg_errcode_t
tre_match_literal_opt(const tre_tnfa_t *tnfa, const char *string, size_t len,
int eflags, int *match_end_ofs)
{
const tre_literal_opt_t *opt = &tnfa->literal_opt;
const unsigned char *haystack = (const unsigned char *)string;
size_t start = 0, end = opt->num_literals, i;
const unsigned char *fold_map = opt->nocase ? opt->fold_map : NULL;
if ((opt->mode == TRE_LITERAL_OPT_PREFIX
|| opt->mode == TRE_LITERAL_OPT_EXACT)
&& (eflags & REG_NOTBOL))
return REG_NOMATCH;
if ((opt->mode == TRE_LITERAL_OPT_SUFFIX
|| opt->mode == TRE_LITERAL_OPT_EXACT)
&& (eflags & REG_NOTEOL))
return REG_NOMATCH;
if ((opt->mode == TRE_LITERAL_OPT_EXACT
|| opt->mode == TRE_LITERAL_OPT_PREFIX)
&& len > 0)
tre_litopt_candidate_range(opt, haystack[0], &start, &end);
if (opt->mode == TRE_LITERAL_OPT_CONTAINS)
{
if (opt->nocase)
return tre_litopt_contains_nocase(opt, haystack, len, match_end_ofs)
? REG_OK : REG_NOMATCH;
for (i = 0; i < opt->num_literals; i++)
{
const tre_literal_opt_literal_t *lit = &opt->literals[i];
if (tre_litopt_contains_case(haystack, len, lit->data, lit->len,
match_end_ofs))
return REG_OK;
}
return REG_NOMATCH;
}
for (i = start; i < end; i++)
{
const tre_literal_opt_literal_t *lit = &opt->literals[i];
switch (opt->mode)
{
case TRE_LITERAL_OPT_EXACT:
if (len == lit->len
&& tre_litopt_bytes_equal(haystack, lit->data, len, fold_map))
{
if (match_end_ofs != NULL)
*match_end_ofs = (int)len;
return REG_OK;
}
break;
case TRE_LITERAL_OPT_PREFIX:
if (len >= lit->len
&& tre_litopt_bytes_equal(haystack, lit->data, lit->len,
fold_map))
{
if (match_end_ofs != NULL)
*match_end_ofs = (int)lit->len;
return REG_OK;
}
break;
case TRE_LITERAL_OPT_SUFFIX:
if (len >= lit->len
&& tre_litopt_bytes_equal(haystack + len - lit->len, lit->data,
lit->len, fold_map))
{
if (match_end_ofs != NULL)
*match_end_ofs = (int)len;
return REG_OK;
}
break;
case TRE_LITERAL_OPT_CONTAINS:
case TRE_LITERAL_OPT_NONE:
break;
}
}
return REG_NOMATCH;
}
/* Fills the POSIX.2 regmatch_t array according to the TNFA tag and match
endpoint values. */
void
tre_fill_pmatch(size_t nmatch, regmatch_t pmatch[], int cflags,
const tre_tnfa_t *tnfa, int *tags, int match_eo)
{
tre_submatch_data_t *submatch_data;
unsigned int i, j;
int *parents;
i = 0;
if (match_eo >= 0 && !(cflags & REG_NOSUB))
{
/* Construct submatch offsets from the tags. */
DPRINT(("end tag = t%d = %d\n", tnfa->end_tag, match_eo));
submatch_data = tnfa->submatch_data;
while (i < tnfa->num_submatches && i < nmatch)
{
if (submatch_data[i].so_tag == tnfa->end_tag)
pmatch[i].rm_so = match_eo;
else
pmatch[i].rm_so = tags[submatch_data[i].so_tag];
if (submatch_data[i].eo_tag == tnfa->end_tag)
pmatch[i].rm_eo = match_eo;
else
pmatch[i].rm_eo = tags[submatch_data[i].eo_tag];
/* If either of the endpoints were not used, this submatch
was not part of the match. */
if (pmatch[i].rm_so == -1 || pmatch[i].rm_eo == -1)
pmatch[i].rm_so = pmatch[i].rm_eo = -1;
DPRINT(("pmatch[%d] = {t%d = %d, t%d = %d}\n", i,
submatch_data[i].so_tag, pmatch[i].rm_so,
submatch_data[i].eo_tag, pmatch[i].rm_eo));
i++;
}
/* Reset all submatches that are not within all of their parent
submatches. */
i = 0;
while (i < tnfa->num_submatches && i < nmatch)
{
if (pmatch[i].rm_eo == -1)
assert(pmatch[i].rm_so == -1);
assert(pmatch[i].rm_so <= pmatch[i].rm_eo);
parents = submatch_data[i].parents;
if (parents != NULL)
for (j = 0; parents[j] >= 0; j++)
{
DPRINT(("pmatch[%d] parent %d\n", i, parents[j]));
if (pmatch[i].rm_so < pmatch[parents[j]].rm_so
|| pmatch[i].rm_eo > pmatch[parents[j]].rm_eo)
pmatch[i].rm_so = pmatch[i].rm_eo = -1;
}
i++;
}
}
while (i < nmatch)
{
pmatch[i].rm_so = -1;
pmatch[i].rm_eo = -1;
i++;
}
}
/*
Wrapper functions for POSIX compatible regexp matching.
*/
int
tre_have_backrefs(const regex_t *preg)
{
tre_tnfa_t *tnfa = (void *)preg->TRE_REGEX_T_FIELD;
return tnfa->have_backrefs;
}
int
tre_have_approx(const regex_t *preg)
{
tre_tnfa_t *tnfa = (void *)preg->TRE_REGEX_T_FIELD;
return tnfa->have_approx;
}
static int
tre_match(const tre_tnfa_t *tnfa, const void *string, ssize_t len,
tre_str_type_t type, size_t nmatch, regmatch_t pmatch[],
int eflags)
{
reg_errcode_t status;
int *tags = NULL, eo;
if (tnfa->num_tags > 0 && nmatch > 0)
{
#ifdef TRE_USE_ALLOCA
tags = alloca(sizeof(*tags) * tnfa->num_tags);
#else /* !TRE_USE_ALLOCA */
tags = xmalloc(sizeof(*tags) * tnfa->num_tags);
#endif /* !TRE_USE_ALLOCA */
if (tags == NULL)
return REG_ESPACE;
}
if (type == STR_BYTE
&& tnfa->literal_opt.mode != TRE_LITERAL_OPT_NONE
&& (nmatch == 0 || (tnfa->cflags & REG_NOSUB))
#ifdef TRE_APPROX
&& !(eflags & REG_APPROX_MATCHER)
#endif /* TRE_APPROX */
&& !(eflags & REG_BACKTRACKING_MATCHER))
{
size_t byte_len = (len >= 0) ? (size_t)len : strlen((const char *)string);
status = tre_match_literal_opt(tnfa, string, byte_len, eflags, &eo);
/* Even when the caller asked for no submatches, regexec() still has to
* clear any pmatch entries it was handed. The normal matcher path does
* this through tre_fill_pmatch(), so mirror that behavior here. */
if (status == REG_OK && nmatch > 0)
tre_fill_pmatch(nmatch, pmatch, tnfa->cflags, tnfa, NULL, eo);
#ifndef TRE_USE_ALLOCA
if (tags)
xfree(tags);
#endif /* !TRE_USE_ALLOCA */
return status;
}
/* Dispatch to the appropriate matcher. */
if (tnfa->have_backrefs || eflags & REG_BACKTRACKING_MATCHER)
{
/* The regex has back references, use the backtracking matcher. */
if (type == STR_USER)
{
const tre_str_source *source = string;
if (source->rewind == NULL || source->compare == NULL)
{
/* The backtracking matcher requires rewind and compare
capabilities from the input stream. */
#ifndef TRE_USE_ALLOCA
if (tags)
xfree(tags);
#endif /* !TRE_USE_ALLOCA */
return REG_BADPAT;
}
}
status = tre_tnfa_run_backtrack(tnfa, string, len, type,
tags, eflags, &eo);
}
#ifdef TRE_APPROX
else if (tnfa->have_approx || eflags & REG_APPROX_MATCHER)
{
/* The regex uses approximate matching, use the approximate matcher. */
regamatch_t match;
regaparams_t params;
tre_regaparams_default(&params);
params.max_err = 0;
params.max_cost = 0;
status = tre_tnfa_run_approx(tnfa, string, len, type, tags,
&match, params, eflags, &eo);
}
#endif /* TRE_APPROX */
else
{
/* Exact matching, no back references, use the parallel matcher. */
status = tre_tnfa_run_parallel(tnfa, string, len, type,
tags, eflags, &eo);
}
if (status == REG_OK)
/* A match was found, so fill the submatch registers. */
tre_fill_pmatch(nmatch, pmatch, tnfa->cflags, tnfa, tags, eo);
#ifndef TRE_USE_ALLOCA
if (tags)
xfree(tags);
#endif /* !TRE_USE_ALLOCA */
return status;
}
int
tre_regnexec(const regex_t *preg, const char *str, size_t len,
size_t nmatch, regmatch_t pmatch[], int eflags)
{
tre_tnfa_t *tnfa = (void *)preg->TRE_REGEX_T_FIELD;
tre_str_type_t type = (TRE_MB_CUR_MAX == 1) ? STR_BYTE : STR_MBS;
return tre_match(tnfa, str, len, type, nmatch, pmatch, eflags);
}
#ifdef TRE_USE_GNUC_REGEXEC_FPL
int
tre_regexec(const regex_t *preg, const char *str,
size_t nmatch, regmatch_t pmatch[_Restrict_arr_ _REGEX_NELTS (nmatch)],
int eflags)
#else
int
tre_regexec(const regex_t *preg, const char *str,
size_t nmatch, regmatch_t pmatch[], int eflags)
#endif
{
return tre_regnexec(preg, str, -1, nmatch, pmatch, eflags);
}
int
tre_regexecb(const regex_t *preg, const char *str,
size_t nmatch, regmatch_t pmatch[], int eflags)
{
tre_tnfa_t *tnfa = (void *)preg->TRE_REGEX_T_FIELD;
return tre_match(tnfa, str, -1, STR_BYTE, nmatch, pmatch, eflags);
}
int
tre_regnexecb(const regex_t *preg, const char *str, size_t len,
size_t nmatch, regmatch_t pmatch[], int eflags)
{
tre_tnfa_t *tnfa = (void *)preg->TRE_REGEX_T_FIELD;
return tre_match(tnfa, str, len, STR_BYTE, nmatch, pmatch, eflags);
}
#ifdef TRE_WCHAR
int
tre_regwnexec(const regex_t *preg, const wchar_t *str, size_t len,
size_t nmatch, regmatch_t pmatch[], int eflags)
{
tre_tnfa_t *tnfa = (void *)preg->TRE_REGEX_T_FIELD;
return tre_match(tnfa, str, len, STR_WIDE, nmatch, pmatch, eflags);
}
int
tre_regwexec(const regex_t *preg, const wchar_t *str,
size_t nmatch, regmatch_t pmatch[], int eflags)
{
return tre_regwnexec(preg, str, -1, nmatch, pmatch, eflags);
}
#endif /* TRE_WCHAR */
int
tre_reguexec(const regex_t *preg, const tre_str_source *str,
size_t nmatch, regmatch_t pmatch[], int eflags)
{
tre_tnfa_t *tnfa = (void *)preg->TRE_REGEX_T_FIELD;
return tre_match(tnfa, str, -1, STR_USER, nmatch, pmatch, eflags);
}
#ifdef TRE_APPROX
/*
Wrapper functions for approximate regexp matching.
*/
static int
tre_match_approx(const tre_tnfa_t *tnfa, const void *string, ssize_t len,
tre_str_type_t type, regamatch_t *match, regaparams_t params,
int eflags)
{
reg_errcode_t status;
int *tags = NULL, eo;
/* If the regexp does not use approximate matching features, the
maximum cost is zero, and the approximate matcher isn't forced,
use the exact matcher instead. */
if (params.max_cost == 0 && !tnfa->have_approx
&& !(eflags & REG_APPROX_MATCHER))
return tre_match(tnfa, string, len, type, match->nmatch, match->pmatch,
eflags);
/* Back references are not supported by the approximate matcher. */
if (tnfa->have_backrefs)
return REG_BADPAT;
if (tnfa->num_tags > 0 && match->nmatch > 0)
{
#if TRE_USE_ALLOCA
tags = alloca(sizeof(*tags) * tnfa->num_tags);
#else /* !TRE_USE_ALLOCA */
tags = xmalloc(sizeof(*tags) * tnfa->num_tags);
#endif /* !TRE_USE_ALLOCA */
if (tags == NULL)
return REG_ESPACE;
}
status = tre_tnfa_run_approx(tnfa, string, len, type, tags,
match, params, eflags, &eo);
if (status == REG_OK)
tre_fill_pmatch(match->nmatch, match->pmatch, tnfa->cflags, tnfa, tags, eo);
#ifndef TRE_USE_ALLOCA
if (tags)
xfree(tags);
#endif /* !TRE_USE_ALLOCA */
return status;
}
int
tre_reganexec(const regex_t *preg, const char *str, size_t len,
regamatch_t *match, regaparams_t params, int eflags)
{
tre_tnfa_t *tnfa = (void *)preg->TRE_REGEX_T_FIELD;
tre_str_type_t type = (TRE_MB_CUR_MAX == 1) ? STR_BYTE : STR_MBS;
return tre_match_approx(tnfa, str, len, type, match, params, eflags);
}
int
tre_regaexec(const regex_t *preg, const char *str,
regamatch_t *match, regaparams_t params, int eflags)
{
return tre_reganexec(preg, str, -1, match, params, eflags);
}
int
tre_regaexecb(const regex_t *preg, const char *str,
regamatch_t *match, regaparams_t params, int eflags)
{
tre_tnfa_t *tnfa = (void *)preg->TRE_REGEX_T_FIELD;
return tre_match_approx(tnfa, str, -1, STR_BYTE, match, params, eflags);
}
#ifdef TRE_WCHAR
int
tre_regawnexec(const regex_t *preg, const wchar_t *str, size_t len,
regamatch_t *match, regaparams_t params, int eflags)
{
tre_tnfa_t *tnfa = (void *)preg->TRE_REGEX_T_FIELD;
return tre_match_approx(tnfa, str, len, STR_WIDE,
match, params, eflags);
}
int
tre_regawexec(const regex_t *preg, const wchar_t *str,
regamatch_t *match, regaparams_t params, int eflags)
{
return tre_regawnexec(preg, str, -1, match, params, eflags);
}
#endif /* TRE_WCHAR */
void
tre_regaparams_default(regaparams_t *params)
{
memset(params, 0, sizeof(*params));
params->cost_ins = 1;
params->cost_del = 1;
params->cost_subst = 1;
params->max_cost = INT_MAX;
params->max_ins = INT_MAX;
params->max_del = INT_MAX;
params->max_subst = INT_MAX;
params->max_err = INT_MAX;
}
#endif /* TRE_APPROX */
/* EOF */

226
deps/tre/lib/tre-ast.c vendored Normal file
View file

@ -0,0 +1,226 @@
/*
tre-ast.c - Abstract syntax tree (AST) routines
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* HAVE_CONFIG_H */
#include <assert.h>
#include "tre-ast.h"
#include "tre-mem.h"
tre_ast_node_t *
tre_ast_new_node(tre_mem_t mem, tre_ast_type_t type, size_t size)
{
tre_ast_node_t *node;
node = tre_mem_calloc(mem, sizeof(*node));
if (!node)
return NULL;
node->obj = tre_mem_calloc(mem, size);
if (!node->obj)
return NULL;
node->type = type;
node->nullable = -1;
node->submatch_id = -1;
return node;
}
tre_ast_node_t *
tre_ast_new_literal(tre_mem_t mem, int code_min, int code_max)
{
tre_ast_node_t *node;
tre_literal_t *lit;
node = tre_ast_new_node(mem, LITERAL, sizeof(tre_literal_t));
if (!node)
return NULL;
lit = node->obj;
lit->code_min = code_min;
lit->code_max = code_max;
lit->position = -1;
return node;
}
tre_ast_node_t *
tre_ast_new_iter(tre_mem_t mem, tre_ast_node_t *arg, int min, int max,
int minimal)
{
tre_ast_node_t *node;
tre_iteration_t *iter;
node = tre_ast_new_node(mem, ITERATION, sizeof(tre_iteration_t));
if (!node)
return NULL;
iter = node->obj;
iter->arg = arg;
iter->min = min;
iter->max = max;
iter->minimal = minimal;
node->num_submatches = arg->num_submatches;
return node;
}
tre_ast_node_t *
tre_ast_new_union(tre_mem_t mem, tre_ast_node_t *left, tre_ast_node_t *right)
{
tre_ast_node_t *node;
node = tre_ast_new_node(mem, UNION, sizeof(tre_union_t));
if (node == NULL)
return NULL;
((tre_union_t *)node->obj)->left = left;
((tre_union_t *)node->obj)->right = right;
node->num_submatches = left->num_submatches + right->num_submatches;
return node;
}
tre_ast_node_t *
tre_ast_new_catenation(tre_mem_t mem, tre_ast_node_t *left,
tre_ast_node_t *right)
{
tre_ast_node_t *node;
node = tre_ast_new_node(mem, CATENATION, sizeof(tre_catenation_t));
if (node == NULL)
return NULL;
((tre_catenation_t *)node->obj)->left = left;
((tre_catenation_t *)node->obj)->right = right;
node->num_submatches = left->num_submatches + right->num_submatches;
return node;
}
#ifdef TRE_DEBUG
static void
tre_findent(FILE *stream, int i)
{
while (i-- > 0)
fputc(' ', stream);
}
void
tre_print_params(int *params)
{
int i;
if (params)
{
DPRINT(("params ["));
for (i = 0; i < TRE_PARAM_LAST; i++)
{
if (params[i] == TRE_PARAM_UNSET)
DPRINT(("unset"));
else if (params[i] == TRE_PARAM_DEFAULT)
DPRINT(("default"));
else
DPRINT(("%d", params[i]));
if (i < TRE_PARAM_LAST - 1)
DPRINT((", "));
}
DPRINT(("]"));
}
}
static void
tre_do_print(FILE *stream, tre_ast_node_t *ast, int indent)
{
int code_min, code_max, pos;
int num_tags = ast->num_tags;
tre_literal_t *lit;
tre_iteration_t *iter;
tre_findent(stream, indent);
switch (ast->type)
{
case LITERAL:
lit = ast->obj;
code_min = lit->code_min;
code_max = lit->code_max;
pos = lit->position;
if (IS_EMPTY(lit))
{
fprintf(stream, "literal empty\n");
}
else if (IS_ASSERTION(lit))
{
int i;
char *assertions[] = { "bol", "eol", "ctype", "!ctype",
"bow", "eow", "wb", "!wb" };
if (code_max >= ASSERT_LAST << 1)
assert(0);
fprintf(stream, "assertions: ");
for (i = 0; (1 << i) <= ASSERT_LAST; i++)
if (code_max & (1 << i))
fprintf(stream, "%s ", assertions[i]);
fprintf(stream, "\n");
}
else if (IS_TAG(lit))
{
fprintf(stream, "tag %d\n", code_max);
}
else if (IS_BACKREF(lit))
{
fprintf(stream, "backref %d, pos %d\n", code_max, pos);
}
else if (IS_PARAMETER(lit))
{
tre_print_params(lit->u.params);
fprintf(stream, "\n");
}
else
{
fprintf(stream, "literal (%c, %c) (%d, %d), pos %d, sub %d, "
"%d tags\n", code_min, code_max, code_min, code_max, pos,
ast->submatch_id, num_tags);
}
break;
case ITERATION:
iter = ast->obj;
fprintf(stream, "iteration {%d, %d}, sub %d, %d tags, %s\n",
iter->min, iter->max, ast->submatch_id, num_tags,
iter->minimal ? "minimal" : "greedy");
tre_do_print(stream, iter->arg, indent + 2);
break;
case UNION:
fprintf(stream, "union, sub %d, %d tags\n", ast->submatch_id, num_tags);
tre_do_print(stream, ((tre_union_t *)ast->obj)->left, indent + 2);
tre_do_print(stream, ((tre_union_t *)ast->obj)->right, indent + 2);
break;
case CATENATION:
fprintf(stream, "catenation, sub %d, %d tags\n", ast->submatch_id,
num_tags);
tre_do_print(stream, ((tre_catenation_t *)ast->obj)->left, indent + 2);
tre_do_print(stream, ((tre_catenation_t *)ast->obj)->right, indent + 2);
break;
default:
assert(0);
break;
}
}
static void
tre_ast_fprint(FILE *stream, tre_ast_node_t *ast)
{
tre_do_print(stream, ast, 0);
}
void
tre_ast_print(tre_ast_node_t *tree)
{
printf("AST:\n");
tre_ast_fprint(stdout, tree);
}
#endif /* TRE_DEBUG */
/* EOF */

128
deps/tre/lib/tre-ast.h vendored Normal file
View file

@ -0,0 +1,128 @@
/*
tre-ast.h - Abstract syntax tree (AST) definitions
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
#ifndef TRE_AST_H
#define TRE_AST_H 1
#include "tre-mem.h"
#include "tre-internal.h"
#include "tre-compile.h"
/* The different AST node types. */
typedef enum {
LITERAL,
CATENATION,
ITERATION,
UNION
} tre_ast_type_t;
/* Special subtypes of TRE_LITERAL. */
#define EMPTY -1 /* Empty leaf (denotes empty string). */
#define ASSERTION -2 /* Assertion leaf. */
#define TAG -3 /* Tag leaf. */
#define BACKREF -4 /* Back reference leaf. */
#define PARAMETER -5 /* Parameter. */
#define IS_SPECIAL(x) ((x)->code_min < 0)
#define IS_EMPTY(x) ((x)->code_min == EMPTY)
#define IS_ASSERTION(x) ((x)->code_min == ASSERTION)
#define IS_TAG(x) ((x)->code_min == TAG)
#define IS_BACKREF(x) ((x)->code_min == BACKREF)
#define IS_PARAMETER(x) ((x)->code_min == PARAMETER)
/* A generic AST node. All AST nodes consist of this node on the top
level with `obj' pointing to the actual content. */
typedef struct {
tre_ast_type_t type; /* Type of the node. */
void *obj; /* Pointer to actual node. */
int nullable;
int submatch_id;
unsigned int num_submatches;
unsigned int num_tags;
tre_pos_and_tags_t *firstpos;
tre_pos_and_tags_t *lastpos;
} tre_ast_node_t;
/* A "literal" node. These are created for assertions, back references,
tags, matching parameter settings, and all expressions that match one
character. */
typedef struct {
long code_min;
long code_max;
int position;
union {
tre_ctype_t class;
int *params;
} u;
tre_ctype_t *neg_classes;
} tre_literal_t;
/* A "catenation" node. These are created when two regexps are concatenated.
If there are more than one subexpressions in sequence, the `left' part
holds all but the last, and `right' part holds the last subexpression
(catenation is left associative). */
typedef struct {
tre_ast_node_t *left;
tre_ast_node_t *right;
} tre_catenation_t;
/* An "iteration" node. These are created for the "*", "+", "?", and "{m,n}"
operators. */
typedef struct {
/* Subexpression to match. */
tre_ast_node_t *arg;
/* Minimum number of consecutive matches. */
int min;
/* Maximum number of consecutive matches. */
int max;
/* If 0, match as many characters as possible, if 1 match as few as
possible. Note that this does not always mean the same thing as
matching as many/few repetitions as possible. */
unsigned int minimal:1;
/* Approximate matching parameters (or NULL). */
int *params;
} tre_iteration_t;
/* An "union" node. These are created for the "|" operator. */
typedef struct {
tre_ast_node_t *left;
tre_ast_node_t *right;
} tre_union_t;
tre_ast_node_t *
tre_ast_new_node(tre_mem_t mem, tre_ast_type_t type, size_t size);
tre_ast_node_t *
tre_ast_new_literal(tre_mem_t mem, int code_min, int code_max);
tre_ast_node_t *
tre_ast_new_iter(tre_mem_t mem, tre_ast_node_t *arg, int min, int max,
int minimal);
tre_ast_node_t *
tre_ast_new_union(tre_mem_t mem, tre_ast_node_t *left, tre_ast_node_t *right);
tre_ast_node_t *
tre_ast_new_catenation(tre_mem_t mem, tre_ast_node_t *left,
tre_ast_node_t *right);
#ifdef TRE_DEBUG
void
tre_ast_print(tre_ast_node_t *tree);
/* XXX - rethink AST printing API */
void
tre_print_params(int *params);
#endif /* TRE_DEBUG */
#endif /* TRE_AST_H */
/* EOF */

2673
deps/tre/lib/tre-compile.c vendored Normal file

File diff suppressed because it is too large Load diff

27
deps/tre/lib/tre-compile.h vendored Normal file
View file

@ -0,0 +1,27 @@
/*
tre-compile.h: Regex compilation definitions
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
#ifndef TRE_COMPILE_H
#define TRE_COMPILE_H 1
typedef struct {
int position;
int code_min;
int code_max;
int *tags;
int assertions;
tre_ctype_t class;
tre_ctype_t *neg_classes;
int backref;
int *params;
} tre_pos_and_tags_t;
#endif /* TRE_COMPILE_H */
/* EOF */

73
deps/tre/lib/tre-filter.c vendored Normal file
View file

@ -0,0 +1,73 @@
/*
tre-filter.c: Histogram filter to quickly find regexp match candidates
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
/* The idea of this filter is quite simple. First, let's assume the
search pattern is a simple string. In order for a substring of a
longer string to match the search pattern, it must have the same
numbers of different characters as the pattern, and those
characters must occur in the same order as they occur in pattern. */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* HAVE_CONFIG_H */
#include <stdio.h>
#include "tre-internal.h"
#include "tre-filter.h"
int
tre_filter_find(const unsigned char *str, size_t len, tre_filter_t *filter)
{
unsigned short counts[256];
unsigned int i;
unsigned int window_len = filter->window_len;
tre_filter_profile_t *profile = filter->profile;
const unsigned char *str_orig = str;
DPRINT(("tre_filter_find: %.*s\n", len, str));
for (i = 0; i < elementsof(counts); i++)
counts[i] = 0;
i = 0;
while (*str && i < window_len && i < len)
{
counts[*str]++;
i++;
str++;
len--;
}
while (len > 0)
{
tre_filter_profile_t *p;
counts[*str]++;
counts[*(str - window_len)]--;
p = profile;
while (p->ch)
{
if (counts[p->ch] < p->count)
break;
p++;
}
if (!p->ch)
{
DPRINT(("Found possible match at %d\n",
str - str_orig));
return str - str_orig;
}
else
{
DPRINT(("No match so far...\n"));
}
len--;
str++;
}
DPRINT(("This string cannot match.\n"));
return -1;
}

19
deps/tre/lib/tre-filter.h vendored Normal file
View file

@ -0,0 +1,19 @@
typedef struct {
unsigned char ch;
unsigned char count;
} tre_filter_profile_t;
typedef struct {
/* Length of the window where the character counts are kept. */
int window_len;
/* Required character counts table. */
tre_filter_profile_t *profile;
} tre_filter_t;
int
tre_filter_find(const unsigned char *str, size_t len, tre_filter_t *filter);

319
deps/tre/lib/tre-internal.h vendored Normal file
View file

@ -0,0 +1,319 @@
/*
tre-internal.h - TRE internal definitions
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
#ifndef TRE_INTERNAL_H
#define TRE_INTERNAL_H 1
#ifdef HAVE_WCHAR_H
#include <wchar.h>
#endif /* HAVE_WCHAR_H */
#ifdef HAVE_WCTYPE_H
#include <wctype.h>
#endif /* HAVE_WCTYPE_H */
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif /* HAVE_SYS_TYPES_H */
#include <limits.h>
#include <ctype.h>
#include "../local_includes/tre.h"
#define TRE_MAX_RE 65536
#define TRE_MAX_STRING INT_MAX
#define TRE_MAX_STACK 1048576
#ifdef TRE_DEBUG
#include <stdio.h>
#define DPRINT(msg) do {printf msg; fflush(stdout);} while(/*CONSTCOND*/(void)0,0)
#else /* !TRE_DEBUG */
#define DPRINT(msg) do { } while(/*CONSTCOND*/(void)0,0)
#endif /* !TRE_DEBUG */
#define elementsof(x) ( sizeof(x) / sizeof(x[0]) )
#ifdef HAVE_MBRTOWC
#define tre_mbrtowc(pwc, s, n, ps) (mbrtowc((pwc), (s), (n), (ps)))
#else /* !HAVE_MBRTOWC */
#ifdef HAVE_MBTOWC
#define tre_mbrtowc(pwc, s, n, ps) (mbtowc((pwc), (s), (n)))
#endif /* HAVE_MBTOWC */
#endif /* !HAVE_MBRTOWC */
#ifdef TRE_MULTIBYTE
#ifdef HAVE_MBSTATE_T
#define TRE_MBSTATE
#endif /* TRE_MULTIBYTE */
#endif /* HAVE_MBSTATE_T */
/* Define the character types and functions. */
#ifdef TRE_WCHAR
/* Wide characters. */
typedef wint_t tre_cint_t;
#if WCHAR_MAX <= INT_MAX
#define TRE_CHAR_MAX WCHAR_MAX
#else /* WCHAR_MAX > INT_MAX */
#define TRE_CHAR_MAX INT_MAX
#endif
#ifdef TRE_MULTIBYTE
#define TRE_MB_CUR_MAX MB_CUR_MAX
#else /* !TRE_MULTIBYTE */
#define TRE_MB_CUR_MAX 1
#endif /* !TRE_MULTIBYTE */
#define tre_isalnum iswalnum
#define tre_isalpha iswalpha
#ifdef HAVE_ISWBLANK
#define tre_isblank iswblank
#endif /* HAVE_ISWBLANK */
#define tre_iscntrl iswcntrl
#define tre_isdigit iswdigit
#define tre_isgraph iswgraph
#define tre_islower iswlower
#define tre_isprint iswprint
#define tre_ispunct iswpunct
#define tre_isspace iswspace
#define tre_isupper iswupper
#define tre_isxdigit iswxdigit
#define tre_tolower towlower
#define tre_toupper towupper
#define tre_strlen wcslen
#else /* !TRE_WCHAR */
/* 8 bit characters. */
typedef short tre_cint_t;
#define TRE_CHAR_MAX 255
#define TRE_MB_CUR_MAX 1
#define tre_isalnum isalnum
#define tre_isalpha isalpha
#ifdef HAVE_ISASCII
#define tre_isascii isascii
#endif /* HAVE_ISASCII */
#ifdef HAVE_ISBLANK
#define tre_isblank isblank
#endif /* HAVE_ISBLANK */
#define tre_iscntrl iscntrl
#define tre_isdigit isdigit
#define tre_isgraph isgraph
#define tre_islower islower
#define tre_isprint isprint
#define tre_ispunct ispunct
#define tre_isspace isspace
#define tre_isupper isupper
#define tre_isxdigit isxdigit
#define tre_tolower(c) (tre_cint_t)(tolower(c))
#define tre_toupper(c) (tre_cint_t)(toupper(c))
#define tre_strlen(s) (strlen((const char*)s))
#endif /* !TRE_WCHAR */
#if defined(TRE_WCHAR) && defined(HAVE_ISWCTYPE) && defined(HAVE_WCTYPE)
#define TRE_USE_SYSTEM_WCTYPE 1
#endif
#ifdef TRE_USE_SYSTEM_WCTYPE
/* Use system provided iswctype() and wctype(). */
typedef wctype_t tre_ctype_t;
#define tre_isctype iswctype
#define tre_ctype wctype
#else /* !TRE_USE_SYSTEM_WCTYPE */
/* Define our own versions of iswctype() and wctype(). */
typedef int (*tre_ctype_t)(tre_cint_t);
#define tre_isctype(c, type) ( (type)(c) )
tre_ctype_t tre_ctype(const char *name);
#endif /* !TRE_USE_SYSTEM_WCTYPE */
typedef enum { STR_WIDE, STR_BYTE, STR_MBS, STR_USER } tre_str_type_t;
/* Returns number of bytes to add to (char *)ptr to make it
properly aligned for the type. */
#define ALIGN(ptr, type) \
((((long)ptr) % sizeof(type)) \
? (sizeof(type) - (((long)ptr) % sizeof(type))) \
: 0)
#undef MAX
#undef MIN
#define MAX(a, b) (((a) >= (b)) ? (a) : (b))
#define MIN(a, b) (((a) <= (b)) ? (a) : (b))
/* Define STRF to the correct printf formatter for strings. */
#ifdef TRE_WCHAR
#define STRF "ls"
#else /* !TRE_WCHAR */
#define STRF "s"
#endif /* !TRE_WCHAR */
/* TNFA transition type. A TNFA state is an array of transitions,
the terminator is a transition with NULL `state'. */
typedef struct tnfa_transition tre_tnfa_transition_t;
struct tnfa_transition {
/* Range of accepted characters. */
tre_cint_t code_min;
tre_cint_t code_max;
/* Pointer to the destination state. */
tre_tnfa_transition_t *state;
/* ID number of the destination state. */
int state_id;
/* -1 terminated array of tags (or NULL). */
int *tags;
/* Matching parameters settings (or NULL). */
int *params;
/* Assertion bitmap. */
int assertions;
/* Assertion parameters. */
union {
/* Character class assertion. */
tre_ctype_t class;
/* Back reference assertion. */
int backref;
} u;
/* Negative character class assertions. */
tre_ctype_t *neg_classes;
};
/* Assertions. */
#define ASSERT_AT_BOL 1 /* Beginning of line. */
#define ASSERT_AT_EOL 2 /* End of line. */
#define ASSERT_CHAR_CLASS 4 /* Character class in `class'. */
#define ASSERT_CHAR_CLASS_NEG 8 /* Character classes in `neg_classes'. */
#define ASSERT_AT_BOW 16 /* Beginning of word. */
#define ASSERT_AT_EOW 32 /* End of word. */
#define ASSERT_AT_WB 64 /* Word boundary. */
#define ASSERT_AT_WB_NEG 128 /* Not a word boundary. */
#define ASSERT_BACKREF 256 /* A back reference in `backref'. */
#define ASSERT_LAST 256
/* Tag directions. */
typedef enum {
TRE_TAG_MINIMIZE = 0,
TRE_TAG_MAXIMIZE = 1
} tre_tag_direction_t;
/* Parameters that can be changed dynamically while matching. */
typedef enum {
TRE_PARAM_COST_INS = 0,
TRE_PARAM_COST_DEL = 1,
TRE_PARAM_COST_SUBST = 2,
TRE_PARAM_COST_MAX = 3,
TRE_PARAM_MAX_INS = 4,
TRE_PARAM_MAX_DEL = 5,
TRE_PARAM_MAX_SUBST = 6,
TRE_PARAM_MAX_ERR = 7,
TRE_PARAM_DEPTH = 8,
TRE_PARAM_LAST = 9
} tre_param_t;
/* Unset matching parameter */
#define TRE_PARAM_UNSET -1
/* Signifies the default matching parameter value. */
#define TRE_PARAM_DEFAULT -2
/* Instructions to compute submatch register values from tag values
after a successful match. */
struct tre_submatch_data {
/* Tag that gives the value for rm_so (submatch start offset). */
int so_tag;
/* Tag that gives the value for rm_eo (submatch end offset). */
int eo_tag;
/* List of submatches this submatch is contained in. */
int *parents;
};
typedef struct tre_submatch_data tre_submatch_data_t;
typedef enum {
TRE_LITERAL_OPT_NONE = 0,
TRE_LITERAL_OPT_CONTAINS,
TRE_LITERAL_OPT_PREFIX,
TRE_LITERAL_OPT_SUFFIX,
TRE_LITERAL_OPT_EXACT
} tre_literal_opt_mode_t;
typedef struct {
unsigned char *data;
size_t len;
} tre_literal_opt_literal_t;
typedef struct {
tre_literal_opt_mode_t mode;
int nocase;
size_t num_literals;
/* Folded byte mapping used by the nocase fast path. */
unsigned char fold_map[256];
/* Literal index ranges grouped by the first literal byte. */
size_t start_offsets[257];
tre_literal_opt_literal_t *literals;
} tre_literal_opt_t;
/* TNFA definition. */
typedef struct tnfa tre_tnfa_t;
struct tnfa {
tre_tnfa_transition_t *transitions;
unsigned int num_transitions;
tre_tnfa_transition_t *initial;
tre_tnfa_transition_t *final;
tre_submatch_data_t *submatch_data;
char *firstpos_chars;
int first_char;
unsigned int num_submatches;
tre_tag_direction_t *tag_directions;
int *minimal_tags;
int num_tags;
int num_minimals;
int end_tag;
int num_states;
int cflags;
int have_backrefs;
int have_approx;
int params_depth;
tre_literal_opt_t literal_opt;
};
int
tre_compile(regex_t *preg, const tre_char_t *regex, size_t n, int cflags);
void
tre_free(regex_t *preg);
void
tre_fill_pmatch(size_t nmatch, regmatch_t pmatch[], int cflags,
const tre_tnfa_t *tnfa, int *tags, int match_eo);
reg_errcode_t
tre_tnfa_run_parallel(const tre_tnfa_t *tnfa, const void *string, ssize_t len,
tre_str_type_t type, int *match_tags, int eflags,
int *match_end_ofs);
reg_errcode_t
tre_tnfa_run_backtrack(const tre_tnfa_t *tnfa, const void *string, ssize_t len,
tre_str_type_t type, int *match_tags, int eflags,
int *match_end_ofs);
#ifdef TRE_APPROX
reg_errcode_t
tre_tnfa_run_approx(const tre_tnfa_t *tnfa, const void *string, ssize_t len,
tre_str_type_t type, int *match_tags, regamatch_t *match,
regaparams_t params, int eflags, int *match_end_ofs);
#endif /* TRE_APPROX */
#endif /* TRE_INTERNAL_H */
/* EOF */

676
deps/tre/lib/tre-match-backtrack.c vendored Normal file
View file

@ -0,0 +1,676 @@
/*
tre-match-backtrack.c - TRE backtracking regex matching engine
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
/*
This matcher is for regexps that use back referencing. Regexp matching
with back referencing is an NP-complete problem on the number of back
references. The easiest way to match them is to use a backtracking
routine which basically goes through all possible paths in the TNFA
and chooses the one which results in the best (leftmost and longest)
match. This can be spectacularly expensive and may run out of stack
space, but there really is no better known generic algorithm. Quoting
Henry Spencer from comp.compilers:
<URL: http://compilers.iecc.com/comparch/article/93-03-102>
POSIX.2 REs require longest match, which is really exciting to
implement since the obsolete ("basic") variant also includes
\<digit>. I haven't found a better way of tackling this than doing
a preliminary match using a DFA (or simulation) on a modified RE
that just replicates subREs for \<digit>, and then doing a
backtracking match to determine whether the subRE matches were
right. This can be rather slow, but I console myself with the
thought that people who use \<digit> deserve very slow execution.
(Pun unintentional but very appropriate.)
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* HAVE_CONFIG_H */
#ifdef TRE_USE_ALLOCA
/* AIX requires this to be the first thing in the file. */
#ifndef __GNUC__
# if HAVE_ALLOCA_H
# include <alloca.h>
# else
# ifdef _AIX
#pragma alloca
# else
# ifndef alloca /* predefined by HP cc +Olibcalls */
char *alloca ();
# endif
# endif
# endif
#endif
#endif /* TRE_USE_ALLOCA */
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_WCHAR_H
#include <wchar.h>
#endif /* HAVE_WCHAR_H */
#ifdef HAVE_WCTYPE_H
#include <wctype.h>
#endif /* HAVE_WCTYPE_H */
#ifndef TRE_WCHAR
#include <ctype.h>
#endif /* !TRE_WCHAR */
#ifdef HAVE_MALLOC_H
#include <malloc.h>
#endif /* HAVE_MALLOC_H */
#include "tre-internal.h"
#include "tre-mem.h"
#include "tre-match-utils.h"
#include "xmalloc.h"
typedef struct {
int pos;
const char *str_byte;
#ifdef TRE_WCHAR
const wchar_t *str_wide;
#endif /* TRE_WCHAR */
tre_tnfa_transition_t *state;
int state_id;
int next_c;
int *tags;
#ifdef TRE_MBSTATE
mbstate_t mbstate;
#endif /* TRE_MBSTATE */
} tre_backtrack_item_t;
typedef struct tre_backtrack_struct {
tre_backtrack_item_t item;
struct tre_backtrack_struct *prev;
struct tre_backtrack_struct *next;
} *tre_backtrack_t;
#ifdef TRE_WCHAR
#define BT_STACK_WIDE_IN(_str_wide) stack->item.str_wide = (_str_wide)
#define BT_STACK_WIDE_OUT (str_wide) = stack->item.str_wide
#else /* !TRE_WCHAR */
#define BT_STACK_WIDE_IN(_str_wide)
#define BT_STACK_WIDE_OUT
#endif /* !TRE_WCHAR */
#ifdef TRE_MBSTATE
#define BT_STACK_MBSTATE_IN stack->item.mbstate = (mbstate)
#define BT_STACK_MBSTATE_OUT (mbstate) = stack->item.mbstate
#else /* !TRE_MBSTATE */
#define BT_STACK_MBSTATE_IN
#define BT_STACK_MBSTATE_OUT
#endif /* !TRE_MBSTATE */
#ifdef TRE_USE_ALLOCA
#define tre_bt_mem_new tre_mem_newa
#define tre_bt_mem_alloc tre_mem_alloca
#define tre_bt_mem_destroy(obj) do { } while (0)
#define xafree(obj) do { } while (0) /* do nothing, obj was obtained with alloca() */
#else /* !TRE_USE_ALLOCA */
#define tre_bt_mem_new tre_mem_new
#define tre_bt_mem_alloc tre_mem_alloc
#define tre_bt_mem_destroy tre_mem_destroy
#define xafree(obj) xfree(obj)
#endif /* !TRE_USE_ALLOCA */
#define BT_STACK_PUSH(_pos, _str_byte, _str_wide, _state, _state_id, _next_c, _tags, _mbstate) \
do \
{ \
int i; \
if (!stack->next) \
{ \
tre_backtrack_t s; \
s = tre_bt_mem_alloc(mem, sizeof(*s)); \
if (!s) \
{ \
tre_bt_mem_destroy(mem); \
if (tags) \
xafree(tags); \
if (pmatch) \
xafree(pmatch); \
if (states_seen) \
xafree(states_seen); \
return REG_ESPACE; \
} \
s->prev = stack; \
s->next = NULL; \
s->item.tags = tre_bt_mem_alloc(mem, \
sizeof(*tags) * tnfa->num_tags); \
if (!s->item.tags) \
{ \
tre_bt_mem_destroy(mem); \
if (tags) \
xafree(tags); \
if (pmatch) \
xafree(pmatch); \
if (states_seen) \
xafree(states_seen); \
return REG_ESPACE; \
} \
stack->next = s; \
stack = s; \
} \
else \
stack = stack->next; \
stack->item.pos = (_pos); \
stack->item.str_byte = (_str_byte); \
BT_STACK_WIDE_IN(_str_wide); \
stack->item.state = (_state); \
stack->item.state_id = (_state_id); \
stack->item.next_c = (_next_c); \
for (i = 0; i < tnfa->num_tags; i++) \
stack->item.tags[i] = (_tags)[i]; \
BT_STACK_MBSTATE_IN; \
} \
while (/*CONSTCOND*/(void)0,0)
#define BT_STACK_POP() \
do \
{ \
int i; \
assert(stack->prev); \
pos = stack->item.pos; \
if (type == STR_USER) \
str_source->rewind(pos + pos_add_next, str_source->context); \
str_byte = stack->item.str_byte; \
BT_STACK_WIDE_OUT; \
state = stack->item.state; \
next_c = (tre_char_t) stack->item.next_c; \
for (i = 0; i < tnfa->num_tags; i++) \
tags[i] = stack->item.tags[i]; \
BT_STACK_MBSTATE_OUT; \
stack = stack->prev; \
} \
while (/*CONSTCOND*/(void)0,0)
#undef MIN
#define MIN(a, b) ((a) <= (b) ? (a) : (b))
reg_errcode_t
tre_tnfa_run_backtrack(const tre_tnfa_t *tnfa, const void *string,
ssize_t len, tre_str_type_t type, int *match_tags,
int eflags, int *match_end_ofs)
{
/* State variables required by GET_NEXT_WCHAR. */
tre_char_t prev_c = 0, next_c = 0;
const char *str_byte = string;
ssize_t pos = 0;
unsigned int pos_add_next = 1;
#ifdef TRE_WCHAR
const wchar_t *str_wide = string;
#ifdef TRE_MBSTATE
mbstate_t mbstate;
#endif /* TRE_MBSTATE */
#endif /* TRE_WCHAR */
int reg_notbol = eflags & REG_NOTBOL;
int reg_noteol = eflags & REG_NOTEOL;
int reg_newline = tnfa->cflags & REG_NEWLINE;
int str_user_end = 0;
/* These are used to remember the necessary values of the above
variables to return to the position where the current search
started from. */
int next_c_start;
const char *str_byte_start;
int pos_start = -1;
#ifdef TRE_WCHAR
const wchar_t *str_wide_start;
#endif /* TRE_WCHAR */
#ifdef TRE_MBSTATE
mbstate_t mbstate_start;
#endif /* TRE_MBSTATE */
reg_errcode_t ret;
/* End offset of best match so far, or -1 if no match found yet. */
int match_eo = -1;
/* Tag arrays. */
int *next_tags, *tags = NULL;
/* Current TNFA state. */
tre_tnfa_transition_t *state;
int *states_seen = NULL;
/* Memory allocator to for allocating the backtracking stack. */
tre_mem_t mem = tre_bt_mem_new();
/* The backtracking stack. */
tre_backtrack_t stack;
tre_tnfa_transition_t *trans_i;
regmatch_t *pmatch = NULL;
/*
* TRE internals tend to use int instead of size_t for positions or
* lengths and don't check for overflow. This will take time to fix
* properly. In the meantime, simply limit the input to what we can
* handle.
*/
if (len > TRE_MAX_STRING)
len = TRE_MAX_STRING;
#ifdef TRE_MBSTATE
memset(&mbstate, '\0', sizeof(mbstate));
#endif /* TRE_MBSTATE */
if (!mem)
return REG_ESPACE;
stack = tre_bt_mem_alloc(mem, sizeof(*stack));
if (!stack)
{
ret = REG_ESPACE;
goto error_exit;
}
stack->prev = NULL;
stack->next = NULL;
DPRINT(("tnfa_execute_backtrack, input type %d\n", type));
DPRINT(("len = %zd\n", len));
#ifdef TRE_USE_ALLOCA
tags = alloca(sizeof(*tags) * tnfa->num_tags);
pmatch = alloca(sizeof(*pmatch) * tnfa->num_submatches);
states_seen = alloca(sizeof(*states_seen) * tnfa->num_states);
#else /* !TRE_USE_ALLOCA */
if (tnfa->num_tags)
{
tags = xmalloc(sizeof(*tags) * tnfa->num_tags);
if (!tags)
{
ret = REG_ESPACE;
goto error_exit;
}
}
if (tnfa->num_submatches)
{
pmatch = xmalloc(sizeof(*pmatch) * tnfa->num_submatches);
if (!pmatch)
{
ret = REG_ESPACE;
goto error_exit;
}
}
if (tnfa->num_states)
{
states_seen = xmalloc(sizeof(*states_seen) * tnfa->num_states);
if (!states_seen)
{
ret = REG_ESPACE;
goto error_exit;
}
}
#endif /* !TRE_USE_ALLOCA */
retry:
{
int i;
for (i = 0; i < tnfa->num_tags; i++)
{
tags[i] = -1;
if (match_tags)
match_tags[i] = -1;
}
for (i = 0; i < tnfa->num_states; i++)
states_seen[i] = 0;
}
state = NULL;
pos = pos_start;
if (type == STR_USER)
str_source->rewind(pos + pos_add_next, str_source->context);
GET_NEXT_WCHAR();
pos_start = pos;
next_c_start = next_c;
str_byte_start = str_byte;
#ifdef TRE_WCHAR
str_wide_start = str_wide;
#endif /* TRE_WCHAR */
#ifdef TRE_MBSTATE
mbstate_start = mbstate;
#endif /* TRE_MBSTATE */
/* Handle initial states. */
next_tags = NULL;
for (trans_i = tnfa->initial; trans_i->state; trans_i++)
{
DPRINT(("> init %p, prev_c %lc\n", trans_i->state, (tre_cint_t)prev_c));
if (trans_i->assertions && CHECK_ASSERTIONS(trans_i->assertions))
{
DPRINT(("assert failed\n"));
continue;
}
if (state == NULL)
{
/* Start from this state. */
state = trans_i->state;
next_tags = trans_i->tags;
}
else
{
/* Backtrack to this state. */
DPRINT(("saving state %d for backtracking\n", trans_i->state_id));
BT_STACK_PUSH(pos, str_byte, str_wide, trans_i->state,
trans_i->state_id, next_c, tags, mbstate);
{
int *tmp = trans_i->tags;
if (tmp)
while (*tmp >= 0)
stack->item.tags[*tmp++] = pos;
}
}
}
if (next_tags)
for (; *next_tags >= 0; next_tags++)
tags[*next_tags] = pos;
DPRINT(("entering match loop, pos %zd, str_byte %p\n", pos, str_byte));
DPRINT(("pos:chr/code | state and tags\n"));
DPRINT(("-------------+------------------------------------------------\n"));
if (state == NULL)
goto backtrack;
while (/*CONSTCOND*/(void)1,1)
{
tre_tnfa_transition_t *next_state;
int empty_br_match;
DPRINT(("start loop\n"));
if (state == tnfa->final)
{
DPRINT((" match found, %d %zd\n", match_eo, pos));
if (match_eo < pos
|| (match_eo == pos
&& match_tags
&& tre_tag_order(tnfa->num_tags, tnfa->tag_directions,
tags, match_tags)))
{
int i;
/* This match wins the previous match. */
DPRINT((" win previous\n"));
match_eo = pos;
if (match_tags)
for (i = 0; i < tnfa->num_tags; i++)
match_tags[i] = tags[i];
}
/* Our TNFAs never have transitions leaving from the final state,
so we jump right to backtracking. */
goto backtrack;
}
#ifdef TRE_DEBUG
DPRINT(("%3zd:%2lc/%05d | %p ", pos, (tre_cint_t)next_c, (int)next_c,
state));
{
int i;
for (i = 0; i < tnfa->num_tags; i++)
DPRINT(("%d%s", tags[i], i < tnfa->num_tags - 1 ? ", " : ""));
DPRINT(("\n"));
}
#endif /* TRE_DEBUG */
/* Go to the next character in the input string. */
empty_br_match = 0;
trans_i = state;
if (trans_i->state && trans_i->assertions & ASSERT_BACKREF)
{
/* This is a back reference state. All transitions leaving from
this state have the same back reference "assertion". Instead
of reading the next character, we match the back reference. */
int so, eo, bt = trans_i->u.backref;
int bt_len;
int result;
DPRINT((" should match back reference %d\n", bt));
/* Get the substring we need to match against. Remember to
turn off REG_NOSUB temporarily. */
tre_fill_pmatch(bt + 1, pmatch, tnfa->cflags & ~REG_NOSUB,
tnfa, tags, pos);
so = pmatch[bt].rm_so;
eo = pmatch[bt].rm_eo;
bt_len = eo - so;
#ifdef TRE_DEBUG
{
int slen;
if (len < 0)
slen = bt_len;
else
slen = MIN(bt_len, len - pos);
if (type == STR_BYTE)
{
DPRINT((" substring (len %d) is [%d, %d[: '%.*s'\n",
bt_len, so, eo, bt_len, (char*)string + so));
DPRINT((" current string is '%.*s'\n", slen, str_byte - 1));
}
#ifdef TRE_WCHAR
else if (type == STR_WIDE)
{
DPRINT((" substring (len %d) is [%d, %d[: '%.*" STRF "'\n",
bt_len, so, eo, bt_len, (wchar_t*)string + so));
DPRINT((" current string is '%.*" STRF "'\n",
slen, str_wide - 1));
}
#endif /* TRE_WCHAR */
}
#endif
if (len < 0)
{
if (type == STR_USER)
result = str_source->compare((unsigned)so, (unsigned)pos,
(unsigned)bt_len,
str_source->context);
#ifdef TRE_WCHAR
else if (type == STR_WIDE)
result = wcsncmp((const wchar_t*)string + so, str_wide - 1,
(size_t)bt_len);
#endif /* TRE_WCHAR */
else
result = strncmp((const char*)string + so, str_byte - 1,
(size_t)bt_len);
}
else if (len - pos < bt_len)
result = 1;
#ifdef TRE_WCHAR
else if (type == STR_WIDE)
result = wmemcmp((const wchar_t*)string + so, str_wide - 1,
(size_t)bt_len);
#endif /* TRE_WCHAR */
else
result = memcmp((const char*)string + so, str_byte - 1,
(size_t)bt_len);
if (result == 0)
{
/* Back reference matched. Check for infinite loop. */
if (bt_len == 0)
empty_br_match = 1;
if (empty_br_match && states_seen[trans_i->state_id])
{
DPRINT((" avoid loop\n"));
goto backtrack;
}
states_seen[trans_i->state_id] = empty_br_match;
/* Advance in input string and resync `prev_c', `next_c'
and pos. */
DPRINT((" back reference matched\n"));
str_byte += bt_len - 1;
#ifdef TRE_WCHAR
str_wide += bt_len - 1;
#endif /* TRE_WCHAR */
pos += bt_len - 1;
GET_NEXT_WCHAR();
DPRINT((" pos now %zd\n", pos));
}
else
{
DPRINT((" back reference did not match\n"));
goto backtrack;
}
}
else
{
/* Check for end of string. */
if (len < 0)
{
if (type == STR_USER)
{
if (str_user_end)
goto backtrack;
}
else if (next_c == L'\0' || pos >= TRE_MAX_STRING)
goto backtrack;
}
else
{
if (pos >= len)
goto backtrack;
}
/* Read the next character. */
GET_NEXT_WCHAR();
}
next_state = NULL;
for (trans_i = state; trans_i->state; trans_i++)
{
DPRINT((" transition %d-%d (%c-%c) %d to %d\n",
trans_i->code_min, trans_i->code_max,
trans_i->code_min, trans_i->code_max,
trans_i->assertions, trans_i->state_id));
if (trans_i->code_min <= (tre_cint_t)prev_c
&& trans_i->code_max >= (tre_cint_t)prev_c)
{
if (trans_i->assertions
&& (CHECK_ASSERTIONS(trans_i->assertions)
|| CHECK_CHAR_CLASSES(trans_i, tnfa, eflags)))
{
DPRINT((" assertion failed\n"));
continue;
}
if (next_state == NULL)
{
/* First matching transition. */
DPRINT((" Next state is %d\n", trans_i->state_id));
next_state = trans_i->state;
next_tags = trans_i->tags;
}
else
{
/* Second matching transition. We may need to backtrack here
to take this transition instead of the first one, so we
push this transition in the backtracking stack so we can
jump back here if needed. */
DPRINT((" saving state %d for backtracking\n",
trans_i->state_id));
BT_STACK_PUSH(pos, str_byte, str_wide, trans_i->state,
trans_i->state_id, next_c, tags, mbstate);
{
int *tmp;
for (tmp = trans_i->tags; tmp && *tmp >= 0; tmp++)
stack->item.tags[*tmp] = pos;
}
#if 0 /* XXX - it's important not to look at all transitions here to keep
the stack small! */
break;
#endif
}
}
}
if (next_state != NULL)
{
/* Matching transitions were found. Take the first one. */
state = next_state;
/* Update the tag values. */
if (next_tags)
while (*next_tags >= 0)
tags[*next_tags++] = pos;
}
else
{
backtrack:
/* A matching transition was not found. Try to backtrack. */
if (stack->prev)
{
DPRINT((" backtracking\n"));
if (stack->item.state->assertions & ASSERT_BACKREF)
{
DPRINT((" states_seen[%d] = 0\n",
stack->item.state_id));
states_seen[stack->item.state_id] = 0;
}
BT_STACK_POP();
}
else if (match_eo < 0)
{
/* Try starting from a later position in the input string. */
/* Check for end of string. */
if (len < 0)
{
if (next_c_start == L'\0' || pos_start >= TRE_MAX_STRING)
{
DPRINT(("end of string.\n"));
break;
}
}
else
{
if (pos_start >= len)
{
DPRINT(("end of string.\n"));
break;
}
}
DPRINT(("restarting from next start position\n"));
next_c = (tre_char_t) next_c_start;
#ifdef TRE_MBSTATE
mbstate = mbstate_start;
#endif /* TRE_MBSTATE */
str_byte = str_byte_start;
#ifdef TRE_WCHAR
str_wide = str_wide_start;
#endif /* TRE_WCHAR */
goto retry;
}
else
{
DPRINT(("finished\n"));
break;
}
}
}
ret = match_eo >= 0 ? REG_OK : REG_NOMATCH;
*match_end_ofs = match_eo;
error_exit:
tre_bt_mem_destroy(mem);
#ifndef TRE_USE_ALLOCA
if (tags)
xafree(tags);
if (pmatch)
xafree(pmatch);
if (states_seen)
xafree(states_seen);
#endif /* !TRE_USE_ALLOCA */
return ret;
}

538
deps/tre/lib/tre-match-parallel.c vendored Normal file
View file

@ -0,0 +1,538 @@
/*
tre-match-parallel.c - TRE parallel regex matching engine
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
/*
This algorithm searches for matches basically by reading characters
in the searched string one by one, starting at the beginning. All
matching paths in the TNFA are traversed in parallel. When two or
more paths reach the same state, exactly one is chosen according to
tag ordering rules; if returning submatches is not required it does
not matter which path is chosen.
The worst case time required for finding the leftmost and longest
match, or determining that there is no match, is always linearly
dependent on the length of the text being searched.
This algorithm cannot handle TNFAs with back referencing nodes.
See `tre-match-backtrack.c'.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* HAVE_CONFIG_H */
#ifdef TRE_USE_ALLOCA
/* AIX requires this to be the first thing in the file. */
#ifndef __GNUC__
# if HAVE_ALLOCA_H
# include <alloca.h>
# else
# ifdef _AIX
#pragma alloca
# else
# ifndef alloca /* predefined by HP cc +Olibcalls */
char *alloca ();
# endif
# endif
# endif
#endif
#endif /* TRE_USE_ALLOCA */
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_WCHAR_H
#include <wchar.h>
#endif /* HAVE_WCHAR_H */
#ifdef HAVE_WCTYPE_H
#include <wctype.h>
#endif /* HAVE_WCTYPE_H */
#ifndef TRE_WCHAR
#include <ctype.h>
#endif /* !TRE_WCHAR */
#ifdef HAVE_MALLOC_H
#include <malloc.h>
#endif /* HAVE_MALLOC_H */
#include "tre-internal.h"
#include "tre-match-utils.h"
#include "xmalloc.h"
typedef struct {
tre_tnfa_transition_t *state;
int *tags;
} tre_tnfa_reach_t;
typedef struct {
int pos;
int **tags;
} tre_reach_pos_t;
#ifdef TRE_DEBUG
static void
tre_print_reach(const tre_tnfa_reach_t *reach, int num_tags)
{
int i;
while (reach->state != NULL)
{
DPRINT((" %p", (void *)reach->state));
if (num_tags > 0)
{
DPRINT(("/"));
for (i = 0; i < num_tags; i++)
{
DPRINT(("%d:%d", i, reach->tags[i]));
if (i < (num_tags-1))
DPRINT((","));
}
}
reach++;
}
DPRINT(("\n"));
}
#endif /* TRE_DEBUG */
reg_errcode_t
tre_tnfa_run_parallel(const tre_tnfa_t *tnfa, const void *string, ssize_t len,
tre_str_type_t type, int *match_tags, int eflags,
int *match_end_ofs)
{
/* State variables required by GET_NEXT_WCHAR. */
tre_char_t prev_c = 0, next_c = 0;
const char *str_byte = string;
ssize_t pos = -1;
unsigned int pos_add_next = 1;
#ifdef TRE_WCHAR
const wchar_t *str_wide = string;
#ifdef TRE_MBSTATE
mbstate_t mbstate;
#endif /* TRE_MBSTATE */
#endif /* TRE_WCHAR */
reg_errcode_t ret;
int reg_notbol = eflags & REG_NOTBOL;
int reg_noteol = eflags & REG_NOTEOL;
int reg_newline = tnfa->cflags & REG_NEWLINE;
int str_user_end = 0;
char *buf;
tre_tnfa_transition_t *trans_i;
tre_tnfa_reach_t *reach, *reach_next, *reach_i, *reach_next_i;
tre_reach_pos_t *reach_pos;
int *tag_i;
int num_tags, i;
int match_eo = -1; /* end offset of match (-1 if no match found yet) */
int new_match = 0;
int *tmp_tags = NULL;
int *tmp_iptr;
/*
* TRE internals tend to use int instead of size_t for positions or
* lengths and don't check for overflow. This will take time to fix
* properly. In the meantime, simply limit the input to what we can
* handle.
*/
if (len > TRE_MAX_STRING)
len = TRE_MAX_STRING;
#ifdef TRE_MBSTATE
memset(&mbstate, '\0', sizeof(mbstate));
#endif /* TRE_MBSTATE */
DPRINT(("tre_tnfa_run_parallel, input type %d\n", type));
if (!match_tags)
num_tags = 0;
else
num_tags = tnfa->num_tags;
/* Allocate memory for temporary data required for matching. This needs to
be done for every matching operation to be thread safe. This allocates
everything in a single large block from the stack frame using alloca()
or with malloc() if alloca is unavailable. */
{
size_t tbytes, rbytes, pbytes, xbytes, total_bytes;
size_t num_states = (size_t)tnfa->num_states;
size_t state_tag_bytes, reach_bytes;
size_t padding = (sizeof(long) - 1) * 4;
char *tmp_buf;
if (num_states > SIZE_MAX / sizeof(*reach_pos))
return REG_ESPACE;
pbytes = sizeof(*reach_pos) * num_states;
if (num_states + 1 > SIZE_MAX / sizeof(*reach_next))
return REG_ESPACE;
rbytes = sizeof(*reach_next) * (num_states + 1);
if ((size_t)num_tags > SIZE_MAX / sizeof(*tmp_tags))
return REG_ESPACE;
tbytes = sizeof(*tmp_tags) * (size_t)num_tags;
if ((size_t)num_tags > SIZE_MAX / sizeof(int))
return REG_ESPACE;
xbytes = sizeof(int) * (size_t)num_tags;
if (num_states > 0 && xbytes > SIZE_MAX / num_states)
return REG_ESPACE;
state_tag_bytes = xbytes * num_states;
if (rbytes > SIZE_MAX - state_tag_bytes)
return REG_ESPACE;
reach_bytes = rbytes + state_tag_bytes;
if (reach_bytes > (SIZE_MAX - padding - tbytes - pbytes) / 2)
return REG_ESPACE;
/* Compute the length of the block we need. */
total_bytes =
padding + reach_bytes * 2 + tbytes + pbytes;
/* Allocate the memory. */
#ifdef TRE_USE_ALLOCA
buf = alloca(total_bytes);
#else /* !TRE_USE_ALLOCA */
buf = xmalloc(total_bytes);
#endif /* !TRE_USE_ALLOCA */
if (buf == NULL)
return REG_ESPACE;
memset(buf, 0, total_bytes);
/* Get the various pointers within tmp_buf (properly aligned). */
tmp_tags = (void *)buf;
tmp_buf = buf + tbytes;
tmp_buf += ALIGN(tmp_buf, long);
reach_next = (void *)tmp_buf;
tmp_buf += rbytes;
tmp_buf += ALIGN(tmp_buf, long);
reach = (void *)tmp_buf;
tmp_buf += rbytes;
tmp_buf += ALIGN(tmp_buf, long);
reach_pos = (void *)tmp_buf;
tmp_buf += pbytes;
tmp_buf += ALIGN(tmp_buf, long);
for (i = 0; i < tnfa->num_states; i++)
{
reach[i].tags = (void *)tmp_buf;
tmp_buf += xbytes;
reach_next[i].tags = (void *)tmp_buf;
tmp_buf += xbytes;
}
}
for (i = 0; i < tnfa->num_states; i++)
reach_pos[i].pos = -1;
/* If only one character can start a match, find it first. */
if (tnfa->first_char >= 0 && type == STR_BYTE && str_byte)
{
const char *orig_str = str_byte;
int first = tnfa->first_char;
if (len >= 0)
str_byte = memchr(orig_str, first, (size_t)len);
else
str_byte = strchr(orig_str, first);
if (str_byte == NULL)
{
#ifndef TRE_USE_ALLOCA
if (buf)
xfree(buf);
#endif /* !TRE_USE_ALLOCA */
return REG_NOMATCH;
}
DPRINT(("skipped %lu chars\n", (unsigned long)(str_byte - orig_str)));
if (str_byte >= orig_str + 1)
prev_c = (unsigned char)*(str_byte - 1);
next_c = (unsigned char)*str_byte;
pos = str_byte - orig_str;
if (len < 0 || pos < len)
str_byte++;
}
else
{
GET_NEXT_WCHAR();
pos = 0;
}
#if 0
/* Skip over characters that cannot possibly be the first character
of a match. */
if (tnfa->firstpos_chars != NULL)
{
char *chars = tnfa->firstpos_chars;
if (len < 0)
{
const char *orig_str = str_byte;
/* XXX - use strpbrk() and wcspbrk() because they might be
optimized for the target architecture. Try also strcspn()
and wcscspn() and compare the speeds. */
while (next_c != L'\0' && !chars[next_c])
{
next_c = *str_byte++;
}
prev_c = *(str_byte - 2);
pos += str_byte - orig_str;
DPRINT(("skipped %d chars\n", str_byte - orig_str));
}
else
{
while (pos <= len && !chars[next_c])
{
prev_c = next_c;
next_c = (unsigned char)(*str_byte++);
pos++;
}
}
}
#endif
DPRINT(("length: %zd\n", len));
DPRINT(("pos:chr/code | states and tags\n"));
DPRINT(("-------------+------------------------------------------------\n"));
reach_next_i = reach_next;
while (/*CONSTCOND*/(void)1,1)
{
/* If no match found yet, add the initial states to `reach_next'. */
if (match_eo < 0)
{
DPRINT((" init >"));
trans_i = tnfa->initial;
while (trans_i->state != NULL)
{
if (reach_pos[trans_i->state_id].pos < pos)
{
if (trans_i->assertions
&& CHECK_ASSERTIONS(trans_i->assertions))
{
DPRINT(("assertion failed\n"));
trans_i++;
continue;
}
DPRINT((" %p", (void *)trans_i->state));
reach_next_i->state = trans_i->state;
for (i = 0; i < num_tags; i++)
reach_next_i->tags[i] = -1;
tag_i = trans_i->tags;
if (tag_i)
while (*tag_i >= 0)
{
if (*tag_i < num_tags)
reach_next_i->tags[*tag_i] = pos;
tag_i++;
}
if (reach_next_i->state == tnfa->final)
{
DPRINT((" found empty match\n"));
match_eo = pos;
new_match = 1;
for (i = 0; i < num_tags; i++)
match_tags[i] = reach_next_i->tags[i];
}
reach_pos[trans_i->state_id].pos = pos;
reach_pos[trans_i->state_id].tags = &reach_next_i->tags;
reach_next_i++;
}
trans_i++;
}
DPRINT(("\n"));
reach_next_i->state = NULL;
}
else
{
if (num_tags == 0 || reach_next_i == reach_next)
/* We have found a match. */
break;
}
/* Check for end of string. */
if (len < 0)
{
if (type == STR_USER)
{
if (str_user_end)
break;
}
else if (next_c == L'\0' || pos >= TRE_MAX_STRING)
break;
}
else
{
if (pos >= len)
break;
}
GET_NEXT_WCHAR();
#ifdef TRE_DEBUG
DPRINT(("%3zd:%2lc/%05d |", pos - 1, (tre_cint_t)prev_c, (int)prev_c));
tre_print_reach(reach_next, num_tags);
DPRINT(("%3zd:%2lc/%05d |", pos, (tre_cint_t)next_c, (int)next_c));
tre_print_reach(reach_next, num_tags);
#endif /* TRE_DEBUG */
/* Swap `reach' and `reach_next'. */
reach_i = reach;
reach = reach_next;
reach_next = reach_i;
/* For each state in `reach', weed out states that don't fulfill the
minimal matching conditions. */
if (tnfa->num_minimals && new_match)
{
new_match = 0;
reach_next_i = reach_next;
for (reach_i = reach; reach_i->state; reach_i++)
{
int skip = 0;
for (i = 0; tnfa->minimal_tags[i] >= 0; i += 2)
{
int end = tnfa->minimal_tags[i];
int start = tnfa->minimal_tags[i + 1];
DPRINT((" Minimal start %d, end %d\n", start, end));
if (end >= num_tags)
{
DPRINT((" Throwing %p out.\n", reach_i->state));
skip = 1;
break;
}
else if (reach_i->tags[start] == match_tags[start]
&& reach_i->tags[end] < match_tags[end])
{
DPRINT((" Throwing %p out because t%d < %d\n",
reach_i->state, end, match_tags[end]));
skip = 1;
break;
}
}
if (!skip)
{
reach_next_i->state = reach_i->state;
tmp_iptr = reach_next_i->tags;
reach_next_i->tags = reach_i->tags;
reach_i->tags = tmp_iptr;
reach_next_i++;
}
}
reach_next_i->state = NULL;
/* Swap `reach' and `reach_next'. */
reach_i = reach;
reach = reach_next;
reach_next = reach_i;
}
/* For each state in `reach' see if there is a transition leaving with
the current input symbol to a state not yet in `reach_next', and
add the destination states to `reach_next'. */
reach_next_i = reach_next;
for (reach_i = reach; reach_i->state; reach_i++)
{
for (trans_i = reach_i->state; trans_i->state; trans_i++)
{
/* Does this transition match the input symbol? */
if (trans_i->code_min <= (tre_cint_t)prev_c &&
trans_i->code_max >= (tre_cint_t)prev_c)
{
if (trans_i->assertions
&& (CHECK_ASSERTIONS(trans_i->assertions)
|| CHECK_CHAR_CLASSES(trans_i, tnfa, eflags)))
{
DPRINT(("assertion failed\n"));
continue;
}
/* Compute the tags after this transition. */
for (i = 0; i < num_tags; i++)
tmp_tags[i] = reach_i->tags[i];
tag_i = trans_i->tags;
if (tag_i != NULL)
while (*tag_i >= 0)
{
if (*tag_i < num_tags)
tmp_tags[*tag_i] = pos;
tag_i++;
}
if (reach_pos[trans_i->state_id].pos < pos)
{
/* Found an unvisited node. */
reach_next_i->state = trans_i->state;
tmp_iptr = reach_next_i->tags;
reach_next_i->tags = tmp_tags;
tmp_tags = tmp_iptr;
reach_pos[trans_i->state_id].pos = pos;
reach_pos[trans_i->state_id].tags = &reach_next_i->tags;
if (reach_next_i->state == tnfa->final
&& (match_eo == -1
|| (num_tags > 0
&& reach_next_i->tags[0] <= match_tags[0])))
{
DPRINT((" found match %p\n", trans_i->state));
match_eo = pos;
new_match = 1;
for (i = 0; i < num_tags; i++)
match_tags[i] = reach_next_i->tags[i];
}
reach_next_i++;
}
else
{
assert(reach_pos[trans_i->state_id].pos == pos);
/* Another path has also reached this state. We choose
the winner by examining the tag values for both
paths. */
if (tre_tag_order(num_tags, tnfa->tag_directions,
tmp_tags,
*reach_pos[trans_i->state_id].tags))
{
/* The new path wins. */
tmp_iptr = *reach_pos[trans_i->state_id].tags;
*reach_pos[trans_i->state_id].tags = tmp_tags;
if (trans_i->state == tnfa->final)
{
DPRINT((" found better match\n"));
match_eo = pos;
new_match = 1;
for (i = 0; i < num_tags; i++)
match_tags[i] = tmp_tags[i];
}
tmp_tags = tmp_iptr;
}
}
}
}
}
reach_next_i->state = NULL;
}
DPRINT(("match end offset = %d\n", match_eo));
*match_end_ofs = match_eo;
ret = match_eo >= 0 ? REG_OK : REG_NOMATCH;
#ifndef TRE_USE_ALLOCA
if (buf)
xfree(buf);
#endif /* !TRE_USE_ALLOCA */
return ret;
}
/* EOF */

215
deps/tre/lib/tre-match-utils.h vendored Normal file
View file

@ -0,0 +1,215 @@
/*
tre-match-utils.h - TRE matcher helper definitions
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
#define str_source ((const tre_str_source*)string)
#ifdef TRE_WCHAR
#ifdef TRE_MULTIBYTE
/* Wide character and multibyte support. */
#define GET_NEXT_WCHAR() \
do { \
prev_c = next_c; \
if (type == STR_BYTE) \
{ \
pos++; \
if (len >= 0 && pos >= len) \
next_c = '\0'; \
else \
next_c = (unsigned char)(*str_byte++); \
} \
else if (type == STR_WIDE) \
{ \
pos++; \
if (len >= 0 && pos >= len) \
next_c = L'\0'; \
else \
next_c = *str_wide++; \
} \
else if (type == STR_MBS) \
{ \
pos += pos_add_next; \
if (str_byte == NULL) \
next_c = L'\0'; \
else \
{ \
size_t w; \
size_t max; \
if (len >= 0) \
max = len - pos; \
else \
max = 32; \
if (max <= 0) \
{ \
next_c = L'\0'; \
pos_add_next = 1; \
} \
else \
{ \
w = tre_mbrtowc(&next_c, str_byte, (size_t)max, &mbstate); \
if (w == (size_t)-1 || w == (size_t)-2) \
return REG_NOMATCH; \
if (w == 0 && len >= 0) \
{ \
pos_add_next = 1; \
next_c = 0; \
str_byte++; \
} \
else \
{ \
pos_add_next = w; \
str_byte += w; \
} \
} \
} \
} \
else if (type == STR_USER) \
{ \
pos += pos_add_next; \
str_user_end = str_source->get_next_char(&next_c, &pos_add_next, \
str_source->context); \
} \
} while(/*CONSTCOND*/(void)0,0)
#else /* !TRE_MULTIBYTE */
/* Wide character support, no multibyte support. */
#define GET_NEXT_WCHAR() \
do { \
prev_c = next_c; \
if (type == STR_BYTE) \
{ \
pos++; \
if (len >= 0 && pos >= len) \
next_c = '\0'; \
else \
next_c = (unsigned char)(*str_byte++); \
} \
else if (type == STR_WIDE) \
{ \
pos++; \
if (len >= 0 && pos >= len) \
next_c = L'\0'; \
else \
next_c = *str_wide++; \
} \
else if (type == STR_USER) \
{ \
pos += pos_add_next; \
str_user_end = str_source->get_next_char(&next_c, &pos_add_next, \
str_source->context); \
} \
} while(/*CONSTCOND*/(void)0,0)
#endif /* !TRE_MULTIBYTE */
#else /* !TRE_WCHAR */
/* No wide character or multibyte support. */
#define GET_NEXT_WCHAR() \
do { \
prev_c = next_c; \
if (type == STR_BYTE) \
{ \
pos++; \
if (len >= 0 && pos >= len) \
next_c = '\0'; \
else \
next_c = (unsigned char)(*str_byte++); \
} \
else if (type == STR_USER) \
{ \
pos += pos_add_next; \
str_user_end = str_source->get_next_char(&next_c, &pos_add_next, \
str_source->context); \
} \
} while(/*CONSTCOND*/(void)0,0)
#endif /* !TRE_WCHAR */
#define IS_WORD_CHAR(c) ((c) == L'_' || tre_isalnum(c))
#define CHECK_ASSERTIONS(assertions) \
(((assertions & ASSERT_AT_BOL) \
&& (pos > 0 || reg_notbol) \
&& (prev_c != L'\n' || !reg_newline)) \
|| ((assertions & ASSERT_AT_EOL) \
&& (next_c != L'\0' || reg_noteol) \
&& (next_c != L'\n' || !reg_newline)) \
|| ((assertions & ASSERT_AT_BOW) \
&& (IS_WORD_CHAR(prev_c) || !IS_WORD_CHAR(next_c))) \
|| ((assertions & ASSERT_AT_EOW) \
&& (!IS_WORD_CHAR(prev_c) || IS_WORD_CHAR(next_c))) \
|| ((assertions & ASSERT_AT_WB) \
&& (pos != 0 && next_c != L'\0' \
&& IS_WORD_CHAR(prev_c) == IS_WORD_CHAR(next_c))) \
|| ((assertions & ASSERT_AT_WB_NEG) \
&& (pos == 0 || next_c == L'\0' \
|| IS_WORD_CHAR(prev_c) != IS_WORD_CHAR(next_c))))
#define CHECK_CHAR_CLASSES(trans_i, tnfa, eflags) \
(((trans_i->assertions & ASSERT_CHAR_CLASS) \
&& !(tnfa->cflags & REG_ICASE) \
&& !tre_isctype((tre_cint_t)prev_c, trans_i->u.class)) \
|| ((trans_i->assertions & ASSERT_CHAR_CLASS) \
&& (tnfa->cflags & REG_ICASE) \
&& !tre_isctype(tre_tolower((tre_cint_t)prev_c),trans_i->u.class) \
&& !tre_isctype(tre_toupper((tre_cint_t)prev_c),trans_i->u.class)) \
|| ((trans_i->assertions & ASSERT_CHAR_CLASS_NEG) \
&& tre_neg_char_classes_match(trans_i->neg_classes,(tre_cint_t)prev_c,\
tnfa->cflags & REG_ICASE)))
/* Returns 1 if `t1' wins `t2', 0 otherwise. */
inline static int
tre_tag_order(int num_tags, tre_tag_direction_t *tag_directions,
int *t1, int *t2)
{
int i;
for (i = 0; i < num_tags; i++)
{
if (tag_directions[i] == TRE_TAG_MINIMIZE)
{
if (t1[i] < t2[i])
return 1;
if (t1[i] > t2[i])
return 0;
}
else
{
if (t1[i] > t2[i])
return 1;
if (t1[i] < t2[i])
return 0;
}
}
/* assert(0);*/
return 0;
}
inline static int
tre_neg_char_classes_match(tre_ctype_t *classes, tre_cint_t wc, int icase)
{
DPRINT(("neg_char_classes_test: %p, %d, %d\n", classes, wc, icase));
while (*classes != (tre_ctype_t)0)
if ((!icase && tre_isctype(wc, *classes))
|| (icase && (tre_isctype(tre_toupper(wc), *classes)
|| tre_isctype(tre_tolower(wc), *classes))))
return 1; /* Match. */
else
classes++;
return 0; /* No match. */
}

155
deps/tre/lib/tre-mem.c vendored Normal file
View file

@ -0,0 +1,155 @@
/*
tre-mem.c - TRE memory allocator
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
/*
This memory allocator is for allocating small memory blocks efficiently
in terms of memory overhead and execution speed. The allocated blocks
cannot be freed individually, only all at once. There can be multiple
allocators, though.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* HAVE_CONFIG_H */
#include <stdlib.h>
#include <string.h>
#include "tre-internal.h"
#include "tre-mem.h"
#include "xmalloc.h"
/* Returns a new memory allocator or NULL if out of memory. */
tre_mem_t
tre_mem_new_impl(int provided, void *provided_block)
{
tre_mem_t mem;
if (provided)
{
mem = provided_block;
memset(mem, 0, sizeof(*mem));
}
else
mem = xcalloc(1, sizeof(*mem));
if (mem == NULL)
return NULL;
return mem;
}
/* Frees the memory allocator and all memory allocated with it. */
void
tre_mem_destroy(tre_mem_t mem)
{
tre_list_t *tmp, *l = mem->blocks;
while (l != NULL)
{
xfree(l->data);
tmp = l->next;
xfree(l);
l = tmp;
}
xfree(mem);
}
/* Allocates a block of `size' bytes from `mem'. Returns a pointer to the
allocated block or NULL if an underlying malloc() failed. */
void *
tre_mem_alloc_impl(tre_mem_t mem, int provided, void *provided_block,
int zero, size_t size)
{
void *ptr;
if (mem->failed)
{
DPRINT(("tre_mem_alloc: oops, called after failure?!\n"));
return NULL;
}
#ifdef MALLOC_DEBUGGING
if (!provided)
{
ptr = xmalloc(1);
if (ptr == NULL)
{
DPRINT(("tre_mem_alloc: xmalloc forced failure\n"));
mem->failed = 1;
return NULL;
}
xfree(ptr);
}
#endif /* MALLOC_DEBUGGING */
if (mem->n < size)
{
/* We need more memory than is available in the current block.
Allocate a new block. */
tre_list_t *l;
if (provided)
{
DPRINT(("tre_mem_alloc: using provided block\n"));
if (provided_block == NULL)
{
DPRINT(("tre_mem_alloc: provided block was NULL\n"));
mem->failed = 1;
return NULL;
}
mem->ptr = provided_block;
mem->n = TRE_MEM_BLOCK_SIZE;
}
else
{
size_t block_size;
if (size * 8 > TRE_MEM_BLOCK_SIZE)
block_size = size * 8;
else
block_size = TRE_MEM_BLOCK_SIZE;
DPRINT(("tre_mem_alloc: allocating new %zu byte block\n",
block_size));
l = xmalloc(sizeof(*l));
if (l == NULL)
{
mem->failed = 1;
return NULL;
}
l->data = xmalloc(block_size);
if (l->data == NULL)
{
xfree(l);
mem->failed = 1;
return NULL;
}
l->next = NULL;
if (mem->current != NULL)
mem->current->next = l;
if (mem->blocks == NULL)
mem->blocks = l;
mem->current = l;
mem->ptr = l->data;
mem->n = block_size;
}
}
/* Make sure the next pointer will be aligned. */
size += ALIGN(mem->ptr + size, long);
/* Allocate from current block. */
ptr = mem->ptr;
mem->ptr += size;
mem->n -= size;
/* Set to zero if needed. */
if (zero)
memset(ptr, 0, size);
return ptr;
}
/* EOF */

66
deps/tre/lib/tre-mem.h vendored Normal file
View file

@ -0,0 +1,66 @@
/*
tre-mem.h - TRE memory allocator interface
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
#ifndef TRE_MEM_H
#define TRE_MEM_H 1
#include <stdlib.h>
#define TRE_MEM_BLOCK_SIZE 1024
typedef struct tre_list {
void *data;
struct tre_list *next;
} tre_list_t;
typedef struct tre_mem_struct {
tre_list_t *blocks;
tre_list_t *current;
char *ptr;
size_t n;
int failed;
void **provided;
} *tre_mem_t;
tre_mem_t tre_mem_new_impl(int provided, void *provided_block);
void *tre_mem_alloc_impl(tre_mem_t mem, int provided, void *provided_block,
int zero, size_t size);
/* Returns a new memory allocator or NULL if out of memory. */
#define tre_mem_new() tre_mem_new_impl(0, NULL)
/* Allocates a block of `size' bytes from `mem'. Returns a pointer to the
allocated block or NULL if an underlying malloc() failed. */
#define tre_mem_alloc(mem, size) tre_mem_alloc_impl(mem, 0, NULL, 0, size)
/* Allocates a block of `size' bytes from `mem'. Returns a pointer to the
allocated block or NULL if an underlying malloc() failed. The memory
is set to zero. */
#define tre_mem_calloc(mem, size) tre_mem_alloc_impl(mem, 0, NULL, 1, size)
#ifdef TRE_USE_ALLOCA
/* alloca() versions. Like above, but memory is allocated with alloca()
instead of malloc(). */
#define tre_mem_newa() \
tre_mem_new_impl(1, alloca(sizeof(struct tre_mem_struct)))
#define tre_mem_alloca(mem, size) \
((mem)->n >= (size) \
? tre_mem_alloc_impl((mem), 1, NULL, 0, (size)) \
: tre_mem_alloc_impl((mem), 1, alloca(TRE_MEM_BLOCK_SIZE), 0, (size)))
#endif /* TRE_USE_ALLOCA */
/* Frees the memory allocator and all memory allocated with it. */
void tre_mem_destroy(tre_mem_t mem);
#endif /* TRE_MEM_H */
/* EOF */

1758
deps/tre/lib/tre-parse.c vendored Normal file

File diff suppressed because it is too large Load diff

52
deps/tre/lib/tre-parse.h vendored Normal file
View file

@ -0,0 +1,52 @@
/*
tre-parse.c - Regexp parser definitions
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
#ifndef TRE_PARSE_H
#define TRE_PARSE_H 1
/* Parse context. */
typedef struct {
/* Memory allocator. The AST is allocated using this. */
tre_mem_t mem;
/* Stack used for keeping track of regexp syntax. */
tre_stack_t *stack;
/* The parse result. */
tre_ast_node_t *result;
/* The regexp to parse and its length. */
const tre_char_t *re;
/* The first character of the entire regexp. */
const tre_char_t *re_start;
/* The first character after the end of the regexp. */
const tre_char_t *re_end;
size_t len;
/* Current submatch ID. */
int submatch_id;
/* The highest back reference or -1 if none seen so far. */
int max_backref;
/* This flag is set if the regexp uses approximate matching. */
int have_approx;
/* This flag is set if the regexp changes cflags inline using (?...) */
int have_inline_cflags;
/* Compilation flags. */
int cflags;
/* If this flag is set the top-level submatch is not captured. */
int nofirstsub;
/* The currently set approximate matching parameters. */
int params[TRE_PARAM_LAST];
/* the MB_CUR_MAX in use */
int mb_cur_max;
} tre_parse_ctx_t;
/* Parses a wide character regexp pattern into a syntax tree. This parser
handles both syntaxes (BRE and ERE), including the TRE extensions. */
reg_errcode_t
tre_parse(tre_parse_ctx_t *ctx);
#endif /* TRE_PARSE_H */
/* EOF */

123
deps/tre/lib/tre-stack.c vendored Normal file
View file

@ -0,0 +1,123 @@
/*
tre-stack.c - Simple stack implementation
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* HAVE_CONFIG_H */
#include <stdlib.h>
#include <assert.h>
#include "tre-internal.h"
#include "tre-stack.h"
#include "xmalloc.h"
union tre_stack_item {
void *voidptr_value;
int int_value;
};
struct tre_stack_rec {
size_t size;
size_t max_size;
size_t ptr;
union tre_stack_item *stack;
};
tre_stack_t *
tre_stack_new(size_t size, size_t max_size)
{
tre_stack_t *s;
s = xmalloc(sizeof(*s));
if (s != NULL)
{
s->stack = xmalloc(sizeof(*s->stack) * size);
if (s->stack == NULL)
{
xfree(s);
return NULL;
}
s->size = size;
s->max_size = max_size;
s->ptr = 0;
}
return s;
}
void
tre_stack_destroy(tre_stack_t *s)
{
xfree(s->stack);
xfree(s);
}
size_t
tre_stack_num_items(tre_stack_t *s)
{
return s->ptr;
}
static reg_errcode_t
tre_stack_push(tre_stack_t *s, union tre_stack_item value)
{
if (s->ptr < s->size)
{
s->stack[s->ptr] = value;
s->ptr++;
}
else
{
if (s->size >= s->max_size)
{
DPRINT(("tre_stack_push: stack full\n"));
return REG_ESPACE;
}
else
{
union tre_stack_item *new_buffer;
size_t new_size;
DPRINT(("tre_stack_push: trying to realloc more space\n"));
new_size = s->size + s->size;
if (new_size > s->max_size)
new_size = s->max_size;
new_buffer = xrealloc(s->stack, sizeof(*new_buffer) * new_size);
if (new_buffer == NULL)
{
DPRINT(("tre_stack_push: realloc failed.\n"));
return REG_ESPACE;
}
DPRINT(("tre_stack_push: realloc succeeded.\n"));
assert(new_size > s->size);
s->size = new_size;
s->stack = new_buffer;
tre_stack_push(s, value);
}
}
return REG_OK;
}
#define define_pushf(typetag, type) \
declare_pushf(typetag, type) { \
union tre_stack_item item; \
item.typetag ## _value = value; \
return tre_stack_push(s, item); \
}
define_pushf(int, int)
define_pushf(voidptr, void *)
#define define_popf(typetag, type) \
declare_popf(typetag, type) { \
return s->stack[--s->ptr].typetag ## _value; \
}
define_popf(int, int)
define_popf(voidptr, void *)
/* EOF */

76
deps/tre/lib/tre-stack.h vendored Normal file
View file

@ -0,0 +1,76 @@
/*
tre-stack.h: Stack definitions
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
#ifndef TRE_STACK_H
#define TRE_STACK_H 1
#include "../local_includes/tre.h"
typedef struct tre_stack_rec tre_stack_t;
/* Creates a new stack object with initial size `size' and maximum size
`max_size'. Pushing an additional item onto a full stack will resize
the stack to double its capacity until the maximum is reached. Returns
the stack object or NULL if out of memory. */
tre_stack_t *
tre_stack_new(size_t size, size_t max_size);
/* Frees the stack object. */
void
tre_stack_destroy(tre_stack_t *s);
/* Returns the current number of items on the stack. */
size_t
tre_stack_num_items(tre_stack_t *s);
/* Each tre_stack_push_*(tre_stack_t *s, <type> value) function pushes
`value' on top of stack `s'. Returns REG_ESPACE if out of memory.
This tries to realloc() more space before failing if maximum size
has not yet been reached. Returns REG_OK if successful. */
#define declare_pushf(typetag, type) \
reg_errcode_t tre_stack_push_ ## typetag(tre_stack_t *s, type value)
declare_pushf(voidptr, void *);
declare_pushf(int, int);
/* Each tre_stack_pop_*(tre_stack_t *s) function pops the topmost
element off of stack `s' and returns it. The stack must not be
empty. */
#define declare_popf(typetag, type) \
type tre_stack_pop_ ## typetag(tre_stack_t *s)
declare_popf(voidptr, void *);
declare_popf(int, int);
/* Just to save some typing. */
#define STACK_PUSH(s, typetag, value) \
do \
{ \
status = tre_stack_push_ ## typetag(s, value); \
} \
while (/*CONSTCOND*/(void)0,0)
#define STACK_PUSHX(s, typetag, value) \
{ \
status = tre_stack_push_ ## typetag(s, value); \
if (status != REG_OK) \
break; \
}
#define STACK_PUSHR(s, typetag, value) \
{ \
reg_errcode_t _status; \
_status = tre_stack_push_ ## typetag(s, value); \
if (_status != REG_OK) \
return _status; \
}
#endif /* TRE_STACK_H */
/* EOF */

362
deps/tre/lib/xmalloc.c vendored Normal file
View file

@ -0,0 +1,362 @@
/*
xmalloc.c - Simple malloc debugging library implementation
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
/*
TODO:
- red zones
- group dumps by source location
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* HAVE_CONFIG_H */
#include <stdint.h>
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#define XMALLOC_INTERNAL 1
#include "xmalloc.h"
/*
Internal stuff.
*/
typedef struct hashTableItemRec {
void *ptr;
size_t bytes;
const char *file;
int line;
const char *func;
struct hashTableItemRec *next;
} hashTableItem;
typedef struct {
hashTableItem **table;
} hashTable;
static int xmalloc_peak;
int xmalloc_current;
static int xmalloc_peak_blocks;
int xmalloc_current_blocks;
static int xmalloc_fail_after;
#define TABLE_BITS 8
#define TABLE_MASK ((1 << TABLE_BITS) - 1)
#define TABLE_SIZE (1 << TABLE_BITS)
static hashTable *
hash_table_new(void)
{
hashTable *tbl;
tbl = malloc(sizeof(*tbl));
if (tbl != NULL)
{
tbl->table = calloc(TABLE_SIZE, sizeof(*tbl->table));
if (tbl->table == NULL)
{
free(tbl);
return NULL;
}
}
return tbl;
}
static unsigned int
hash_void_ptr(void *ptr)
{
unsigned int hash;
unsigned int i;
/* I took this hash function just off the top of my head, I have
no idea whether it is bad or very bad. */
hash = 0;
for (i = 0; i < sizeof(ptr) * 8 / TABLE_BITS; i++)
{
hash ^= (uintptr_t)ptr >> i * 8;
hash += i * 17;
hash &= TABLE_MASK;
}
return hash;
}
static void
hash_table_add(hashTable *tbl, void *ptr, size_t bytes,
const char *file, int line, const char *func)
{
unsigned int i;
hashTableItem *item, *new;
i = hash_void_ptr(ptr);
item = tbl->table[i];
if (item != NULL)
while (item->next != NULL)
item = item->next;
new = malloc(sizeof(*new));
assert(new != NULL);
new->ptr = ptr;
new->bytes = bytes;
new->file = file;
new->line = line;
new->func = func;
new->next = NULL;
if (item != NULL)
item->next = new;
else
tbl->table[i] = new;
xmalloc_current += bytes;
if (xmalloc_current > xmalloc_peak)
xmalloc_peak = xmalloc_current;
xmalloc_current_blocks++;
if (xmalloc_current_blocks > xmalloc_peak_blocks)
xmalloc_peak_blocks = xmalloc_current_blocks;
}
static void
#if defined(__GNUC__) && __GNUC__ >= 10
__attribute__((access(none, 2)))
#endif
hash_table_del(hashTable *tbl, void *ptr)
{
int i;
hashTableItem *item, *prev;
i = hash_void_ptr(ptr);
item = tbl->table[i];
if (item == NULL)
{
printf("xfree: invalid ptr %p\n", ptr);
abort();
}
prev = NULL;
while (item->ptr != ptr)
{
prev = item;
item = item->next;
}
if (item->ptr != ptr)
{
printf("xfree: invalid ptr %p\n", ptr);
abort();
}
xmalloc_current -= item->bytes;
xmalloc_current_blocks--;
if (prev != NULL)
{
prev->next = item->next;
free(item);
}
else
{
tbl->table[i] = item->next;
free(item);
}
}
static hashTable *xmalloc_table = NULL;
static void
xmalloc_init(void)
{
if (xmalloc_table == NULL)
{
xmalloc_table = hash_table_new();
xmalloc_peak = 0;
xmalloc_peak_blocks = 0;
xmalloc_current = 0;
xmalloc_current_blocks = 0;
xmalloc_fail_after = -1;
}
assert(xmalloc_table != NULL);
assert(xmalloc_table->table != NULL);
}
/*
Public API.
*/
void
xmalloc_configure(int fail_after)
{
xmalloc_init();
xmalloc_fail_after = fail_after;
}
int
xmalloc_dump_leaks(void)
{
unsigned int i;
unsigned int num_leaks = 0;
size_t leaked_bytes = 0;
hashTableItem *item;
xmalloc_init();
for (i = 0; i < TABLE_SIZE; i++)
{
item = xmalloc_table->table[i];
while (item != NULL)
{
printf("%s:%d: %s: %zu bytes at %p not freed\n",
item->file, item->line, item->func, item->bytes, item->ptr);
num_leaks++;
leaked_bytes += item->bytes;
item = item->next;
}
}
if (num_leaks == 0)
printf("No memory leaks.\n");
else
printf("%u unfreed memory chuncks, total %zu unfreed bytes.\n",
num_leaks, leaked_bytes);
printf("Peak memory consumption %d bytes (%.1f kB, %.1f MB) in %d blocks ",
xmalloc_peak, (double)xmalloc_peak / 1024,
(double)xmalloc_peak / (1024*1024), xmalloc_peak_blocks);
printf("(average ");
if (xmalloc_peak_blocks)
printf("%d", ((xmalloc_peak + xmalloc_peak_blocks / 2)
/ xmalloc_peak_blocks));
else
printf("N/A");
printf(" bytes per block).\n");
return num_leaks;
}
void *
xmalloc_impl(size_t size, const char *file, int line, const char *func)
{
void *ptr;
xmalloc_init();
assert(size > 0);
if (xmalloc_fail_after == 0)
{
xmalloc_fail_after = -2;
#if 0
printf("xmalloc: forced failure %s:%d: %s\n", file, line, func);
#endif
return NULL;
}
else if (xmalloc_fail_after == -2)
{
printf("xmalloc: called after failure from %s:%d: %s\n",
file, line, func);
assert(0);
}
else if (xmalloc_fail_after > 0)
xmalloc_fail_after--;
ptr = malloc(size);
if (ptr != NULL)
hash_table_add(xmalloc_table, ptr, (int)size, file, line, func);
return ptr;
}
void *
xcalloc_impl(size_t nmemb, size_t size, const char *file, int line,
const char *func)
{
void *ptr;
xmalloc_init();
assert(size > 0);
if (xmalloc_fail_after == 0)
{
xmalloc_fail_after = -2;
#if 0
printf("xcalloc: forced failure %s:%d: %s\n", file, line, func);
#endif
return NULL;
}
else if (xmalloc_fail_after == -2)
{
printf("xcalloc: called after failure from %s:%d: %s\n",
file, line, func);
assert(0);
}
else if (xmalloc_fail_after > 0)
xmalloc_fail_after--;
ptr = calloc(nmemb, size);
if (ptr != NULL)
hash_table_add(xmalloc_table, ptr, (int)(nmemb * size), file, line, func);
return ptr;
}
void
xfree_impl(void *ptr, const char *file, int line, const char *func)
{
/*LINTED*/(void)&file;
/*LINTED*/(void)&line;
/*LINTED*/(void)&func;
xmalloc_init();
if (ptr != NULL)
hash_table_del(xmalloc_table, ptr);
free(ptr);
}
void *
xrealloc_impl(void *ptr, size_t new_size, const char *file, int line,
const char *func)
{
void *new_ptr;
xmalloc_init();
assert(ptr != NULL);
assert(new_size > 0);
if (xmalloc_fail_after == 0)
{
xmalloc_fail_after = -2;
return NULL;
}
else if (xmalloc_fail_after == -2)
{
printf("xrealloc: called after failure from %s:%d: %s\n",
file, line, func);
assert(0);
}
else if (xmalloc_fail_after > 0)
xmalloc_fail_after--;
new_ptr = realloc(ptr, new_size);
if (new_ptr != NULL && new_ptr != ptr)
{
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wuse-after-free"
#endif
hash_table_del(xmalloc_table, ptr);
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic pop
#endif
hash_table_add(xmalloc_table, new_ptr, (int)new_size, file, line, func);
}
return new_ptr;
}
/* EOF */

77
deps/tre/lib/xmalloc.h vendored Normal file
View file

@ -0,0 +1,77 @@
/*
xmalloc.h - Simple malloc debugging library API
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
#ifndef _XMALLOC_H
#define _XMALLOC_H 1
void *xmalloc_impl(size_t size, const char *file, int line, const char *func);
void *xcalloc_impl(size_t nmemb, size_t size, const char *file, int line,
const char *func);
void xfree_impl(void *ptr, const char *file, int line, const char *func);
void *xrealloc_impl(void *ptr, size_t new_size, const char *file, int line,
const char *func);
int xmalloc_dump_leaks(void);
void xmalloc_configure(int fail_after);
#ifndef XMALLOC_INTERNAL
#ifdef MALLOC_DEBUGGING
/* Version 2.4 and later of GCC define a magical variable `__PRETTY_FUNCTION__'
which contains the name of the function currently being defined.
# define __XMALLOC_FUNCTION __PRETTY_FUNCTION__
This is broken in G++ before version 2.6.
C9x has a similar variable called __func__, but prefer the GCC one since
it demangles C++ function names. */
# ifdef __GNUC__
# if __GNUC__ > 2 || (__GNUC__ == 2 \
&& __GNUC_MINOR__ >= (defined __cplusplus ? 6 : 4))
# define __XMALLOC_FUNCTION __PRETTY_FUNCTION__
# else
# define __XMALLOC_FUNCTION ((const char *) 0)
# endif
# else
# if defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L
# define __XMALLOC_FUNCTION __func__
# else
# define __XMALLOC_FUNCTION ((const char *) 0)
# endif
# endif
#define xmalloc(size) xmalloc_impl(size, __FILE__, __LINE__, \
__XMALLOC_FUNCTION)
#define xcalloc(nmemb, size) xcalloc_impl(nmemb, size, __FILE__, __LINE__, \
__XMALLOC_FUNCTION)
#define xfree(ptr) xfree_impl(ptr, __FILE__, __LINE__, __XMALLOC_FUNCTION)
#define xrealloc(ptr, new_size) xrealloc_impl(ptr, new_size, __FILE__, \
__LINE__, __XMALLOC_FUNCTION)
#undef malloc
#undef calloc
#undef free
#undef realloc
#define malloc USE_XMALLOC_INSTEAD_OF_MALLOC
#define calloc USE_XCALLOC_INSTEAD_OF_CALLOC
#define free USE_XFREE_INSTEAD_OF_FREE
#define realloc USE_XREALLOC_INSTEAD_OF_REALLOC
#else /* !MALLOC_DEBUGGING */
#include <stdlib.h>
#define xmalloc(size) malloc(size)
#define xcalloc(nmemb, size) calloc(nmemb, size)
#define xfree(ptr) free(ptr)
#define xrealloc(ptr, new_size) realloc(ptr, new_size)
#endif /* !MALLOC_DEBUGGING */
#endif /* !XMALLOC_INTERNAL */
#endif /* _XMALLOC_H */
/* EOF */

48
deps/tre/local_includes/regex.h vendored Normal file
View file

@ -0,0 +1,48 @@
/*
regex.h - TRE legacy API
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
This header is for source level compatibility with old code using
the <tre/regex.h> header which defined the TRE API functions without
a prefix. New code should include <tre/tre.h> instead.
*/
#ifndef TRE_REXEX_H
#define TRE_REGEX_H 1
#ifdef USE_LOCAL_TRE_H
/* Use the header(s) from the TRE package that this file is part of.
(Yes, this file is in local_include too, but the explict path
means there is no way to get a system tre.h by accident.) */
#include "../local_includes/tre.h"
#else
/* Use the header(s) from an installed version of the TRE package
(so that this application matches the installed libtre),
not the one(s) in the local_includes directory. */
#include <tre/tre.h>
#endif
#ifndef TRE_USE_SYSTEM_REGEX_H
#define regcomp tre_regcomp
#define regerror tre_regerror
#define regexec tre_regexec
#define regfree tre_regfree
#endif /* TRE_USE_SYSTEM_REGEX_H */
#define regacomp tre_regacomp
#define regaexec tre_regaexec
#define regancomp tre_regancomp
#define reganexec tre_reganexec
#define regawncomp tre_regawncomp
#define regawnexec tre_regawnexec
#define regncomp tre_regncomp
#define regnexec tre_regnexec
#define regwcomp tre_regwcomp
#define regwexec tre_regwexec
#define regwncomp tre_regwncomp
#define regwnexec tre_regwnexec
#endif /* TRE_REGEX_H */

14
deps/tre/local_includes/tre-config.h vendored Normal file
View file

@ -0,0 +1,14 @@
/* Minimal TRE configuration for Redis.
*
* We use TRE as a byte-oriented regex matcher for ARGREP. Redis SDS values are
* binary-safe byte strings, so we intentionally keep the dependency build
* simple: no wide-char path, no multibyte locale handling, and no approximate
* matching engine.
*/
#define HAVE_SYS_TYPES_H 1
#define TRE_VERSION "redis-vendored"
#define TRE_VERSION_1 0
#define TRE_VERSION_2 0
#define TRE_VERSION_3 0

344
deps/tre/local_includes/tre.h vendored Normal file
View file

@ -0,0 +1,344 @@
/*
tre.h - TRE public API definitions
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
#ifndef TRE_H
#define TRE_H 1
#ifdef USE_LOCAL_TRE_H
/* Make certain to use the header(s) from the TRE package that this
file is part of by giving the full path to the header from this directory. */
#include "../local_includes/tre-config.h"
#else
/* Use the header in the same directory as this file if there is one. */
#include "tre-config.h"
#endif
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif /* HAVE_SYS_TYPES_H */
#ifdef HAVE_LIBUTF8_H
#include <libutf8.h>
#endif /* HAVE_LIBUTF8_H */
#ifdef TRE_USE_SYSTEM_REGEX_H
/* Include the system regex.h to make TRE ABI compatible with the
system regex. */
#include TRE_SYSTEM_REGEX_H_PATH
#define tre_regcomp regcomp
#define tre_regexec regexec
#define tre_regerror regerror
#define tre_regfree regfree
/* The GNU C regex has a number of refinements to the POSIX standard for the
formal parameter list of the regexec() function, and some of those fail to
compile when using LLVM. The refinements seem to be opt-out rather than
opt-in when using a recent gcc, and they produce a warning when TRE tries
to mimic the API without the refinements. The TRE code still works but
the warnings are distracting, so try to #define a flag to indicate when to
add the refinements to TRE's parameter list too. */
#ifdef __GNUC__
/* Try to test something that looks pretty REGEX specific and hope we don't
need a zillion different platform+compiler specific tests to deal with this. */
#ifdef _REGEX_NELTS
/* Define a TRE specific flag here so that:
1) there is only one place where code has to be changed if the test above is not adequate, and
2) the flag can be used in any other parts of the TRE source that might be affected by the
GNUC refinements.
Note that this flag is only defined when all of TRE_USE_SYSTEM_REGEX_H, __GNUC__, and _REGEX_NELTS are defined. */
#define TRE_USE_GNUC_REGEXEC_FPL 1
#endif
#endif
#endif /* TRE_USE_SYSTEM_REGEX_H */
#ifdef __cplusplus
extern "C" {
#endif
#ifdef TRE_USE_SYSTEM_REGEX_H
#ifndef REG_OK
#define REG_OK 0
#endif /* !REG_OK */
#ifndef HAVE_REG_ERRCODE_T
typedef int reg_errcode_t;
#endif /* !HAVE_REG_ERRCODE_T */
#if !defined(REG_NOSPEC) && !defined(REG_LITERAL)
#define REG_LITERAL 0x1000
#endif
/* Extra tre_regcomp() return error codes. */
#define REG_BADMAX REG_BADBR
/* Extra tre_regcomp() flags. */
#ifndef REG_BASIC
#define REG_BASIC 0
#endif /* !REG_BASIC */
#define REG_RIGHT_ASSOC (REG_LITERAL << 1)
#ifdef REG_UNGREEDY
/* We're going to use TRE code, so we need the TRE define (dodge problem in MacOS). */
#undef REG_UNGREEDY
#endif
#define REG_UNGREEDY (REG_RIGHT_ASSOC << 1)
#define REG_USEBYTES (REG_UNGREEDY << 1)
/* Extra tre_regexec() flags. */
#define REG_APPROX_MATCHER 0x1000
#ifdef REG_BACKTRACKING_MATCHER
/* We're going to use TRE code, so we need the TRE define (dodge problem in MacOS). */
#undef REG_BACKTRACKING_MATCHER
#endif
#define REG_BACKTRACKING_MATCHER (REG_APPROX_MATCHER << 1)
#else /* !TRE_USE_SYSTEM_REGEX_H */
/* If the we're not using system regex.h, we need to define the
structs and enums ourselves. */
typedef int regoff_t;
typedef struct {
size_t re_nsub; /* Number of parenthesized subexpressions. */
void *value; /* For internal use only. */
} regex_t;
typedef struct {
regoff_t rm_so;
regoff_t rm_eo;
} regmatch_t;
typedef enum {
REG_OK = 0, /* No error. */
/* POSIX tre_regcomp() return error codes. (In the order listed in the
standard.) */
REG_NOMATCH, /* No match. */
REG_BADPAT, /* Invalid regexp. */
REG_ECOLLATE, /* Unknown collating element. */
REG_ECTYPE, /* Unknown character class name. */
REG_EESCAPE, /* Trailing backslash. */
REG_ESUBREG, /* Invalid back reference. */
REG_EBRACK, /* "[]" imbalance */
REG_EPAREN, /* "\(\)" or "()" imbalance */
REG_EBRACE, /* "\{\}" or "{}" imbalance */
REG_BADBR, /* Invalid content of {} */
REG_ERANGE, /* Invalid use of range operator */
REG_ESPACE, /* Out of memory. */
REG_BADRPT, /* Invalid use of repetition operators. */
REG_BADMAX, /* Maximum repetition in {} too large */
} reg_errcode_t;
/* POSIX tre_regcomp() flags. */
#define REG_EXTENDED 1
#define REG_ICASE (REG_EXTENDED << 1)
#define REG_NEWLINE (REG_ICASE << 1)
#define REG_NOSUB (REG_NEWLINE << 1)
/* Extra tre_regcomp() flags. */
#define REG_BASIC 0
#define REG_LITERAL (REG_NOSUB << 1)
#define REG_RIGHT_ASSOC (REG_LITERAL << 1)
#define REG_UNGREEDY (REG_RIGHT_ASSOC << 1)
#define REG_USEBYTES (REG_UNGREEDY << 1)
/* POSIX tre_regexec() flags. */
#define REG_NOTBOL 1
#define REG_NOTEOL (REG_NOTBOL << 1)
/* Extra tre_regexec() flags. */
#define REG_APPROX_MATCHER (REG_NOTEOL << 1)
#define REG_BACKTRACKING_MATCHER (REG_APPROX_MATCHER << 1)
#endif /* !TRE_USE_SYSTEM_REGEX_H */
/* REG_NOSPEC and REG_LITERAL mean the same thing. */
#if defined(REG_LITERAL) && !defined(REG_NOSPEC)
#define REG_NOSPEC REG_LITERAL
#elif defined(REG_NOSPEC) && !defined(REG_LITERAL)
#define REG_LITERAL REG_NOSPEC
#endif /* defined(REG_NOSPEC) */
/* The maximum number of iterations in a bound expression. */
#undef RE_DUP_MAX
#define RE_DUP_MAX 255
/* The POSIX.2 regexp functions */
extern int
tre_regcomp(regex_t *preg, const char *regex, int cflags);
#ifdef TRE_USE_GNUC_REGEXEC_FPL
extern int
tre_regexec(const regex_t *preg, const char *string,
size_t nmatch, regmatch_t pmatch[_Restrict_arr_ _REGEX_NELTS (nmatch)],
int eflags);
#else
extern int
tre_regexec(const regex_t *preg, const char *string, size_t nmatch,
regmatch_t pmatch[], int eflags);
#endif
extern int
tre_regcompb(regex_t *preg, const char *regex, int cflags);
extern int
tre_regexecb(const regex_t *preg, const char *string, size_t nmatch,
regmatch_t pmatch[], int eflags);
extern size_t
tre_regerror(int errcode, const regex_t *preg, char *errbuf,
size_t errbuf_size);
extern void
tre_regfree(regex_t *preg);
#ifdef TRE_WCHAR
#ifdef HAVE_WCHAR_H
#include <wchar.h>
#endif /* HAVE_WCHAR_H */
/* Wide character versions (not in POSIX.2). */
extern int
tre_regwcomp(regex_t *preg, const wchar_t *regex, int cflags);
extern int
tre_regwexec(const regex_t *preg, const wchar_t *string,
size_t nmatch, regmatch_t pmatch[], int eflags);
#endif /* TRE_WCHAR */
/* Versions with a maximum length argument and therefore the capability to
handle null characters in the middle of the strings (not in POSIX.2). */
extern int
tre_regncomp(regex_t *preg, const char *regex, size_t len, int cflags);
extern int
tre_regnexec(const regex_t *preg, const char *string, size_t len,
size_t nmatch, regmatch_t pmatch[], int eflags);
/* regn*b versions take byte literally as 8-bit values */
extern int
tre_regncompb(regex_t *preg, const char *regex, size_t n, int cflags);
extern int
tre_regnexecb(const regex_t *preg, const char *str, size_t len,
size_t nmatch, regmatch_t pmatch[], int eflags);
#ifdef TRE_WCHAR
extern int
tre_regwncomp(regex_t *preg, const wchar_t *regex, size_t len, int cflags);
extern int
tre_regwnexec(const regex_t *preg, const wchar_t *string, size_t len,
size_t nmatch, regmatch_t pmatch[], int eflags);
#endif /* TRE_WCHAR */
#ifdef TRE_APPROX
/* Approximate matching parameter struct. */
typedef struct {
int cost_ins; /* Default cost of an inserted character. */
int cost_del; /* Default cost of a deleted character. */
int cost_subst; /* Default cost of a substituted character. */
int max_cost; /* Maximum allowed cost of a match. */
int max_ins; /* Maximum allowed number of inserts. */
int max_del; /* Maximum allowed number of deletes. */
int max_subst; /* Maximum allowed number of substitutes. */
int max_err; /* Maximum allowed number of errors total. */
} regaparams_t;
/* Approximate matching result struct. */
typedef struct {
size_t nmatch; /* Length of pmatch[] array. */
regmatch_t *pmatch; /* Submatch data. */
int cost; /* Cost of the match. */
int num_ins; /* Number of inserts in the match. */
int num_del; /* Number of deletes in the match. */
int num_subst; /* Number of substitutes in the match. */
} regamatch_t;
/* Approximate matching functions. */
extern int
tre_regaexec(const regex_t *preg, const char *string,
regamatch_t *match, regaparams_t params, int eflags);
extern int
tre_reganexec(const regex_t *preg, const char *string, size_t len,
regamatch_t *match, regaparams_t params, int eflags);
extern int
tre_regaexecb(const regex_t *preg, const char *string,
regamatch_t *match, regaparams_t params, int eflags);
#ifdef TRE_WCHAR
/* Wide character approximate matching. */
extern int
tre_regawexec(const regex_t *preg, const wchar_t *string,
regamatch_t *match, regaparams_t params, int eflags);
extern int
tre_regawnexec(const regex_t *preg, const wchar_t *string, size_t len,
regamatch_t *match, regaparams_t params, int eflags);
#endif /* TRE_WCHAR */
/* Sets the parameters to default values. */
extern void
tre_regaparams_default(regaparams_t *params);
#endif /* TRE_APPROX */
#ifdef TRE_WCHAR
typedef wchar_t tre_char_t;
#else /* !TRE_WCHAR */
typedef unsigned char tre_char_t;
#endif /* !TRE_WCHAR */
typedef struct {
int (*get_next_char)(tre_char_t *c, unsigned int *pos_add, void *context);
void (*rewind)(size_t pos, void *context);
int (*compare)(size_t pos1, size_t pos2, size_t len, void *context);
void *context;
} tre_str_source;
extern int
tre_reguexec(const regex_t *preg, const tre_str_source *string,
size_t nmatch, regmatch_t pmatch[], int eflags);
/* Returns the version string. The returned string is static. */
extern char *
tre_version(void);
/* Returns the value for a config parameter. The type to which `result'
must point to depends of the value of `query', see documentation for
more details. */
extern int
tre_config(int query, void *result);
enum {
TRE_CONFIG_APPROX,
TRE_CONFIG_WCHAR,
TRE_CONFIG_MULTIBYTE,
TRE_CONFIG_SYSTEM_ABI,
TRE_CONFIG_VERSION
};
/* Returns 1 if the compiled pattern has back references, 0 if not. */
extern int
tre_have_backrefs(const regex_t *preg);
/* Returns 1 if the compiled pattern uses approximate matching features,
0 if not. */
extern int
tre_have_approx(const regex_t *preg);
#ifdef __cplusplus
}
#endif
#endif /* TRE_H */
/* EOF */

1871
deps/tre/tests/retest.c vendored Normal file

File diff suppressed because it is too large Load diff

303
deps/tre/tests/test-literal-opt.c vendored Normal file
View file

@ -0,0 +1,303 @@
/*
test-literal-opt.c - Validate TRE literal optimization against the
generic matcher.
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* HAVE_CONFIG_H */
#include <locale.h>
#include <stdio.h>
#include <string.h>
#include "tre-internal.h"
#define PMATCH_SLOTS 4
#define RC_ANY -9999
typedef struct {
const char *name;
const char *pattern;
size_t pattern_len;
int cflags;
const char *string;
size_t string_len;
int eflags;
int expected_rc;
tre_literal_opt_mode_t expected_mode;
} litopt_case_t;
static void
init_pmatch(regmatch_t pmatch[], size_t count)
{
size_t i;
for (i = 0; i < count; i++)
{
pmatch[i].rm_so = 111;
pmatch[i].rm_eo = 222;
}
}
static int
same_pmatch(const regmatch_t a[], const regmatch_t b[], size_t count)
{
size_t i;
for (i = 0; i < count; i++)
if (a[i].rm_so != b[i].rm_so || a[i].rm_eo != b[i].rm_eo)
return 0;
return 1;
}
static int
pmatch_cleared(const regmatch_t pmatch[], size_t count)
{
size_t i;
for (i = 0; i < count; i++)
if (pmatch[i].rm_so != -1 || pmatch[i].rm_eo != -1)
return 0;
return 1;
}
static int
run_case(const litopt_case_t *tc)
{
regex_t preg;
tre_tnfa_t *tnfa;
regmatch_t fast[PMATCH_SLOTS], slow[PMATCH_SLOTS];
tre_literal_opt_mode_t saved_mode;
char errbuf[256];
int errcode, fast_rc, slow_rc;
memset(&preg, 0, sizeof(preg));
errcode = tre_regncompb(&preg, tc->pattern, tc->pattern_len, tc->cflags);
if (errcode != REG_OK)
{
tre_regerror(errcode, &preg, errbuf, sizeof(errbuf));
fprintf(stderr, "%s: compile failed: %s\n", tc->name, errbuf);
return 1;
}
tnfa = (tre_tnfa_t *)preg.value;
if (tnfa->literal_opt.mode != tc->expected_mode)
{
fprintf(stderr, "%s: optimizer mode %d, expected %d\n",
tc->name, (int)tnfa->literal_opt.mode, (int)tc->expected_mode);
tre_regfree(&preg);
return 1;
}
init_pmatch(fast, PMATCH_SLOTS);
init_pmatch(slow, PMATCH_SLOTS);
fast_rc = tre_regnexecb(&preg, tc->string, tc->string_len,
PMATCH_SLOTS, fast, tc->eflags);
saved_mode = tnfa->literal_opt.mode;
tnfa->literal_opt.mode = TRE_LITERAL_OPT_NONE;
slow_rc = tre_regnexecb(&preg, tc->string, tc->string_len,
PMATCH_SLOTS, slow, tc->eflags);
tnfa->literal_opt.mode = saved_mode;
if (fast_rc != slow_rc)
{
fprintf(stderr, "%s: fast rc %d, slow rc %d\n",
tc->name, fast_rc, slow_rc);
tre_regfree(&preg);
return 1;
}
if (tc->expected_rc != RC_ANY && fast_rc != tc->expected_rc)
{
fprintf(stderr, "%s: rc %d, expected %d\n",
tc->name, fast_rc, tc->expected_rc);
tre_regfree(&preg);
return 1;
}
if (!same_pmatch(fast, slow, PMATCH_SLOTS))
{
fprintf(stderr, "%s: fast and slow pmatch differ\n", tc->name);
tre_regfree(&preg);
return 1;
}
if ((tc->cflags & REG_NOSUB) && fast_rc == REG_OK
&& !pmatch_cleared(fast, PMATCH_SLOTS))
{
fprintf(stderr, "%s: REG_NOSUB match did not clear pmatch\n", tc->name);
tre_regfree(&preg);
return 1;
}
tre_regfree(&preg);
return 0;
}
int
main(void)
{
static const char nonascii_pattern[] = { (char)0xc0, '|', (char)0xe0 };
static const char nonascii_haystack[] = { 'x', (char)0xe0, 'y' };
static const litopt_case_t cases[] = {
{
"contains basic",
"foo|bar|baz",
sizeof("foo|bar|baz") - 1,
REG_EXTENDED | REG_NOSUB,
"xxbaryy",
sizeof("xxbaryy") - 1,
0,
REG_OK,
TRE_LITERAL_OPT_CONTAINS
},
{
"contains ignores bol/eol flags",
"foo|bar|baz",
sizeof("foo|bar|baz") - 1,
REG_EXTENDED | REG_NOSUB,
"xxbaryy",
sizeof("xxbaryy") - 1,
REG_NOTBOL | REG_NOTEOL,
REG_OK,
TRE_LITERAL_OPT_CONTAINS
},
{
"prefix basic",
"^(foo|bar|baz)",
sizeof("^(foo|bar|baz)") - 1,
REG_EXTENDED | REG_NOSUB,
"barrier",
sizeof("barrier") - 1,
0,
REG_OK,
TRE_LITERAL_OPT_PREFIX
},
{
"prefix respects REG_NOTBOL",
"^(foo|bar|baz)",
sizeof("^(foo|bar|baz)") - 1,
REG_EXTENDED | REG_NOSUB,
"barrier",
sizeof("barrier") - 1,
REG_NOTBOL,
REG_NOMATCH,
TRE_LITERAL_OPT_PREFIX
},
{
"suffix basic",
"(foo|bar|baz)$",
sizeof("(foo|bar|baz)$") - 1,
REG_EXTENDED | REG_NOSUB,
"crowbar",
sizeof("crowbar") - 1,
0,
REG_OK,
TRE_LITERAL_OPT_SUFFIX
},
{
"suffix respects REG_NOTEOL",
"(foo|bar|baz)$",
sizeof("(foo|bar|baz)$") - 1,
REG_EXTENDED | REG_NOSUB,
"crowbar",
sizeof("crowbar") - 1,
REG_NOTEOL,
REG_NOMATCH,
TRE_LITERAL_OPT_SUFFIX
},
{
"exact basic",
"^(foo|bar|baz)$",
sizeof("^(foo|bar|baz)$") - 1,
REG_EXTENDED | REG_NOSUB,
"bar",
sizeof("bar") - 1,
0,
REG_OK,
TRE_LITERAL_OPT_EXACT
},
{
"exact respects REG_NOTBOL",
"^(foo|bar|baz)$",
sizeof("^(foo|bar|baz)$") - 1,
REG_EXTENDED | REG_NOSUB,
"bar",
sizeof("bar") - 1,
REG_NOTBOL,
REG_NOMATCH,
TRE_LITERAL_OPT_EXACT
},
{
"exact respects REG_NOTEOL",
"^(foo|bar|baz)$",
sizeof("^(foo|bar|baz)$") - 1,
REG_EXTENDED | REG_NOSUB,
"bar",
sizeof("bar") - 1,
REG_NOTEOL,
REG_NOMATCH,
TRE_LITERAL_OPT_EXACT
},
{
"empty alternation disables optimization",
"(|foo|bar)",
sizeof("(|foo|bar)") - 1,
REG_EXTENDED | REG_NOSUB,
"",
0,
0,
REG_OK,
TRE_LITERAL_OPT_NONE
},
{
"inline flag disable stays generic",
"foo(?-i:zap)zot",
sizeof("foo(?-i:zap)zot") - 1,
REG_EXTENDED | REG_ICASE | REG_NOSUB,
"FoOzApZOt",
sizeof("FoOzApZOt") - 1,
0,
REG_NOMATCH,
TRE_LITERAL_OPT_NONE
},
{
"inline flag disable still matches exact scoped bytes",
"foo(?-i:zap)zot",
sizeof("foo(?-i:zap)zot") - 1,
REG_EXTENDED | REG_ICASE | REG_NOSUB,
"FoOzapZOt",
sizeof("FoOzapZOt") - 1,
0,
REG_OK,
TRE_LITERAL_OPT_NONE
},
{
"nocase non-ascii bytes stay in sync",
nonascii_pattern,
sizeof(nonascii_pattern),
REG_EXTENDED | REG_ICASE | REG_NOSUB,
nonascii_haystack,
sizeof(nonascii_haystack),
0,
RC_ANY,
TRE_LITERAL_OPT_CONTAINS
}
};
size_t i;
int failures = 0;
setlocale(LC_CTYPE, "en_US.ISO-8859-1");
for (i = 0; i < elementsof(cases); i++)
failures += run_case(&cases[i]);
return failures;
}

85
deps/tre/tests/test-malformed-regn.c vendored Normal file
View file

@ -0,0 +1,85 @@
/*
test-malformed-regn.c - Verify exact-length edge-case regexps compile or fail
cleanly both with and without a trailing NUL byte.
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "tre.h"
typedef struct {
const char *name;
const char *pattern;
int expected_err;
} malformed_case_t;
static int
run_case(const malformed_case_t *tc, int nul_terminated)
{
regex_t preg;
size_t len = strlen(tc->pattern);
size_t alloc_len = len + (nul_terminated ? 1 : 0);
char *pattern = malloc(alloc_len ? alloc_len : 1);
int errcode;
if (pattern == NULL)
{
fprintf(stderr, "%s: out of memory\n", tc->name);
return 1;
}
if (len > 0)
memcpy(pattern, tc->pattern, len);
if (nul_terminated)
pattern[len] = '\0';
memset(&preg, 0, sizeof(preg));
errcode = tre_regncompb(&preg, pattern, len, REG_EXTENDED | REG_NOSUB);
if (errcode == REG_OK)
tre_regfree(&preg);
free(pattern);
if (errcode != tc->expected_err)
{
char errbuf[128];
memset(&preg, 0, sizeof(preg));
tre_regerror(errcode, &preg, errbuf, sizeof(errbuf));
fprintf(stderr, "%s (%s): got %d (%s), expected %d\n",
tc->name, nul_terminated ? "nul" : "exact",
errcode, errbuf, tc->expected_err);
return 1;
}
return 0;
}
int
main(void)
{
static const malformed_case_t cases[] = {
{ "open paren", "(", REG_EPAREN },
{ "open bracket", "[", REG_EBRACK },
{ "unterminated comment", "(?#", REG_BADPAT },
{ "unterminated inline flags", "(?i", REG_BADPAT },
{ "short hex escape", "\\x", REG_OK },
{ "unterminated wide hex", "\\x{", REG_EBRACE },
{ "empty wide hex", "\\x{}", REG_OK }
};
size_t i;
for (i = 0; i < sizeof(cases) / sizeof(*cases); i++)
{
if (run_case(&cases[i], 0))
return 1;
if (run_case(&cases[i], 1))
return 1;
}
return 0;
}

192
deps/tre/tests/test-str-source.c vendored Normal file
View file

@ -0,0 +1,192 @@
/*
test-str-source.c - Sample program for using tre_reguexec()
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* HAVE_CONFIG_H */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* look for getopt in order to use a -o option for output. */
#if defined(HAVE_UNISTD_H)
#include <unistd.h>
#elif defined(HAVE_GETOPT_H)
#include <getopt.h>
#endif
#include "tre-internal.h"
static FILE *outf = NULL;
/* Context structure for the tre_str_source wrappers. */
typedef struct {
/* Our string. */
const char *str;
/* Current position in the string. */
size_t pos;
} str_handler_ctx;
/* The get_next_char() handler. Sets `c' to the value of the next character,
and increases `pos_add' by the number of bytes read. Returns 1 if the
string has ended, 0 if there are more characters. */
static int
str_handler_get_next(tre_char_t *c, unsigned int *pos_add, void *context)
{
str_handler_ctx *ctx = context;
unsigned char ch = ctx->str[ctx->pos];
#ifdef TRE_DEBUG
fprintf(outf, "str[%lu] = %d\n", (unsigned long)ctx->pos, ch);
#endif /* TRE_DEBUG */
*c = ch;
if (ch)
ctx->pos++;
*pos_add = 1;
return ch == '\0';
}
/* The rewind() handler. Resets the current position in the input string. */
static void
str_handler_rewind(size_t pos, void *context)
{
str_handler_ctx *ctx = context;
#ifdef TRE_DEBUG
fprintf(outf, "rewind to %lu\n", (unsigned long)pos);
#endif /* TRE_DEBUG */
ctx->pos = pos;
}
/* The compare() handler. Compares two substrings in the input and returns
0 if the substrings are equal, and a nonzero value if not. */
static int
str_handler_compare(size_t pos1, size_t pos2, size_t len, void *context)
{
str_handler_ctx *ctx = context;
#ifdef TRE_DEBUG
fprintf(outf, "comparing %lu-%lu and %lu-%lu\n",
(unsigned long)pos1, (unsigned long)pos1 + len,
(unsigned long)pos2, (unsigned long)pos2 + len);
#endif /* TRE_DEBUG */
return strncmp(ctx->str + pos1, ctx->str + pos2, len);
}
/* Creates a tre_str_source wrapper around the string `str'. Returns the
tre_str_source object or NULL if out of memory. */
static tre_str_source *
make_str_source(const char *str)
{
tre_str_source *s;
str_handler_ctx *ctx;
s = calloc(1, sizeof(*s));
if (!s)
return NULL;
ctx = malloc(sizeof(str_handler_ctx));
if (!ctx)
{
free(s);
return NULL;
}
ctx->str = str;
ctx->pos = 0;
s->context = ctx;
s->get_next_char = str_handler_get_next;
s->rewind = str_handler_rewind;
s->compare = str_handler_compare;
return s;
}
/* Frees the memory allocated for `s'. */
static void
free_str_source(tre_str_source *s)
{
free(s->context);
free(s);
}
/* Run one test with tre_reguexec. Returns 1 if the regex matches, 0 if
it doesn't, and -1 if an error occurs. */
static int
test_reguexec(const char *str, const char *regex)
{
regex_t preg;
tre_str_source *source;
regmatch_t pmatch[5];
int ret;
if ((source = make_str_source(str)) == NULL)
{
fprintf(stderr, "Out of memory\n");
ret = -1;
}
else
{
if (tre_regcomp(&preg, regex, REG_EXTENDED) != REG_OK)
{
fprintf(stderr, "Failed to compile /%s/\n", regex);
ret = -1;
}
else
{
if (tre_reguexec(&preg, source, elementsof(pmatch), pmatch, 0) == 0)
{
fprintf(outf, "Match: /%s/ matches \"%.*s\" in \"%s\"\n", regex,
(int)(pmatch[0].rm_eo - pmatch[0].rm_so),
str + pmatch[0].rm_so, str);
ret = 1;
}
else
{
fprintf(outf, "No match: /%s/ in \"%s\"\n", regex, str);
ret = 0;
}
tre_regfree(&preg);
}
free_str_source(source);
}
return ret;
}
int
main(int argc, char **argv)
{
int ret = 0;
outf = stdout;
#if defined(HAVE_UNISTD_H) || defined(HAVE_GETOPT_H)
int opt;
while ((opt = getopt(argc, argv, "o:")) != EOF)
{
switch (opt)
{
case 'o':
if ((outf = fopen(optarg, "w")) == NULL)
{
perror(optarg);
exit(1);
}
break;
default:
/* getopt() will have printed an error message already */
exit(1);
}
}
#endif
ret += test_reguexec("xfoofofoofoo", "(foo)\\1") != 1;
ret += test_reguexec("catcat", "(cat|dog)\\1") != 1;
ret += test_reguexec("catdog", "(cat|dog)\\1") != 0;
ret += test_reguexec("dogdog", "(cat|dog)\\1") != 1;
ret += test_reguexec("dogcat", "(cat|dog)\\1") != 0;
return ret;
}

View file

@ -1,5 +1,5 @@
SRC_DIR = src
MODULE_VERSION = v8.7.90
MODULE_VERSION = v8.7.91
MODULE_REPO = https://github.com/redisbloom/redisbloom
TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/redisbloom.so

View file

@ -1,5 +1,5 @@
SRC_DIR = src
MODULE_VERSION = v8.7.90
MODULE_VERSION = v8.7.91
MODULE_REPO = https://github.com/redisearch/redisearch
TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/search-community/redisearch.so
@ -7,5 +7,10 @@ TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/search-community/redisearch.so
LTO ?= 1
export LTO
# Set INLINE_LSE_ATOMICS=1 for perf improvement on common ARM CPUs (i.e. Graviton2/3/4); no effect on x86 or macOS.
# Default 0 keeps the binary runnable on pre-Armv8.1-a cores (Cortex-A72, Graviton1, RPi4) that would otherwise SIGILL at module load.
INLINE_LSE_ATOMICS ?= 0
export INLINE_LSE_ATOMICS
include ../common.mk

View file

@ -1,5 +1,5 @@
SRC_DIR = src
MODULE_VERSION = v8.7.90
MODULE_VERSION = v8.7.91
MODULE_REPO = https://github.com/redisjson/redisjson
TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/rejson.so

View file

@ -1,5 +1,5 @@
SRC_DIR = src
MODULE_VERSION = v8.7.90
MODULE_VERSION = v8.7.91
MODULE_REPO = https://github.com/redistimeseries/redistimeseries
TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/redistimeseries.so

View file

@ -0,0 +1,129 @@
from test import TestCase, generate_random_vector
import struct
import redis.exceptions
MAX_DIM = 65536
class DimensionMaxLimitVaddAtLimit(TestCase):
def getname(self):
return "[regression] VADD VALUES dim == MAX_DIM accepted"
def estimated_runtime(self):
return 0.5
def test(self):
dim = MAX_DIM
vec = generate_random_vector(dim)
result = self.redis.execute_command(
'VADD', self.test_key,
'VALUES', dim,
*[str(x) for x in vec],
f"{self.test_key}:item:maxdim")
assert result == 1, "VADD with dimension at the limit should succeed"
class DimensionMaxLimitVaddAboveLimit(TestCase):
def getname(self):
return "[regression] VADD VALUES dim > MAX_DIM rejected"
def estimated_runtime(self):
return 0.1
def test(self):
too_big_dim = MAX_DIM + 1
too_big_vec = generate_random_vector(16)
try:
self.redis.execute_command(
'VADD', self.test_key,
'VALUES', too_big_dim,
*[str(x) for x in too_big_vec],
f"{self.test_key}:item:toolarge")
assert False, "VADD with dimension above the limit should fail"
except redis.exceptions.ResponseError as e:
# parseVector returns NULL so caller uses the generic invalid spec error
assert "invalid vector specification" in str(e), (
f"Expected invalid vector specification error, got: {e}")
class DimensionMaxLimitVsimAtLimit(TestCase):
def getname(self):
return "[regression] VSIM VALUES dim == MAX_DIM accepted"
def estimated_runtime(self):
return 0.5
def test(self):
# Insert a vector at the maximum allowed dimension, then query at the same dimension.
dim = MAX_DIM
base_vec = generate_random_vector(dim)
result = self.redis.execute_command(
'VADD', self.test_key,
'VALUES', dim,
*[str(x) for x in base_vec],
f"{self.test_key}:item:1")
assert result == 1, "VADD with dimension at the limit should succeed"
query = generate_random_vector(dim)
res = self.redis.execute_command(
'VSIM', self.test_key,
'VALUES', dim,
*[str(x) for x in query],
'COUNT', 1)
assert isinstance(res, list), "VSIM with dimension at the limit should return a list"
class DimensionMaxLimitVsimAboveLimit(TestCase):
def getname(self):
return "[regression] VSIM VALUES dim > MAX_DIM rejected"
def estimated_runtime(self):
return 0.1
def test(self):
# Create a small index, then issue a VSIM with an over-limit dimension.
base_dim = 16
base_vec = generate_random_vector(base_dim)
result = self.redis.execute_command(
'VADD', self.test_key,
'VALUES', base_dim,
*[str(x) for x in base_vec],
f"{self.test_key}:item:1")
assert result == 1, "VADD with base_dim should succeed"
too_big_dim = MAX_DIM + 1
too_big_vec = generate_random_vector(16)
try:
self.redis.execute_command(
'VSIM', self.test_key,
'VALUES', too_big_dim,
*[str(x) for x in too_big_vec],
'COUNT', 1)
assert False, "VSIM with dimension above the limit should fail"
except redis.exceptions.ResponseError as e:
assert "invalid vector specification" in str(e), (
f"Expected invalid vector specification error in VSIM, got: {e}")
class DimensionMaxLimitHugeDimension(TestCase):
def getname(self):
return "[regression] VADD VALUES absurdly large dim rejected"
def estimated_runtime(self):
return 0.1
def test(self):
# Extremely large dimension close to LLONG_MAX should also be rejected safely.
huge_dim = 9223372036854775807 # LLONG_MAX from the original report
try:
self.redis.execute_command(
'VADD', self.test_key,
'VALUES', huge_dim,
'0') # Just a dummy value; parseVector should reject based on dimension alone
assert False, "VADD with absurdly large dimension should fail"
except redis.exceptions.ResponseError as e:
assert "invalid vector specification" in str(e), (
f"Expected invalid vector specification error for huge dim, got: {e}")

View file

@ -65,3 +65,33 @@ class DimensionValidation(TestCase):
assert False, "VSIM with wrong dimension should fail"
except redis.exceptions.ResponseError as e:
assert "Input dimension mismatch for projection" in str(e), f"Expected dimension mismatch error in VSIM, got: {e}"
class ReduceDimConstraintValidation(TestCase):
def getname(self):
return "[regression] VADD enforces reduce_dim <= dim"
def estimated_runtime(self):
return 0.1
def test(self):
import struct
dim = 16
reduce_dim = dim + 1 # Intentionally larger than dim
# Build a simple FP32 vector of the given dimension.
vec = [0.0] * dim
vec_bytes = struct.pack(f'{dim}f', *vec)
try:
self.redis.execute_command(
'VADD', self.test_key,
'REDUCE', reduce_dim,
'FP32', vec_bytes,
f'{self.test_key}:item:reducemismatch')
assert False, "VADD with reduce_dim > dim should fail"
except redis.exceptions.ResponseError as e:
# Same generic validation error path as other vector spec problems.
assert "invalid vector specification" in str(e), (
f"Expected invalid vector error, got: {e}")

View file

@ -134,6 +134,9 @@ static uint64_t VectorSetTypeNextId = 0;
// Default num elements returned by VSIM.
#define VSET_DEFAULT_COUNT 10
// Maximum allowed vector dimension for input vectors and sets.
#define VSET_MAX_VECTOR_DIM (1<<16)
/* ========================== Internal data structure ====================== */
/* Our abstract data type needs a dual representation similar to Redis
@ -408,6 +411,7 @@ float *parseVector(RedisModuleString **argv, int argc, int start_idx,
// Must be 4 bytes per component.
if (vec_raw_len % 4 || vec_raw_len < 4) return NULL;
*dim = vec_raw_len/4;
if (*dim > VSET_MAX_VECTOR_DIM) return NULL;
vec = RedisModule_Alloc(vec_raw_len);
if (!vec) return NULL;
@ -417,7 +421,7 @@ float *parseVector(RedisModuleString **argv, int argc, int start_idx,
if (argc < start_idx + 2) return NULL; // Need at least the dimension.
long long vdim; // Vector dimension passed by the user.
if (RedisModule_StringToLongLong(argv[start_idx+1],&vdim)
!= REDISMODULE_OK || vdim < 1) return NULL;
!= REDISMODULE_OK || vdim < 1 || vdim > VSET_MAX_VECTOR_DIM) return NULL;
// Check that all the arguments are available.
if (argc < start_idx + 2 + vdim) return NULL;
@ -441,6 +445,12 @@ float *parseVector(RedisModuleString **argv, int argc, int start_idx,
return NULL; // Unknown format.
}
// reduce_dim must be <= dim
if (reduce_dim && *reduce_dim && *reduce_dim > *dim) {
if (vec) RedisModule_Free(vec);
return NULL;
}
if (consumed_args) *consumed_args = consumed;
return vec;
}
@ -1966,6 +1976,15 @@ void *VectorSetRdbLoad(RedisModuleIO *rdb, int encver) {
uint32_t quant_type = hnsw_config & 0xff;
uint32_t hnsw_m = (hnsw_config >> 8) & 0xffff;
/* Validate dimension loaded from RDB to enforce invariants and
* avoid absurd allocations or inconsistent state. */
if (dim == 0 || dim > VSET_MAX_VECTOR_DIM) {
RedisModule_LogIOError(rdb, "warning",
"Invalid vector dimension in RDB: dim=%u (max allowed %u)",
(unsigned)dim, (unsigned)VSET_MAX_VECTOR_DIM);
return NULL;
}
/* Check that the quantization type is correct. Otherwise
* return ASAP signaling the error. */
if (quant_type != HNSW_QUANT_NONE &&
@ -1987,14 +2006,44 @@ void *VectorSetRdbLoad(RedisModuleIO *rdb, int encver) {
uint32_t input_dim = RedisModule_LoadUnsigned(rdb);
if (RedisModule_IsIOError(rdb)) goto ioerr;
uint32_t output_dim = dim;
size_t matrix_size = sizeof(float) * input_dim * output_dim;
/* Sanity check projection dimensions. */
if (input_dim == 0 || output_dim == 0 || input_dim > VSET_MAX_VECTOR_DIM || output_dim > input_dim) {
RedisModule_LogIOError(rdb, "warning",
"Invalid projection matrix dimensions: input_dim=%u, output_dim=%u (max allowed %u)",
(unsigned)input_dim, (unsigned)output_dim,
(unsigned)VSET_MAX_VECTOR_DIM);
goto ioerr;
}
/* Check for overflow in matrix_size = sizeof(float) * input_dim * output_dim. */
#if SIZE_MAX == UINT32_MAX
uint64_t product = (uint64_t) output_dim * (uint64_t) input_dim * sizeof(float);
if (product > SIZE_MAX) {
RedisModule_LogIOError(rdb, "warning",
"Projection matrix size overflow (output_dim too large): input_dim=%u, output_dim=%u",
(unsigned)input_dim, (unsigned)output_dim);
goto ioerr;
}
#endif
size_t matrix_size = sizeof(float) * (size_t)input_dim * (size_t)output_dim;
/* Load projection matrix as a binary blob and validate length. */
size_t blob_len = 0;
char *matrix_blob = RedisModule_LoadStringBuffer(rdb, &blob_len);
if (matrix_blob == NULL) goto ioerr;
if (blob_len != matrix_size) {
RedisModule_LogIOError(rdb, "warning",
"Mismatching projection matrix length: expected=%zu, got=%zu",
matrix_size, blob_len);
RedisModule_Free(matrix_blob);
goto ioerr;
}
vset->proj_matrix = RedisModule_Alloc(matrix_size);
vset->proj_input_size = input_dim;
// Load projection matrix as a binary blob
char *matrix_blob = RedisModule_LoadStringBuffer(rdb, NULL);
if (matrix_blob == NULL) goto ioerr;
memcpy(vset->proj_matrix, matrix_blob, matrix_size);
RedisModule_Free(matrix_blob);
}

View file

@ -2044,21 +2044,21 @@ latency-monitor-threshold 0
# e Evicted events (events generated when a key is evicted for maxmemory)
# n New key events (Note: not included in the 'A' class)
# t Stream commands
# a Array commands
# d Module key type events
# m Key-miss events (Note: It is not included in the 'A' class)
# o Overwritten events generated every time a key is overwritten.
# (Note: not included in the 'A' class)
# c Type-changed events generated every time a key's type changes
# (Note: not included in the 'A' class)
# r rate limit event
# S Subkeyspace events, published with __subkeyspace@<db>__:<key> prefix.
# T Subkeyevent events, published with __subkeyevent@<db>__:<event> prefix.
# I Subkeyspaceitem events, published per subkey with
# __subkeyspaceitem@<db>__:<key>\n<subkey> prefix.
# V Subkeyspaceevent events, published with
# __subkeyspaceevent@<db>__:<event>|<key> prefix.
# A Alias for g$lshzxetd, so that the "AKE" string means all the events
# except key-miss, new key, overwritten, type-changed and rate-limit.
# A Alias for g$lshzxetad, so that the "AKE" string means all the events
# except key-miss, new key, overwritten and type-changed.
#
# The "notify-keyspace-events" takes as argument a string that is composed
# of zero or multiple characters. The empty string means that notifications
@ -2187,6 +2187,37 @@ stream-node-max-entries 100
# stream-idmp-duration 100
# stream-idmp-maxsize 100
# Arrays use a sliced directory structure for O(1) access. The slice size
# controls the granularity of memory allocation - each slice covers a range
# of indices. Must be a power of two between 256 and 65536.
#
# Smaller slices (1024-2048): Better for sparse data with large gaps between
# indices, or many small arrays. Uses less memory per slice but more directory
# entries.
#
# Larger slices (8192-16384): Better for dense/contiguous data. Fewer directory
# entries but may waste memory if data is sparse within slices.
#
# Default 4096 works well for mixed workloads. If you change this setting via
# CONFIG SET, existing arrays retain their original slice size.
#
# IMPORTANT CONSIDERATION: Redis arrays, for slices with very few elements, are
# able to use a sparse representation, where the slice is not really
# materialized into an actual contiguous allocation. See the next configuration
# parameters for more information.
array-slice-size 4096
# Arrays start with sparse slices (sorted key-value pairs) for memory efficiency
# when elements are scattered. When a sparse slice exceeds array-sparse-kmax
# entries, it promotes to a dense slice (direct array). When a dense slice's
# element count drops below array-sparse-kmin and demotion would save memory,
# it demotes back to sparse. Set kmax to 0 to disable sparse encoding entirely.
# Set kmin to 0 if you never want dense slices to be demoted to sparse (useful
# when in your work load arrays reach an almost empty state to be filled again
# and so forth).
array-sparse-kmax 10
array-sparse-kmin 5
# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in
# order to help rehashing the main Redis hash table (the one mapping top-level
# keys to values). The hash table implementation Redis uses (see dict.c)

View file

@ -37,7 +37,7 @@ endif
ifneq ($(OPTIMIZATION),-O0)
OPTIMIZATION+=-fno-omit-frame-pointer
endif
DEPENDENCY_TARGETS=hiredis linenoise lua hdr_histogram fpconv xxhash
DEPENDENCY_TARGETS=hiredis linenoise lua hdr_histogram fpconv xxhash tre
NODEPS:=clean distclean
# Default settings
@ -384,7 +384,7 @@ endif
REDIS_SERVER_NAME=redis-server$(PROG_SUFFIX)
REDIS_SENTINEL_NAME=redis-sentinel$(PROG_SUFFIX)
REDIS_SERVER_OBJ=threads_mngr.o memory_prefetch.o adlist.o quicklist.o ae.o anet.o dict.o ebuckets.o eventnotifier.o iothread.o mstr.o entry.o kvstore.o fwtree.o estore.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o cluster_asm.o cluster_legacy.o cluster_slot_stats.o crc16.o endianconv.o slowlog.o eval.o bio.o rio.o rand.o memtest.o syscheck.o crcspeed.o crccombine.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o lolwut6.o lolwut8.o acl.o tracking.o socket.o tls.o sha256.o timeout.o setcpuaffinity.o monotonic.o mt19937-64.o resp_parser.o call_reply.o script_lua.o script.o functions.o function_lua.o commands.o strl.o connection.o unix.o logreqres.o keymeta.o chk.o hotkeys.o gcra.o vector.o fast_float_strtod.o
REDIS_SERVER_OBJ=threads_mngr.o memory_prefetch.o adlist.o quicklist.o ae.o anet.o dict.o ebuckets.o eventnotifier.o iothread.o mstr.o entry.o kvstore.o fwtree.o estore.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o t_array.o sparsearray.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o cluster_asm.o cluster_legacy.o cluster_slot_stats.o crc16.o endianconv.o slowlog.o eval.o bio.o rio.o rand.o memtest.o syscheck.o crcspeed.o crccombine.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o lolwut6.o lolwut8.o acl.o tracking.o socket.o tls.o sha256.o timeout.o setcpuaffinity.o monotonic.o mt19937-64.o resp_parser.o call_reply.o script_lua.o script.o functions.o function_lua.o commands.o strl.o connection.o unix.o logreqres.o keymeta.o chk.o hotkeys.o gcra.o vector.o fast_float_strtod.o
REDIS_CLI_NAME=redis-cli$(PROG_SUFFIX)
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o ae.o redisassert.o crcspeed.o crccombine.o crc64.o siphash.o crc16.o monotonic.o cli_common.o mt19937-64.o strl.o cli_commands.o
REDIS_BENCHMARK_NAME=redis-benchmark$(PROG_SUFFIX)
@ -444,7 +444,7 @@ endif
# redis-server
$(REDIS_SERVER_NAME): $(REDIS_SERVER_OBJ) $(REDIS_VEC_SETS_OBJ)
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/lua/src/liblua.a ../deps/hdr_histogram/libhdrhistogram.a ../deps/fpconv/libfpconv.a ../deps/xxhash/libxxhash.a $(FINAL_LIBS)
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/lua/src/liblua.a ../deps/hdr_histogram/libhdrhistogram.a ../deps/fpconv/libfpconv.a ../deps/xxhash/libxxhash.a ../deps/tre/libtre.a $(FINAL_LIBS)
# redis-sentinel
$(REDIS_SENTINEL_NAME): $(REDIS_SERVER_NAME)

View file

@ -57,6 +57,7 @@ struct ACLCategoryItem {
{"list", ACL_CATEGORY_LIST},
{"hash", ACL_CATEGORY_HASH},
{"string", ACL_CATEGORY_STRING},
{"array", ACL_CATEGORY_ARRAY},
{"bitmap", ACL_CATEGORY_BITMAP},
{"hyperloglog", ACL_CATEGORY_HYPERLOGLOG},
{"geo", ACL_CATEGORY_GEO},
@ -70,7 +71,9 @@ struct ACLCategoryItem {
{"connection", ACL_CATEGORY_CONNECTION},
{"transaction", ACL_CATEGORY_TRANSACTION},
{"scripting", ACL_CATEGORY_SCRIPTING},
#ifdef ENABLE_GCRA
{"ratelimit", ACL_CATEGORY_RATE_LIMIT},
#endif
{NULL,0} /* Terminator. */
};

116
src/aof.c
View file

@ -2467,6 +2467,7 @@ int rewriteStreamObject(rio *r, robj *key, robj *o) {
return 1;
}
#ifdef ENABLE_GCRA
int rewriteGCRAObject(rio *r, robj *key, robj *o) {
long long val;
getLongLongFromGCRAObject(o, &val);
@ -2478,6 +2479,7 @@ int rewriteGCRAObject(rio *r, robj *key, robj *o) {
if (rioWriteBulkLongLong(r,val) == 0) return 0;
return 1;
}
#endif
/* Call the module type callback in order to rewrite a data type
* that is exported by a module and is not handled by Redis itself.
@ -2515,6 +2517,116 @@ werr:
return 0;
}
/* Write unsigned 64-bit integer as bulk string.
* Unlike rioWriteBulkLongLong which uses signed representation,
* this correctly handles values >= 2^63 (e.g., array indices). */
static int rioWriteBulkUnsignedLongLong(rio *r, uint64_t value) {
char buf[24];
int len = ull2string(buf, sizeof(buf), value);
return rioWriteBulkString(r, buf, len);
}
/* Helper to emit a single array element for AOF rewrite.
* Returns 0 on error, 1 on success. Updates count and items. */
static int aofEmitArrayElement(rio *r, robj *key, uint64_t idx, void *v,
long long *count, long long *items) {
if (*count == 0) {
int cmd_items = (*items > AOF_REWRITE_ITEMS_PER_CMD/2) ?
AOF_REWRITE_ITEMS_PER_CMD/2 : *items; /* pairs of idx+val */
if (!rioWriteBulkCount(r,'*',2+cmd_items*2) ||
!rioWriteBulkString(r,"ARMSET",6) ||
!rioWriteBulkObject(r,key))
{
return 0;
}
}
/* Write index (unsigned to handle indices >= 2^63) */
if (!rioWriteBulkUnsignedLongLong(r, idx)) return 0;
/* Write value - inline types use scratch space, arString aliases directly. */
char buf[AR_INLINE_BUFSIZE];
size_t len;
const char *data = arDecode(v, buf, sizeof(buf), &len);
if (!rioWriteBulkString(r, data, len)) return 0;
if (++(*count) == AOF_REWRITE_ITEMS_PER_CMD/2) *count = 0;
(*items)--;
return 1;
}
/* Helper to emit all elements from a slice for AOF rewrite. */
static int aofEmitSliceElements(rio *r, robj *key, arSlice *s, uint64_t slice_id,
uint32_t slice_size, long long *count, long long *items) {
if (s->encoding == AR_SLICE_DENSE) {
for (uint32_t i = 0; i < s->layout.dense.winsize; i++) {
void *v = s->layout.dense.items[i];
if (arIsEmpty(v)) continue;
uint64_t idx = arMakeIdx(slice_id, s->layout.dense.offset + i, slice_size);
if (!aofEmitArrayElement(r, key, idx, v, count, items)) return 0;
}
} else {
/* Sparse slice */
uint16_t *offsets = s->layout.sparse.offsets;
void **values = s->layout.sparse.values;
for (uint32_t i = 0; i < s->count; i++) {
uint64_t idx = arMakeIdx(slice_id, offsets[i], slice_size);
if (!aofEmitArrayElement(r, key, idx, values[i], count, items)) return 0;
}
}
return 1;
}
/* Emit the commands needed to rebuild an array object.
* The function returns 0 on error, 1 on success. */
int rewriteArrayObject(rio *r, robj *key, robj *o) {
redisArray *ar = o->ptr;
long long count = 0, items = ar->count;
if (items == 0) return 1;
/* Iterate through all slices, handling both flat directory mode and
* superdir mode. This mirrors the iteration logic in rdb.c. */
if (ar->superdir) {
/* Superdir mode: iterate through blocks */
for (uint32_t bi = 0; bi < ar->sdir_len; bi++) {
arSDirEntry *e = ar->superdir + bi;
uint64_t block_base = e->block_id * AR_SUPER_BLOCK_SLOTS;
for (uint32_t si = 0; si < AR_SUPER_BLOCK_SLOTS; si++) {
arSlice *s = e->slots[si];
if (!s) continue;
uint64_t slice_id = block_base + si;
if (!aofEmitSliceElements(r, key, s, slice_id, ar->slice_size,
&count, &items)) return 0;
}
}
} else {
/* Flat directory mode */
for (uint64_t slice_id = 0; slice_id <= ar->dir_highest_used && slice_id < ar->dir_alloc; slice_id++) {
arSlice *s = ar->dir[slice_id];
if (!s) continue;
if (!aofEmitSliceElements(r, key, s, slice_id, ar->slice_size,
&count, &items)) return 0;
}
}
/* If insert_idx is set, emit ARSEEK command to restore it.
* When insert_idx == UINT64_MAX-1, we emit ARSEEK UINT64_MAX which
* correctly sets insert_idx back to UINT64_MAX-1 (terminal state). */
if (ar->insert_idx != AR_INSERT_IDX_NONE) {
/* ARSEEK key insert_idx+1 (ARSEEK sets position for next insert) */
if (!rioWriteBulkCount(r,'*',3) ||
!rioWriteBulkString(r,"ARSEEK",6) ||
!rioWriteBulkObject(r,key) ||
!rioWriteBulkUnsignedLongLong(r, ar->insert_idx + 1))
{
return 0;
}
}
return 1;
}
int rewriteObject(rio *r, robj *key, robj *o, int dbid, long long expiretime) {
/* Save the key and associated value */
if (o->type == OBJ_STRING) {
@ -2534,8 +2646,12 @@ int rewriteObject(rio *r, robj *key, robj *o, int dbid, long long expiretime) {
if (rewriteHashObject(r,key,o) == 0) return C_ERR;
} else if (o->type == OBJ_STREAM) {
if (rewriteStreamObject(r,key,o) == 0) return C_ERR;
#ifdef ENABLE_GCRA
} else if (o->type == OBJ_GCRA) {
if (rewriteGCRAObject(r,key,o) == 0) return C_ERR;
#endif
} else if (o->type == OBJ_ARRAY) {
if (rewriteArrayObject(r,key,o) == 0) return C_ERR;
} else if (o->type == OBJ_MODULE) {
if (rewriteModuleObject(r,key,o,dbid) == 0) return C_ERR;
} else {

View file

@ -699,7 +699,13 @@ static void unblockClientOnKey(client *c, robj *key) {
client *old_client = server.current_client;
server.current_client = c;
enterExecutionUnit(1, 0);
processCommandAndResetClient(c);
if (processCommandAndResetClient(c) == C_ERR) {
/* Client was freed during command processing, exit immediately */
exitExecutionUnit();
server.current_client = old_client;
return;
}
if (!(c->flags & CLIENT_BLOCKED)) {
if (c->flags & CLIENT_MODULE) {
moduleCallCommandUnblockedHandler(c);

View file

@ -804,7 +804,12 @@ int verifyClusterNodeId(const char *name, int length) {
}
int isValidAuxChar(int c) {
return isalnum(c) || (strchr("!#$%&()*+:;<>?@[]^{|}~", c) == NULL);
/* Reject control characters (0x00-0x1F and 0x7F). */
if (iscntrl(c)) {
return 0;
}
/* Reject forbidden characters including nodes.conf delimiters and special parsing characters */
return isalnum(c) || (strchr("!#$%&()*+:;<>?@[]^{|}~,= \"'\\", c) == NULL);
}
int isValidAuxString(char *s, unsigned int length) {

View file

@ -24,13 +24,545 @@ const char *COMMAND_GROUP_STR[] = {
"geo",
"stream",
"bitmap",
"array",
"module",
#ifdef ENABLE_GCRA
"rate_limit"
#endif
};
const char *commandGroupStr(int index) {
return COMMAND_GROUP_STR[index];
}
/********** ARCOUNT ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* ARCOUNT history */
#define ARCOUNT_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* ARCOUNT tips */
#define ARCOUNT_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* ARCOUNT key specs */
keySpec ARCOUNT_Keyspecs[1] = {
{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* ARCOUNT argument table */
struct COMMAND_ARG ARCOUNT_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/********** ARDEL ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* ARDEL history */
#define ARDEL_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* ARDEL tips */
#define ARDEL_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* ARDEL key specs */
keySpec ARDEL_Keyspecs[1] = {
{NULL,CMD_KEY_RW|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* ARDEL argument table */
struct COMMAND_ARG ARDEL_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("index",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
};
/********** ARDELRANGE ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* ARDELRANGE history */
#define ARDELRANGE_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* ARDELRANGE tips */
#define ARDELRANGE_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* ARDELRANGE key specs */
keySpec ARDELRANGE_Keyspecs[1] = {
{NULL,CMD_KEY_RW|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* ARDELRANGE range argument table */
struct COMMAND_ARG ARDELRANGE_range_Subargs[] = {
{MAKE_ARG("start",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("end",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/* ARDELRANGE argument table */
struct COMMAND_ARG ARDELRANGE_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("range",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,2,NULL),.subargs=ARDELRANGE_range_Subargs},
};
/********** ARGET ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* ARGET history */
#define ARGET_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* ARGET tips */
#define ARGET_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* ARGET key specs */
keySpec ARGET_Keyspecs[1] = {
{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* ARGET argument table */
struct COMMAND_ARG ARGET_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("index",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/********** ARGETRANGE ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* ARGETRANGE history */
#define ARGETRANGE_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* ARGETRANGE tips */
#define ARGETRANGE_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* ARGETRANGE key specs */
keySpec ARGETRANGE_Keyspecs[1] = {
{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* ARGETRANGE argument table */
struct COMMAND_ARG ARGETRANGE_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("start",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("end",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/********** ARGREP ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* ARGREP history */
#define ARGREP_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* ARGREP tips */
#define ARGREP_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* ARGREP key specs */
keySpec ARGREP_Keyspecs[1] = {
{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* ARGREP predicate exact argument table */
struct COMMAND_ARG ARGREP_predicate_exact_Subargs[] = {
{MAKE_ARG("exact",ARG_TYPE_PURE_TOKEN,-1,"EXACT",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("string",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/* ARGREP predicate match argument table */
struct COMMAND_ARG ARGREP_predicate_match_Subargs[] = {
{MAKE_ARG("match",ARG_TYPE_PURE_TOKEN,-1,"MATCH",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("string",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/* ARGREP predicate glob argument table */
struct COMMAND_ARG ARGREP_predicate_glob_Subargs[] = {
{MAKE_ARG("glob",ARG_TYPE_PURE_TOKEN,-1,"GLOB",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("pattern",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/* ARGREP predicate re argument table */
struct COMMAND_ARG ARGREP_predicate_re_Subargs[] = {
{MAKE_ARG("re",ARG_TYPE_PURE_TOKEN,-1,"RE",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("pattern",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/* ARGREP predicate argument table */
struct COMMAND_ARG ARGREP_predicate_Subargs[] = {
{MAKE_ARG("exact",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=ARGREP_predicate_exact_Subargs},
{MAKE_ARG("match",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=ARGREP_predicate_match_Subargs},
{MAKE_ARG("glob",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=ARGREP_predicate_glob_Subargs},
{MAKE_ARG("re",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=ARGREP_predicate_re_Subargs},
};
/* ARGREP options argument table */
struct COMMAND_ARG ARGREP_options_Subargs[] = {
{MAKE_ARG("and",ARG_TYPE_PURE_TOKEN,-1,"AND",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("or",ARG_TYPE_PURE_TOKEN,-1,"OR",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("limit",ARG_TYPE_INTEGER,-1,"LIMIT",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("withvalues",ARG_TYPE_PURE_TOKEN,-1,"WITHVALUES",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("nocase",ARG_TYPE_PURE_TOKEN,-1,"NOCASE",NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/* ARGREP argument table */
struct COMMAND_ARG ARGREP_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("start",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("end",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("predicate",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,4,NULL),.subargs=ARGREP_predicate_Subargs},
{MAKE_ARG("options",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,5,NULL),.subargs=ARGREP_options_Subargs},
};
/********** ARINFO ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* ARINFO history */
#define ARINFO_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* ARINFO tips */
#define ARINFO_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* ARINFO key specs */
keySpec ARINFO_Keyspecs[1] = {
{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* ARINFO argument table */
struct COMMAND_ARG ARINFO_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("full",ARG_TYPE_PURE_TOKEN,-1,"FULL",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)},
};
/********** ARINSERT ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* ARINSERT history */
#define ARINSERT_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* ARINSERT tips */
#define ARINSERT_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* ARINSERT key specs */
keySpec ARINSERT_Keyspecs[1] = {
{NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* ARINSERT argument table */
struct COMMAND_ARG ARINSERT_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("value",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
};
/********** ARLASTITEMS ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* ARLASTITEMS history */
#define ARLASTITEMS_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* ARLASTITEMS tips */
#define ARLASTITEMS_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* ARLASTITEMS key specs */
keySpec ARLASTITEMS_Keyspecs[1] = {
{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* ARLASTITEMS argument table */
struct COMMAND_ARG ARLASTITEMS_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("count",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("rev",ARG_TYPE_PURE_TOKEN,-1,"REV",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)},
};
/********** ARLEN ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* ARLEN history */
#define ARLEN_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* ARLEN tips */
#define ARLEN_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* ARLEN key specs */
keySpec ARLEN_Keyspecs[1] = {
{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* ARLEN argument table */
struct COMMAND_ARG ARLEN_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/********** ARMGET ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* ARMGET history */
#define ARMGET_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* ARMGET tips */
#define ARMGET_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* ARMGET key specs */
keySpec ARMGET_Keyspecs[1] = {
{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* ARMGET argument table */
struct COMMAND_ARG ARMGET_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("index",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
};
/********** ARMSET ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* ARMSET history */
#define ARMSET_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* ARMSET tips */
#define ARMSET_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* ARMSET key specs */
keySpec ARMSET_Keyspecs[1] = {
{NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* ARMSET data argument table */
struct COMMAND_ARG ARMSET_data_Subargs[] = {
{MAKE_ARG("index",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("value",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/* ARMSET argument table */
struct COMMAND_ARG ARMSET_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("data",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,2,NULL),.subargs=ARMSET_data_Subargs},
};
/********** ARNEXT ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* ARNEXT history */
#define ARNEXT_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* ARNEXT tips */
#define ARNEXT_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* ARNEXT key specs */
keySpec ARNEXT_Keyspecs[1] = {
{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* ARNEXT argument table */
struct COMMAND_ARG ARNEXT_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/********** AROP ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* AROP history */
#define AROP_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* AROP tips */
#define AROP_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* AROP key specs */
keySpec AROP_Keyspecs[1] = {
{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* AROP operation match argument table */
struct COMMAND_ARG AROP_operation_match_Subargs[] = {
{MAKE_ARG("match",ARG_TYPE_PURE_TOKEN,-1,"MATCH",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("value",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/* AROP operation argument table */
struct COMMAND_ARG AROP_operation_Subargs[] = {
{MAKE_ARG("sum",ARG_TYPE_PURE_TOKEN,-1,"SUM",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("min",ARG_TYPE_PURE_TOKEN,-1,"MIN",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("max",ARG_TYPE_PURE_TOKEN,-1,"MAX",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("and",ARG_TYPE_PURE_TOKEN,-1,"AND",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("or",ARG_TYPE_PURE_TOKEN,-1,"OR",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("xor",ARG_TYPE_PURE_TOKEN,-1,"XOR",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("match",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=AROP_operation_match_Subargs},
{MAKE_ARG("used",ARG_TYPE_PURE_TOKEN,-1,"USED",NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/* AROP argument table */
struct COMMAND_ARG AROP_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("start",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("end",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("operation",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,8,NULL),.subargs=AROP_operation_Subargs},
};
/********** ARRING ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* ARRING history */
#define ARRING_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* ARRING tips */
#define ARRING_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* ARRING key specs */
keySpec ARRING_Keyspecs[1] = {
{NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* ARRING argument table */
struct COMMAND_ARG ARRING_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("size",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("value",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
};
/********** ARSCAN ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* ARSCAN history */
#define ARSCAN_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* ARSCAN tips */
#define ARSCAN_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* ARSCAN key specs */
keySpec ARSCAN_Keyspecs[1] = {
{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* ARSCAN argument table */
struct COMMAND_ARG ARSCAN_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("start",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("end",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("limit",ARG_TYPE_INTEGER,-1,"LIMIT",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)},
};
/********** ARSEEK ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* ARSEEK history */
#define ARSEEK_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* ARSEEK tips */
#define ARSEEK_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* ARSEEK key specs */
keySpec ARSEEK_Keyspecs[1] = {
{NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* ARSEEK argument table */
struct COMMAND_ARG ARSEEK_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("index",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/********** ARSET ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* ARSET history */
#define ARSET_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* ARSET tips */
#define ARSET_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* ARSET key specs */
keySpec ARSET_Keyspecs[1] = {
{NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* ARSET argument table */
struct COMMAND_ARG ARSET_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("index",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("value",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
};
/********** BITCOUNT ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
@ -5380,59 +5912,6 @@ struct COMMAND_ARG UNSUBSCRIBE_Args[] = {
{MAKE_ARG("channel",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)},
};
/********** GCRA ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* GCRA history */
#define GCRA_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* GCRA tips */
#define GCRA_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* GCRA key specs */
keySpec GCRA_Keyspecs[1] = {
{NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* GCRA argument table */
struct COMMAND_ARG GCRA_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("max-burst",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("tokens-per-period",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("period",ARG_TYPE_DOUBLE,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("count",ARG_TYPE_INTEGER,-1,"TOKENS",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)},
};
/********** GCRASETVALUE ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* GCRASETVALUE history */
#define GCRASETVALUE_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* GCRASETVALUE tips */
#define GCRASETVALUE_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* GCRASETVALUE key specs */
keySpec GCRASETVALUE_Keyspecs[1] = {
{NULL,CMD_KEY_OW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* GCRASETVALUE argument table */
struct COMMAND_ARG GCRASETVALUE_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("tat",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/********** EVAL ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
@ -11876,6 +12355,25 @@ struct COMMAND_ARG WATCH_Args[] = {
/* Main command table */
struct COMMAND_STRUCT redisCommandTable[] = {
/* array */
{MAKE_CMD("arcount","Returns the number of non-empty elements in an array.","O(1)","8.8.0",CMD_DOC_NONE,NULL,NULL,"array",COMMAND_GROUP_ARRAY,ARCOUNT_History,0,ARCOUNT_Tips,0,arcountCommand,2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_ARRAY,ARCOUNT_Keyspecs,1,NULL,1),.args=ARCOUNT_Args},
{MAKE_CMD("ardel","Deletes elements at the specified indices in an array.","O(N) where N is the number of indices to delete","8.8.0",CMD_DOC_NONE,NULL,NULL,"array",COMMAND_GROUP_ARRAY,ARDEL_History,0,ARDEL_Tips,0,ardelCommand,-3,CMD_WRITE|CMD_FAST,ACL_CATEGORY_ARRAY,ARDEL_Keyspecs,1,NULL,2),.args=ARDEL_Args},
{MAKE_CMD("ardelrange","Deletes elements in one or more ranges.","Proportional to the number of existing elements / slices touched, not to the numeric span of the requested ranges","8.8.0",CMD_DOC_NONE,NULL,NULL,"array",COMMAND_GROUP_ARRAY,ARDELRANGE_History,0,ARDELRANGE_Tips,0,ardelrangeCommand,-4,CMD_WRITE,ACL_CATEGORY_ARRAY,ARDELRANGE_Keyspecs,1,NULL,2),.args=ARDELRANGE_Args},
{MAKE_CMD("arget","Gets the value at an index in an array.","O(1)","8.8.0",CMD_DOC_NONE,NULL,NULL,"array",COMMAND_GROUP_ARRAY,ARGET_History,0,ARGET_Tips,0,argetCommand,3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_ARRAY,ARGET_Keyspecs,1,NULL,2),.args=ARGET_Args},
{MAKE_CMD("argetrange","Gets values in a range of indices.","O(N) where N is the range length","8.8.0",CMD_DOC_NONE,NULL,NULL,"array",COMMAND_GROUP_ARRAY,ARGETRANGE_History,0,ARGETRANGE_Tips,0,argetrangeCommand,4,CMD_READONLY,ACL_CATEGORY_ARRAY,ARGETRANGE_Keyspecs,1,NULL,3),.args=ARGETRANGE_Args},
{MAKE_CMD("argrep","Searches array elements in a range using textual predicates.","O(P * C) where P is the number of visited positions in touched slices and C is the cost of evaluating the predicates on one existing element.","8.8.0",CMD_DOC_NONE,NULL,NULL,"array",COMMAND_GROUP_ARRAY,ARGREP_History,0,ARGREP_Tips,0,argrepCommand,-6,CMD_READONLY,ACL_CATEGORY_ARRAY,ARGREP_Keyspecs,1,NULL,5),.args=ARGREP_Args},
{MAKE_CMD("arinfo","Returns metadata about an array.","O(1), or O(N) with FULL option where N is the number of slices.","8.8.0",CMD_DOC_NONE,NULL,NULL,"array",COMMAND_GROUP_ARRAY,ARINFO_History,0,ARINFO_Tips,0,arinfoCommand,-2,CMD_READONLY,ACL_CATEGORY_ARRAY,ARINFO_Keyspecs,1,NULL,2),.args=ARINFO_Args},
{MAKE_CMD("arinsert","Inserts one or more values at consecutive indices.","O(N) where N is the number of values","8.8.0",CMD_DOC_NONE,NULL,NULL,"array",COMMAND_GROUP_ARRAY,ARINSERT_History,0,ARINSERT_Tips,0,arinsertCommand,-3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_ARRAY,ARINSERT_Keyspecs,1,NULL,2),.args=ARINSERT_Args},
{MAKE_CMD("arlastitems","Returns the most recently inserted elements.","O(N) where N is the count","8.8.0",CMD_DOC_NONE,NULL,NULL,"array",COMMAND_GROUP_ARRAY,ARLASTITEMS_History,0,ARLASTITEMS_Tips,0,arlastitemsCommand,-3,CMD_READONLY,ACL_CATEGORY_ARRAY,ARLASTITEMS_Keyspecs,1,NULL,3),.args=ARLASTITEMS_Args},
{MAKE_CMD("arlen","Returns the length of an array (max index + 1).","O(1)","8.8.0",CMD_DOC_NONE,NULL,NULL,"array",COMMAND_GROUP_ARRAY,ARLEN_History,0,ARLEN_Tips,0,arlenCommand,2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_ARRAY,ARLEN_Keyspecs,1,NULL,1),.args=ARLEN_Args},
{MAKE_CMD("armget","Gets values at multiple indices in an array.","O(N) where N is the number of indices","8.8.0",CMD_DOC_NONE,NULL,NULL,"array",COMMAND_GROUP_ARRAY,ARMGET_History,0,ARMGET_Tips,0,armgetCommand,-3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_ARRAY,ARMGET_Keyspecs,1,NULL,2),.args=ARMGET_Args},
{MAKE_CMD("armset","Sets multiple index-value pairs in an array.","O(N) where N is the number of pairs","8.8.0",CMD_DOC_NONE,NULL,NULL,"array",COMMAND_GROUP_ARRAY,ARMSET_History,0,ARMSET_Tips,0,armsetCommand,-4,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_ARRAY,ARMSET_Keyspecs,1,NULL,2),.args=ARMSET_Args},
{MAKE_CMD("arnext","Returns the next index ARINSERT would use.","O(1)","8.8.0",CMD_DOC_NONE,NULL,NULL,"array",COMMAND_GROUP_ARRAY,ARNEXT_History,0,ARNEXT_Tips,0,arnextCommand,2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_ARRAY,ARNEXT_Keyspecs,1,NULL,1),.args=ARNEXT_Args},
{MAKE_CMD("arop","Performs aggregate operations on array elements in a range.","O(P) where P is visited positions in touched slices (dense scanned slots + sparse entries), with worst-case O(|end-start|+1) and typical case close to O(N), where N is the number of existing elements in range.","8.8.0",CMD_DOC_NONE,NULL,NULL,"array",COMMAND_GROUP_ARRAY,AROP_History,0,AROP_Tips,0,aropCommand,-5,CMD_READONLY,ACL_CATEGORY_ARRAY,AROP_Keyspecs,1,NULL,4),.args=AROP_Args},
{MAKE_CMD("arring","Inserts values into a ring buffer of specified size, wrapping and truncating as needed.","O(M) normally, O(N+M) on ring resize, where N is the maximum of the old and new ring size and M is the number of inserted values","8.8.0",CMD_DOC_NONE,NULL,NULL,"array",COMMAND_GROUP_ARRAY,ARRING_History,0,ARRING_Tips,0,arringCommand,-4,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_ARRAY,ARRING_Keyspecs,1,NULL,3),.args=ARRING_Args},
{MAKE_CMD("arscan","Iterates existing elements in a range, returning index-value pairs.","O(P) where P is visited positions in touched slices (dense scanned slots + sparse entries), with worst-case O(|end-start|+1) and typical case close to O(N), where N is the number of existing elements in range.","8.8.0",CMD_DOC_NONE,NULL,NULL,"array",COMMAND_GROUP_ARRAY,ARSCAN_History,0,ARSCAN_Tips,0,arscanCommand,-4,CMD_READONLY,ACL_CATEGORY_ARRAY,ARSCAN_Keyspecs,1,NULL,4),.args=ARSCAN_Args},
{MAKE_CMD("arseek","Sets the ARINSERT / ARRING cursor to a specific index.","O(1)","8.8.0",CMD_DOC_NONE,NULL,NULL,"array",COMMAND_GROUP_ARRAY,ARSEEK_History,0,ARSEEK_Tips,0,arseekCommand,3,CMD_WRITE|CMD_FAST,ACL_CATEGORY_ARRAY,ARSEEK_Keyspecs,1,NULL,2),.args=ARSEEK_Args},
{MAKE_CMD("arset","Sets one or more contiguous values starting at an index in an array.","O(N) where N is the number of values","8.8.0",CMD_DOC_NONE,NULL,NULL,"array",COMMAND_GROUP_ARRAY,ARSET_History,0,ARSET_Tips,0,arsetCommand,-4,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_ARRAY,ARSET_Keyspecs,1,NULL,3),.args=ARSET_Args},
/* bitmap */
{MAKE_CMD("bitcount","Counts the number of set bits (population counting) in a string.","O(N)","2.6.0",CMD_DOC_NONE,NULL,NULL,"bitmap",COMMAND_GROUP_BITMAP,BITCOUNT_History,1,BITCOUNT_Tips,0,bitcountCommand,-2,CMD_READONLY,ACL_CATEGORY_BITMAP,BITCOUNT_Keyspecs,1,NULL,2),.args=BITCOUNT_Args},
{MAKE_CMD("bitfield","Performs arbitrary bitfield integer operations on strings.","O(1) for each subcommand specified","3.2.0",CMD_DOC_NONE,NULL,NULL,"bitmap",COMMAND_GROUP_BITMAP,BITFIELD_History,0,BITFIELD_Tips,0,bitfieldCommand,-2,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_BITMAP,BITFIELD_Keyspecs,1,bitfieldGetKeys,2),.args=BITFIELD_Args},
@ -11998,18 +12496,15 @@ struct COMMAND_STRUCT redisCommandTable[] = {
{MAKE_CMD("rpush","Appends one or more elements to a list. Creates the key if it doesn't exist.","O(1) for each element added, so O(N) to add N elements when the command is called with multiple arguments.","1.0.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,RPUSH_History,1,RPUSH_Tips,0,rpushCommand,-3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_LIST,RPUSH_Keyspecs,1,NULL,2),.args=RPUSH_Args},
{MAKE_CMD("rpushx","Appends an element to a list only when the list exists.","O(1) for each element added, so O(N) to add N elements when the command is called with multiple arguments.","2.2.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,RPUSHX_History,1,RPUSHX_Tips,0,rpushxCommand,-3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_LIST,RPUSHX_Keyspecs,1,NULL,2),.args=RPUSHX_Args},
/* pubsub */
{MAKE_CMD("psubscribe","Listens for messages published to channels that match one or more patterns.","O(N) where N is the number of patterns to subscribe to.","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PSUBSCRIBE_History,0,PSUBSCRIBE_Tips,0,psubscribeCommand,-2,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,PSUBSCRIBE_Keyspecs,0,NULL,1),.args=PSUBSCRIBE_Args},
{MAKE_CMD("psubscribe","Listens for messages published to channels that match one or more patterns.","O(N) where N is the number of patterns to subscribe to.","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PSUBSCRIBE_History,0,PSUBSCRIBE_Tips,0,psubscribeCommand,-2,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL|CMD_DENYOOM,0,PSUBSCRIBE_Keyspecs,0,NULL,1),.args=PSUBSCRIBE_Args},
{MAKE_CMD("publish","Posts a message to a channel.","O(N+M) where N is the number of clients subscribed to the receiving channel and M is the total number of subscribed patterns (by any client).","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PUBLISH_History,0,PUBLISH_Tips,0,publishCommand,3,CMD_PUBSUB|CMD_LOADING|CMD_STALE|CMD_FAST|CMD_MAY_REPLICATE|CMD_SENTINEL,0,PUBLISH_Keyspecs,0,NULL,2),.args=PUBLISH_Args},
{MAKE_CMD("pubsub","A container for Pub/Sub commands.","Depends on subcommand.","2.8.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PUBSUB_History,0,PUBSUB_Tips,0,NULL,-2,0,0,PUBSUB_Keyspecs,0,NULL,0),.subcommands=PUBSUB_Subcommands},
{MAKE_CMD("punsubscribe","Stops listening to messages published to channels that match one or more patterns.","O(N) where N is the number of patterns to unsubscribe.","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PUNSUBSCRIBE_History,0,PUNSUBSCRIBE_Tips,0,punsubscribeCommand,-1,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,PUNSUBSCRIBE_Keyspecs,0,NULL,1),.args=PUNSUBSCRIBE_Args},
{MAKE_CMD("spublish","Post a message to a shard channel","O(N) where N is the number of clients subscribed to the receiving shard channel.","7.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SPUBLISH_History,0,SPUBLISH_Tips,0,spublishCommand,3,CMD_PUBSUB|CMD_LOADING|CMD_STALE|CMD_FAST|CMD_MAY_REPLICATE,0,SPUBLISH_Keyspecs,1,NULL,2),.args=SPUBLISH_Args},
{MAKE_CMD("ssubscribe","Listens for messages published to shard channels.","O(N) where N is the number of shard channels to subscribe to.","7.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SSUBSCRIBE_History,0,SSUBSCRIBE_Tips,0,ssubscribeCommand,-2,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,0,SSUBSCRIBE_Keyspecs,1,NULL,1),.args=SSUBSCRIBE_Args},
{MAKE_CMD("subscribe","Listens for messages published to channels.","O(N) where N is the number of channels to subscribe to.","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SUBSCRIBE_History,0,SUBSCRIBE_Tips,0,subscribeCommand,-2,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,SUBSCRIBE_Keyspecs,0,NULL,1),.args=SUBSCRIBE_Args},
{MAKE_CMD("ssubscribe","Listens for messages published to shard channels.","O(N) where N is the number of shard channels to subscribe to.","7.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SSUBSCRIBE_History,0,SSUBSCRIBE_Tips,0,ssubscribeCommand,-2,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_DENYOOM,0,SSUBSCRIBE_Keyspecs,1,NULL,1),.args=SSUBSCRIBE_Args},
{MAKE_CMD("subscribe","Listens for messages published to channels.","O(N) where N is the number of channels to subscribe to.","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SUBSCRIBE_History,0,SUBSCRIBE_Tips,0,subscribeCommand,-2,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL|CMD_DENYOOM,0,SUBSCRIBE_Keyspecs,0,NULL,1),.args=SUBSCRIBE_Args},
{MAKE_CMD("sunsubscribe","Stops listening to messages posted to shard channels.","O(N) where N is the number of shard channels to unsubscribe.","7.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SUNSUBSCRIBE_History,0,SUNSUBSCRIBE_Tips,0,sunsubscribeCommand,-1,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,0,SUNSUBSCRIBE_Keyspecs,1,NULL,1),.args=SUNSUBSCRIBE_Args},
{MAKE_CMD("unsubscribe","Stops listening to messages posted to channels.","O(N) where N is the number of channels to unsubscribe.","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,UNSUBSCRIBE_History,0,UNSUBSCRIBE_Tips,0,unsubscribeCommand,-1,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,UNSUBSCRIBE_Keyspecs,0,NULL,1),.args=UNSUBSCRIBE_Args},
/* rate_limit */
{MAKE_CMD("gcra","Rate limit via GCRA (Generic Cell Rate Algorithm).","O(1)","8.8.0",CMD_DOC_NONE,NULL,NULL,"rate_limit",COMMAND_GROUP_RATE_LIMIT,GCRA_History,0,GCRA_Tips,0,gcraCommand,-5,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_RATE_LIMIT,GCRA_Keyspecs,1,NULL,5),.args=GCRA_Args},
{MAKE_CMD("gcrasetvalue","An internal command for recording a GCRA TAT value during AOF rewrite and replication.","O(1)","8.8.0",CMD_DOC_NONE,NULL,NULL,"rate_limit",COMMAND_GROUP_RATE_LIMIT,GCRASETVALUE_History,0,GCRASETVALUE_Tips,0,gcraSetValueCommand,3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_RATE_LIMIT,GCRASETVALUE_Keyspecs,1,NULL,2),.args=GCRASETVALUE_Args},
/* scripting */
{MAKE_CMD("eval","Executes a server-side Lua script.","Depends on the script that is executed.","2.6.0",CMD_DOC_NONE,NULL,NULL,"scripting",COMMAND_GROUP_SCRIPTING,EVAL_History,0,EVAL_Tips,0,evalCommand,-3,CMD_NOSCRIPT|CMD_SKIP_MONITOR|CMD_MAY_REPLICATE|CMD_NO_MANDATORY_KEYS|CMD_STALE,ACL_CATEGORY_SCRIPTING,EVAL_Keyspecs,1,evalGetKeys,4),.args=EVAL_Args},
{MAKE_CMD("evalsha","Executes a server-side Lua script by SHA1 digest.","Depends on the script that is executed.","2.6.0",CMD_DOC_NONE,NULL,NULL,"scripting",COMMAND_GROUP_SCRIPTING,EVALSHA_History,0,EVALSHA_Tips,0,evalShaCommand,-3,CMD_NOSCRIPT|CMD_SKIP_MONITOR|CMD_MAY_REPLICATE|CMD_NO_MANDATORY_KEYS|CMD_STALE,ACL_CATEGORY_SCRIPTING,EVALSHA_Keyspecs,1,evalGetKeys,4),.args=EVALSHA_Args},

48
src/commands/arcount.json Normal file
View file

@ -0,0 +1,48 @@
{
"ARCOUNT": {
"summary": "Returns the number of non-empty elements in an array.",
"complexity": "O(1)",
"group": "array",
"since": "8.8.0",
"arity": 2,
"function": "arcountCommand",
"command_flags": [
"READONLY",
"FAST"
],
"acl_categories": [
"ARRAY"
],
"key_specs": [
{
"flags": [
"RO",
"ACCESS"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"description": "The number of non-empty elements, or 0 if key does not exist.",
"type": "integer"
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
}
]
}
}

53
src/commands/ardel.json Normal file
View file

@ -0,0 +1,53 @@
{
"ARDEL": {
"summary": "Deletes elements at the specified indices in an array.",
"complexity": "O(N) where N is the number of indices to delete",
"group": "array",
"since": "8.8.0",
"arity": -3,
"function": "ardelCommand",
"command_flags": [
"WRITE",
"FAST"
],
"acl_categories": [
"ARRAY"
],
"key_specs": [
{
"flags": [
"RW",
"DELETE"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"description": "Number of elements deleted.",
"type": "integer"
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
},
{
"name": "index",
"type": "integer",
"multiple": true
}
]
}
}

View file

@ -0,0 +1,62 @@
{
"ARDELRANGE": {
"summary": "Deletes elements in one or more ranges.",
"complexity": "Proportional to the number of existing elements / slices touched, not to the numeric span of the requested ranges",
"group": "array",
"since": "8.8.0",
"arity": -4,
"function": "ardelrangeCommand",
"command_flags": [
"WRITE"
],
"acl_categories": [
"ARRAY"
],
"key_specs": [
{
"flags": [
"RW",
"DELETE"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"description": "Number of elements deleted.",
"type": "integer"
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
},
{
"name": "range",
"type": "block",
"multiple": true,
"arguments": [
{
"name": "start",
"type": "integer"
},
{
"name": "end",
"type": "integer"
}
]
}
]
}
}

60
src/commands/arget.json Normal file
View file

@ -0,0 +1,60 @@
{
"ARGET": {
"summary": "Gets the value at an index in an array.",
"complexity": "O(1)",
"group": "array",
"since": "8.8.0",
"arity": 3,
"function": "argetCommand",
"command_flags": [
"READONLY",
"FAST"
],
"acl_categories": [
"ARRAY"
],
"key_specs": [
{
"flags": [
"RO",
"ACCESS"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"oneOf": [
{
"description": "The value at the given index.",
"type": "string"
},
{
"description": "Null reply if key or index does not exist.",
"type": "null"
}
]
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
},
{
"name": "index",
"type": "integer"
}
]
}
}

View file

@ -0,0 +1,64 @@
{
"ARGETRANGE": {
"summary": "Gets values in a range of indices.",
"complexity": "O(N) where N is the range length",
"group": "array",
"since": "8.8.0",
"arity": 4,
"function": "argetrangeCommand",
"command_flags": [
"READONLY"
],
"acl_categories": [
"ARRAY"
],
"key_specs": [
{
"flags": [
"RO",
"ACCESS"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"type": "array",
"items": {
"oneOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
},
{
"name": "start",
"type": "integer"
},
{
"name": "end",
"type": "integer"
}
]
}
}

182
src/commands/argrep.json Normal file
View file

@ -0,0 +1,182 @@
{
"ARGREP": {
"summary": "Searches array elements in a range using textual predicates.",
"complexity": "O(P * C) where P is the number of visited positions in touched slices and C is the cost of evaluating the predicates on one existing element.",
"group": "array",
"since": "8.8.0",
"arity": -6,
"function": "argrepCommand",
"command_flags": [
"READONLY"
],
"acl_categories": [
"ARRAY"
],
"key_specs": [
{
"flags": [
"RO",
"ACCESS"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"anyOf": [
{
"description": "Array of matching indexes.",
"type": "array",
"items": {
"type": "integer",
"description": "Index of a matching element"
}
},
{
"description": "Array of [index, value] pairs. Returned in case `WITHVALUES` was used.",
"type": "array",
"items": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": [
{
"type": "integer",
"description": "Index of a matching element"
},
{
"type": "string",
"description": "Value at that index"
}
]
}
}
]
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
},
{
"name": "start",
"type": "string"
},
{
"name": "end",
"type": "string"
},
{
"name": "predicate",
"type": "oneof",
"multiple": true,
"arguments": [
{
"name": "exact",
"type": "block",
"arguments": [
{
"name": "exact",
"type": "pure-token",
"token": "EXACT"
},
{
"name": "string",
"type": "string"
}
]
},
{
"name": "match",
"type": "block",
"arguments": [
{
"name": "match",
"type": "pure-token",
"token": "MATCH"
},
{
"name": "string",
"type": "string"
}
]
},
{
"name": "glob",
"type": "block",
"arguments": [
{
"name": "glob",
"type": "pure-token",
"token": "GLOB"
},
{
"name": "pattern",
"type": "string"
}
]
},
{
"name": "re",
"type": "block",
"arguments": [
{
"name": "re",
"type": "pure-token",
"token": "RE"
},
{
"name": "pattern",
"type": "string"
}
]
}
]
},
{
"name": "options",
"type": "oneof",
"optional": true,
"multiple": true,
"arguments": [
{
"name": "and",
"type": "pure-token",
"token": "AND"
},
{
"name": "or",
"type": "pure-token",
"token": "OR"
},
{
"name": "limit",
"type": "integer",
"token": "LIMIT"
},
{
"name": "withvalues",
"type": "pure-token",
"token": "WITHVALUES"
},
{
"name": "nocase",
"type": "pure-token",
"token": "NOCASE"
}
]
}
]
}
}

103
src/commands/arinfo.json Normal file
View file

@ -0,0 +1,103 @@
{
"ARINFO": {
"summary": "Returns metadata about an array.",
"complexity": "O(1), or O(N) with FULL option where N is the number of slices.",
"group": "array",
"since": "8.8.0",
"arity": -2,
"function": "arinfoCommand",
"command_flags": [
"READONLY"
],
"acl_categories": [
"ARRAY"
],
"key_specs": [
{
"flags": [
"RO",
"ACCESS"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"type": "object",
"additionalProperties": false,
"properties": {
"count": {
"type": "integer",
"description": "Total number of non-empty elements."
},
"len": {
"type": "integer",
"description": "Logical length (highest index + 1)."
},
"next-insert-index": {
"type": "integer",
"description": "Index the next ARINSERT would use, or 0 if unset/exhausted."
},
"slices": {
"type": "integer",
"description": "Number of allocated slices."
},
"directory-size": {
"type": "integer",
"description": "Directory allocation capacity (flat dir_alloc or superdir sdir_cap)."
},
"super-dir-entries": {
"type": "integer",
"description": "Number of super-directory entries (0 if not in superdir mode)."
},
"slice-size": {
"type": "integer",
"description": "Configured slice size."
},
"dense-slices": {
"type": "integer",
"description": "Number of dense slices (FULL only)."
},
"sparse-slices": {
"type": "integer",
"description": "Number of sparse slices (FULL only)."
},
"avg-dense-size": {
"type": "number",
"description": "Average allocation size of dense slices (FULL only)."
},
"avg-dense-fill": {
"type": "number",
"description": "Average fill rate of dense slices (FULL only)."
},
"avg-sparse-size": {
"type": "number",
"description": "Average capacity of sparse slices (FULL only)."
}
}
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
},
{
"name": "full",
"type": "pure-token",
"token": "FULL",
"optional": true
}
]
}
}

View file

@ -0,0 +1,54 @@
{
"ARINSERT": {
"summary": "Inserts one or more values at consecutive indices.",
"complexity": "O(N) where N is the number of values",
"group": "array",
"since": "8.8.0",
"arity": -3,
"function": "arinsertCommand",
"command_flags": [
"WRITE",
"DENYOOM",
"FAST"
],
"acl_categories": [
"ARRAY"
],
"key_specs": [
{
"flags": [
"RW",
"UPDATE"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"description": "The last index where a value was inserted.",
"type": "integer"
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
},
{
"name": "value",
"type": "string",
"multiple": true
}
]
}
}

View file

@ -0,0 +1,66 @@
{
"ARLASTITEMS": {
"summary": "Returns the most recently inserted elements.",
"complexity": "O(N) where N is the count",
"group": "array",
"since": "8.8.0",
"arity": -3,
"function": "arlastitemsCommand",
"command_flags": [
"READONLY"
],
"acl_categories": [
"ARRAY"
],
"key_specs": [
{
"flags": [
"RO",
"ACCESS"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"type": "array",
"items": {
"oneOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
},
{
"name": "count",
"type": "integer"
},
{
"name": "rev",
"type": "pure-token",
"token": "REV",
"optional": true
}
]
}
}

48
src/commands/arlen.json Normal file
View file

@ -0,0 +1,48 @@
{
"ARLEN": {
"summary": "Returns the length of an array (max index + 1).",
"complexity": "O(1)",
"group": "array",
"since": "8.8.0",
"arity": 2,
"function": "arlenCommand",
"command_flags": [
"READONLY",
"FAST"
],
"acl_categories": [
"ARRAY"
],
"key_specs": [
{
"flags": [
"RO",
"ACCESS"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"description": "The length of the array (max index + 1), or 0 if key does not exist.",
"type": "integer"
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
}
]
}
}

62
src/commands/armget.json Normal file
View file

@ -0,0 +1,62 @@
{
"ARMGET": {
"summary": "Gets values at multiple indices in an array.",
"complexity": "O(N) where N is the number of indices",
"group": "array",
"since": "8.8.0",
"arity": -3,
"function": "armgetCommand",
"command_flags": [
"READONLY",
"FAST"
],
"acl_categories": [
"ARRAY"
],
"key_specs": [
{
"flags": [
"RO",
"ACCESS"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"type": "array",
"items": {
"oneOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
},
{
"name": "index",
"type": "integer",
"multiple": true
}
]
}
}

64
src/commands/armset.json Normal file
View file

@ -0,0 +1,64 @@
{
"ARMSET": {
"summary": "Sets multiple index-value pairs in an array.",
"complexity": "O(N) where N is the number of pairs",
"group": "array",
"since": "8.8.0",
"arity": -4,
"function": "armsetCommand",
"command_flags": [
"WRITE",
"DENYOOM",
"FAST"
],
"acl_categories": [
"ARRAY"
],
"key_specs": [
{
"flags": [
"RW",
"UPDATE"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"description": "Number of new slots that were set (previously empty).",
"type": "integer"
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
},
{
"name": "data",
"type": "block",
"multiple": true,
"arguments": [
{
"name": "index",
"type": "integer"
},
{
"name": "value",
"type": "string"
}
]
}
]
}
}

56
src/commands/arnext.json Normal file
View file

@ -0,0 +1,56 @@
{
"ARNEXT": {
"summary": "Returns the next index ARINSERT would use.",
"complexity": "O(1)",
"group": "array",
"since": "8.8.0",
"arity": 2,
"function": "arnextCommand",
"command_flags": [
"READONLY",
"FAST"
],
"acl_categories": [
"ARRAY"
],
"key_specs": [
{
"flags": [
"RO",
"ACCESS"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"oneOf": [
{
"description": "The next index ARINSERT would use. Returns 0 for missing keys or when no insert happened yet.",
"type": "integer"
},
{
"description": "Null when the insertion cursor is exhausted (next insert would overflow).",
"type": "null"
}
]
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
}
]
}
}

123
src/commands/arop.json Normal file
View file

@ -0,0 +1,123 @@
{
"AROP": {
"summary": "Performs aggregate operations on array elements in a range.",
"complexity": "O(P) where P is visited positions in touched slices (dense scanned slots + sparse entries), with worst-case O(|end-start|+1) and typical case close to O(N), where N is the number of existing elements in range.",
"group": "array",
"since": "8.8.0",
"arity": -5,
"function": "aropCommand",
"command_flags": [
"READONLY"
],
"acl_categories": [
"ARRAY"
],
"key_specs": [
{
"flags": [
"RO",
"ACCESS"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"oneOf": [
{
"description": "Result of the operation.",
"type": "string"
},
{
"description": "Integer result for MATCH, USED, AND, OR, XOR.",
"type": "integer"
},
{
"description": "Null if no elements match the operation.",
"type": "null"
}
]
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
},
{
"name": "start",
"type": "integer"
},
{
"name": "end",
"type": "integer"
},
{
"name": "operation",
"type": "oneof",
"arguments": [
{
"name": "sum",
"type": "pure-token",
"token": "SUM"
},
{
"name": "min",
"type": "pure-token",
"token": "MIN"
},
{
"name": "max",
"type": "pure-token",
"token": "MAX"
},
{
"name": "and",
"type": "pure-token",
"token": "AND"
},
{
"name": "or",
"type": "pure-token",
"token": "OR"
},
{
"name": "xor",
"type": "pure-token",
"token": "XOR"
},
{
"name": "match",
"type": "block",
"arguments": [
{
"name": "match",
"type": "pure-token",
"token": "MATCH"
},
{
"name": "value",
"type": "string"
}
]
},
{
"name": "used",
"type": "pure-token",
"token": "USED"
}
]
}
]
}
}

57
src/commands/arring.json Normal file
View file

@ -0,0 +1,57 @@
{
"ARRING": {
"summary": "Inserts values into a ring buffer of specified size, wrapping and truncating as needed.",
"complexity": "O(M) normally, O(N+M) on ring resize, where N is the maximum of the old and new ring size and M is the number of inserted values",
"group": "array",
"since": "8.8.0",
"arity": -4,
"function": "arringCommand",
"command_flags": [
"WRITE",
"DENYOOM"
],
"acl_categories": [
"ARRAY"
],
"key_specs": [
{
"flags": [
"RW",
"UPDATE"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"description": "The last index where a value was inserted.",
"type": "integer"
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
},
{
"name": "size",
"type": "integer"
},
{
"name": "value",
"type": "string",
"multiple": true
}
]
}
}

76
src/commands/arscan.json Normal file
View file

@ -0,0 +1,76 @@
{
"ARSCAN": {
"summary": "Iterates existing elements in a range, returning index-value pairs.",
"complexity": "O(P) where P is visited positions in touched slices (dense scanned slots + sparse entries), with worst-case O(|end-start|+1) and typical case close to O(N), where N is the number of existing elements in range.",
"group": "array",
"since": "8.8.0",
"arity": -4,
"function": "arscanCommand",
"command_flags": [
"READONLY"
],
"acl_categories": [
"ARRAY"
],
"key_specs": [
{
"flags": [
"RO",
"ACCESS"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"description": "Array of [index, value] pairs.",
"type": "array",
"items": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": [
{
"type": "integer",
"description": "Index of existing element"
},
{
"type": "string",
"description": "Value at that index"
}
]
}
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
},
{
"name": "start",
"type": "integer"
},
{
"name": "end",
"type": "integer"
},
{
"name": "limit",
"token": "LIMIT",
"type": "integer",
"optional": true
}
]
}
}

View file

@ -1,23 +1,22 @@
{
"GCRASETVALUE": {
"summary": "An internal command for recording a GCRA TAT value during AOF rewrite and replication.",
"ARSEEK": {
"summary": "Sets the ARINSERT / ARRING cursor to a specific index.",
"complexity": "O(1)",
"group": "rate_limit",
"group": "array",
"since": "8.8.0",
"arity": 3,
"function": "gcraSetValueCommand",
"function": "arseekCommand",
"command_flags": [
"WRITE",
"DENYOOM",
"FAST"
],
"acl_categories": [
"RATE_LIMIT"
"ARRAY"
],
"key_specs": [
{
"flags": [
"OW",
"RW",
"UPDATE"
],
"begin_search": {
@ -35,7 +34,8 @@
}
],
"reply_schema": {
"const": "OK"
"description": "1 if the cursor was set, 0 if the key does not exist.",
"type": "integer"
},
"arguments": [
{
@ -44,7 +44,7 @@
"key_spec_index": 0
},
{
"name": "tat",
"name": "index",
"type": "integer"
}
]

58
src/commands/arset.json Normal file
View file

@ -0,0 +1,58 @@
{
"ARSET": {
"summary": "Sets one or more contiguous values starting at an index in an array.",
"complexity": "O(N) where N is the number of values",
"group": "array",
"since": "8.8.0",
"arity": -4,
"function": "arsetCommand",
"command_flags": [
"WRITE",
"DENYOOM",
"FAST"
],
"acl_categories": [
"ARRAY"
],
"key_specs": [
{
"flags": [
"RW",
"UPDATE"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"description": "Number of new slots that were set (previously empty).",
"type": "integer"
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
},
{
"name": "index",
"type": "integer"
},
{
"name": "value",
"type": "string",
"multiple": true
}
]
}
}

View file

@ -59,6 +59,9 @@
{
"const": "hyperloglog"
},
{
"const": "array"
},
{
"const": "list"
},

View file

@ -1,92 +0,0 @@
{
"GCRA": {
"summary": "Rate limit via GCRA (Generic Cell Rate Algorithm).",
"complexity": "O(1)",
"group": "rate_limit",
"since": "8.8.0",
"arity": -5,
"function": "gcraCommand",
"command_flags": [
"WRITE",
"DENYOOM",
"FAST"
],
"acl_categories": [
"RATE_LIMIT"
],
"key_specs": [
{
"flags": [
"RW",
"ACCESS",
"UPDATE"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"type": "array",
"minItems": 5,
"maxItems": 5,
"description": "Rate limiting result",
"items": [
{
"type": "integer",
"description": "Limited: 0 if allowed, 1 if rate limited"
},
{
"type": "integer",
"description": "Max request tokens: always equal to max_burst+1"
},
{
"type": "integer",
"description": "Number of tokens available immediately"
},
{
"type": "integer",
"description": "Retry after: seconds after which the caller should retry. Always -1 if not limited"
},
{
"type": "integer",
"description": "Full burst after: seconds after which a full burst will be allowed"
}
]
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
},
{
"name": "max-burst",
"type": "integer"
},
{
"name": "tokens-per-period",
"type": "integer"
},
{
"name": "period",
"type": "double"
},
{
"name": "count",
"type": "integer",
"token": "TOKENS",
"optional": true
}
]
}
}

View file

@ -11,7 +11,8 @@
"NOSCRIPT",
"LOADING",
"STALE",
"SENTINEL"
"SENTINEL",
"DENYOOM"
],
"arguments": [
{

View file

@ -10,7 +10,8 @@
"PUBSUB",
"NOSCRIPT",
"LOADING",
"STALE"
"STALE",
"DENYOOM"
],
"arguments": [
{

View file

@ -12,7 +12,8 @@
"NOSCRIPT",
"LOADING",
"STALE",
"SENTINEL"
"SENTINEL",
"DENYOOM"
],
"arguments": [
{

View file

@ -24,6 +24,7 @@
#include <string.h>
#include <locale.h>
#include <ctype.h>
#include <arpa/inet.h>
/*-----------------------------------------------------------------------------
* Config file name-value maps.
@ -2428,13 +2429,7 @@ static int isValidAnnouncedNodename(char *val,const char **err) {
return 1;
}
static int isValidAnnouncedHostname(char *val, const char **err) {
if (strlen(val) >= NET_HOST_STR_LEN) {
*err = "Hostnames must be less than "
STRINGIFY(NET_HOST_STR_LEN) " characters";
return 0;
}
static int isValidHostnameChars(char *val, const char **err) {
int i = 0;
char c;
while ((c = val[i])) {
@ -2452,6 +2447,39 @@ static int isValidAnnouncedHostname(char *val, const char **err) {
return 1;
}
static int isValidAnnouncedHostname(char *val, const char **err) {
if (strlen(val) >= NET_HOST_STR_LEN) {
*err = "Hostnames must be less than "
STRINGIFY(NET_HOST_STR_LEN) " characters";
return 0;
}
return isValidHostnameChars(val, err);
}
/* Validation function for cluster-announce-ip.
* Ensures the IP address is valid and rejects control characters. */
static int isValidClusterAnnounceIp(char *val, const char **err) {
unsigned char buf[sizeof(struct in6_addr)];
/* Empty string is allowed - it will be converted to NULL by EMPTY_STRING_IS_NULL flag */
if (val[0] == '\0') {
return 1;
}
/* Accept valid IPv4 or IPv6 */
if (inet_pton(AF_INET, val, buf) == 1 || inet_pton(AF_INET6, val, buf) == 1) {
return 1;
}
/* Also accept valid hostnames, but limited to NET_IP_STR_LEN since
* cluster_announce_ip is stored in a NET_IP_STR_LEN buffer */
if (strlen(val) >= NET_IP_STR_LEN) {
*err = "Hostnames for cluster-announce-ip must be less than "
STRINGIFY(NET_IP_STR_LEN) " characters";
return 0;
}
/* Also accept valid hostnames */
return isValidHostnameChars(val, err);
}
/* Validate specified string is a valid proc-title-template */
static int isValidProcTitleTemplate(char *val, const char **err) {
if (!validateProcTitleTemplate(val)) {
@ -2461,6 +2489,33 @@ static int isValidProcTitleTemplate(char *val, const char **err) {
return 1;
}
/* Validate that array-slice-size is a power of two */
static int isValidArraySliceSize(long long val, const char **err) {
if (val <= 0 || (val & (val - 1)) != 0) {
*err = "array-slice-size must be a power of two";
return 0;
}
return 1;
}
/* Validate array-sparse-kmax: if non-zero, must be > kmin */
static int isValidArraySparseKmax(long long val, const char **err) {
if (val > 0 && (unsigned int)val <= server.array_sparse_kmin) {
*err = "array-sparse-kmax must be greater than array-sparse-kmin when non-zero";
return 0;
}
return 1;
}
/* Validate array-sparse-kmin: must be < kmax when kmax is non-zero */
static int isValidArraySparseKmin(long long val, const char **err) {
if (server.array_sparse_kmax > 0 && (unsigned int)val >= server.array_sparse_kmax) {
*err = "array-sparse-kmin must be less than array-sparse-kmax";
return 0;
}
return 1;
}
static int updateLocaleCollate(const char **err) {
const char *s = setlocale(LC_COLLATE, server.locale_collate);
if (s == NULL) {
@ -2917,7 +2972,11 @@ static int setConfigNotifyKeyspaceEventsOption(standardConfig *config, sds *argv
}
int flags = keyspaceEventsStringToFlags(argv[0]);
if (flags == -1) {
*err = "Invalid event class character. Use 'Ag$lshzxeKEtmdnocrSTIV'.";
#ifdef ENABLE_GCRA
*err = "Invalid event class character. Use 'Ag$lshzxeKEtmdnocraSTIV'.";
#else
*err = "Invalid event class character. Use 'Ag$lshzxeKEtmdnocaSTIV'.";
#endif
return 0;
}
server.notify_keyspace_events = flags;
@ -3159,7 +3218,7 @@ standardConfig static_configs[] = {
createStringConfig("pidfile", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.pidfile, NULL, NULL, NULL),
createStringConfig("replica-announce-ip", "slave-announce-ip", MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.slave_announce_ip, NULL, NULL, NULL),
createStringConfig("masteruser", NULL, MODIFIABLE_CONFIG | SENSITIVE_CONFIG, EMPTY_STRING_IS_NULL, server.masteruser, NULL, NULL, NULL),
createStringConfig("cluster-announce-ip", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.cluster_announce_ip, NULL, NULL, updateClusterIp),
createStringConfig("cluster-announce-ip", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.cluster_announce_ip, NULL, isValidClusterAnnounceIp, updateClusterIp),
createStringConfig("cluster-config-file", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.cluster_configfile, "nodes.conf", NULL, NULL),
createStringConfig("cluster-announce-hostname", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.cluster_announce_hostname, NULL, isValidAnnouncedHostname, updateClusterHostname),
createStringConfig("cluster-announce-human-nodename", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.cluster_announce_human_nodename, NULL, isValidAnnouncedNodename, updateClusterHumanNodename),
@ -3252,6 +3311,10 @@ standardConfig static_configs[] = {
createUIntConfig("socket-mark-id", NULL, IMMUTABLE_CONFIG, 0, UINT_MAX, server.socket_mark_id, 0, INTEGER_CONFIG, NULL, NULL),
createUIntConfig("max-new-connections-per-cycle", NULL, MODIFIABLE_CONFIG, 1, 1000, server.max_new_conns_per_cycle, 10, INTEGER_CONFIG, NULL, NULL),
createUIntConfig("max-new-tls-connections-per-cycle", NULL, MODIFIABLE_CONFIG, 1, 1000, server.max_new_tls_conns_per_cycle, 1, INTEGER_CONFIG, NULL, NULL),
/* Array type configuration */
createUIntConfig("array-slice-size", NULL, MODIFIABLE_CONFIG, AR_SLICE_SIZE_MIN, AR_SLICE_SIZE_MAX, server.array_slice_size, AR_SLICE_SIZE_DEFAULT, INTEGER_CONFIG, isValidArraySliceSize, NULL),
createUIntConfig("array-sparse-kmax", NULL, MODIFIABLE_CONFIG, 0, 256, server.array_sparse_kmax, AR_SPARSE_KMAX_DEFAULT, INTEGER_CONFIG, isValidArraySparseKmax, NULL),
createUIntConfig("array-sparse-kmin", NULL, MODIFIABLE_CONFIG, 0, 256, server.array_sparse_kmin, AR_SPARSE_KMIN_DEFAULT, INTEGER_CONFIG, isValidArraySparseKmin, NULL),
#ifdef LOG_REQ_RES
createUIntConfig("client-default-resp", NULL, IMMUTABLE_CONFIG | HIDDEN_CONFIG, 2, 3, server.client_default_resp, 2, INTEGER_CONFIG, NULL, NULL),
#endif

View file

@ -1751,14 +1751,17 @@ int parseScanCursorOrReply(client *c, robj *o, unsigned long long *cursor) {
}
char *obj_type_name[OBJ_TYPE_MAX] = {
"string",
"list",
"set",
"zset",
"hash",
"string",
"list",
"set",
"zset",
"hash",
NULL, /* module type is special */
"stream",
"array",
#ifdef ENABLE_GCRA
"gcra"
#endif
};
/* Helper function to get type from a string in scan commands */
@ -2433,11 +2436,14 @@ void copyCommand(client *c) {
case OBJ_ZSET: newobj = zsetDup(o); break;
case OBJ_HASH: newobj = hashTypeDup(o, &minHashExpire); break;
case OBJ_STREAM: newobj = streamDup(o); break;
#ifdef ENABLE_GCRA
case OBJ_GCRA: newobj = gcraDup(o); break;
#endif
case OBJ_MODULE:
newobj = moduleTypeDupOrReply(c, key, newkey, dst->id, o);
if (!newobj) return;
break;
case OBJ_ARRAY: newobj = arrayTypeDup(o); break;
default:
addReplyError(c, "unknown type object");
return;

View file

@ -123,6 +123,7 @@ void mixStringObjectDigest(unsigned char *digest, robj *o) {
decrRefCount(o);
}
#ifdef ENABLE_GCRA
void mixGCRAObjectDigest(unsigned char *digest, robj *o) {
char buf[LONG_STR_SIZE];
long long val;
@ -130,6 +131,7 @@ void mixGCRAObjectDigest(unsigned char *digest, robj *o) {
int len = ll2string(buf, sizeof(buf), val);
mixDigest(digest,buf,len);
}
#endif
/* This function computes the digest of a data structure stored in the
* object 'o'. It is the core of the DEBUG DIGEST command: when taking the
@ -263,8 +265,10 @@ void xorObjectDigest(redisDb *db, robj *keyobj, unsigned char *digest, robj *o)
}
}
streamIteratorStop(&si);
#ifdef ENABLE_GCRA
} else if (o->type == OBJ_GCRA) {
mixGCRAObjectDigest(digest, o);
#endif
} else if (o->type == OBJ_MODULE) {
RedisModuleDigest md = {{0},{0},keyobj,db->id};
moduleValue *mv = o->ptr;
@ -274,6 +278,21 @@ void xorObjectDigest(redisDb *db, robj *keyobj, unsigned char *digest, robj *o)
mt->digest(&md,mv->value);
xorDigest(digest,md.x,sizeof(md.x));
}
} else if (o->type == OBJ_ARRAY) {
redisArray *ar = o->ptr;
uint64_t len = arLen(ar);
for (uint64_t idx = 0; idx < len; idx++) {
void *v = arGet(ar, idx);
if (arIsEmpty(v)) {
/* For empty slots, contribute "(null)" */
mixDigest(digest, "(null)", 6);
} else {
char vbuf[AR_INLINE_BUFSIZE];
size_t vlen;
const char *data = arDecode(v, vbuf, sizeof(vbuf), &vlen);
mixDigest(digest, data, vlen);
}
}
} else {
serverPanic("Unknown object type");
}
@ -1312,9 +1331,11 @@ void serverLogObjectDebugInfo(const robj *o) {
serverLog(LL_WARNING,"Skiplist level: %d", (int) ((const zset*)o->ptr)->zsl->level);
} else if (o->type == OBJ_STREAM) {
serverLog(LL_WARNING,"Stream size: %d", (int) streamLength(o));
#ifdef ENABLE_GCRA
} else if (o->type == OBJ_GCRA) {
#if UINTPTR_MAX == 0xffffffffffffffff
serverLog(LL_WARNING, "GCRA object: %lld", (long long)o->ptr);
#endif
#endif
}
#endif

View file

@ -754,6 +754,32 @@ void defragSet(defragKeysCtx *ctx, kvobj *ob) {
ob->ptr = newd;
}
/* Arrays can be expensive to defrag in one shot because they may contain many
* independently allocated slices. Small arrays are defragmented immediately,
* while large arrays are queued for later and processed one slice per step. */
void defragArray(defragKeysCtx *ctx, kvobj *ob) {
serverAssert(ob->type == OBJ_ARRAY);
/* Maybe arCount() is not the best possible value to check against
* server.active_defrag_max_scan_fields, also because anyway when we
* defrag incrementally, we defrag a since slice per call. Yet it makes
* sense in a non very obvious way, for several reasons:
*
* 1. If the array is very sparse, it is an upper bound to the max
* number of slices it is composed to.
* 2. If the array is dense, we will scan in the default case at most 4096
* entries, and the default defrag limit for max scans is 1000. They
* are kinda comparable numbers.
* 3. In case of a highly sparse array with huge indexes, in superdir mode,
* yet the super blocks are going to be at max arCount().
*
* So regardless of the fact we later will defrag in slice units, this
* is a good trigger for the one shot or incremental selection. */
if (arCount(ob->ptr) > server.active_defrag_max_scan_fields)
defragLater(ctx, ob);
else
ob->ptr = arDefrag(ob->ptr, activeDefragAlloc);
}
/* Defrag callback for radix tree iterator, called for each node,
* used in order to defrag the nodes allocations. */
int defragRaxNode(raxNode **noderef, void *privdata) {
@ -1163,15 +1189,19 @@ void defragKey(defragKeysCtx *ctx, dictEntry *de, dictEntryLink link) {
}
} else if (ob->type == OBJ_STREAM) {
defragStream(ctx, ob);
#ifdef ENABLE_GCRA
} else if (ob->type == OBJ_GCRA) {
/* GCRA object is just an allocation to a long long value */
#if UINTPTR_MAX == 0xffffffff
void *newptr, *ptr = ob->ptr;
if ((newptr = activeDefragAlloc(ptr)))
ob->ptr = newptr;
#endif
#endif
} else if (ob->type == OBJ_MODULE) {
defragModule(ctx,db, ob);
} else if (ob->type == OBJ_ARRAY) {
defragArray(ctx, ob);
} else {
serverPanic("Unknown object type");
}
@ -1288,6 +1318,10 @@ int defragLaterItem(kvobj *ob, unsigned long *cursor, monotime endtime, int dbid
robj keyobj;
initStaticStringObject(keyobj, kvobjGetKey(ob));
return moduleLateDefrag(&keyobj, ob, cursor, endtime, dbid);
} else if (ob->type == OBJ_ARRAY) {
redisArray *ar = ob->ptr;
*cursor = arDefragIncremental(&ar, *cursor, activeDefragAlloc);
ob->ptr = ar;
} else {
*cursor = 0; /* object type/encoding may have changed since we schedule it for later */
}

View file

@ -1502,7 +1502,9 @@ void ldbEval(lua_State *lua, sds *argv, int argc) {
sdsfree(code);
sdsfree(expr);
if (lua_pcall(lua,0,1,0)) {
ldbLog(sdscatfmt(sdsempty(),"<error> %s",lua_tostring(lua,-1)));
const char *err = lua_tostring(lua,-1);
ldbLog(sdscatfmt(sdsempty(),"<error> %s",
err ? err : "(error object is not a string)"));
lua_pop(lua,1);
return;
}

View file

@ -48,6 +48,195 @@ static const double powers_of_ten[] = {
1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22
};
/* ----------------------------------------------------------------------------
* Eisel-Lemire algorithm extended-precision powers of five.
*
* The table below maps from decimal scaling (10^q) to a 128-bit binary
* approximation. Since 10^q = 2^q * 5^q and the 2^q factor is exact in
* binary, only 5^q affects the binary significand so we precompute
* 5^q rounded toward 1 to 128 bits. Used by `compute_float()` to avoid
* any iterative rounding in the widened (mantissa > 2^53) range.
*
* Pulled verbatim from fast_float by Daniel Lemire & Joao Paulo Magalhaes
* (MIT-licensed, https://github.com/fastfloat/fast_float — fast_table.h).
*
* Range: 5^-342 ... 5^308 covers every value that can produce a finite
* non-zero double from a 64-bit decimal mantissa. 651 entries, each stored
* as { high64, low64 } pairs (1302 uint64_t total).
* ---------------------------------------------------------------------------- */
#define EISEL_LEMIRE_SMALLEST_POWER_OF_FIVE -342
#define EISEL_LEMIRE_LARGEST_POWER_OF_FIVE 308
#define EISEL_LEMIRE_NUMBER_OF_ENTRIES (2 * (EISEL_LEMIRE_LARGEST_POWER_OF_FIVE - \
EISEL_LEMIRE_SMALLEST_POWER_OF_FIVE + 1))
static const uint64_t power_of_five_128[EISEL_LEMIRE_NUMBER_OF_ENTRIES] = {
0xeef453d6923bd65a, 0x113faa2906a13b3f, 0x9558b4661b6565f8, 0x4ac7ca59a424c507, 0xbaaee17fa23ebf76, 0x5d79bcf00d2df649, 0xe95a99df8ace6f53, 0xf4d82c2c107973dc,
0x91d8a02bb6c10594, 0x79071b9b8a4be869, 0xb64ec836a47146f9, 0x9748e2826cdee284, 0xe3e27a444d8d98b7, 0xfd1b1b2308169b25, 0x8e6d8c6ab0787f72, 0xfe30f0f5e50e20f7,
0xb208ef855c969f4f, 0xbdbd2d335e51a935, 0xde8b2b66b3bc4723, 0xad2c788035e61382, 0x8b16fb203055ac76, 0x4c3bcb5021afcc31, 0xaddcb9e83c6b1793, 0xdf4abe242a1bbf3d,
0xd953e8624b85dd78, 0xd71d6dad34a2af0d, 0x87d4713d6f33aa6b, 0x8672648c40e5ad68, 0xa9c98d8ccb009506, 0x680efdaf511f18c2, 0xd43bf0effdc0ba48, 0x212bd1b2566def2,
0x84a57695fe98746d, 0x14bb630f7604b57, 0xa5ced43b7e3e9188, 0x419ea3bd35385e2d, 0xcf42894a5dce35ea, 0x52064cac828675b9, 0x818995ce7aa0e1b2, 0x7343efebd1940993,
0xa1ebfb4219491a1f, 0x1014ebe6c5f90bf8, 0xca66fa129f9b60a6, 0xd41a26e077774ef6, 0xfd00b897478238d0, 0x8920b098955522b4, 0x9e20735e8cb16382, 0x55b46e5f5d5535b0,
0xc5a890362fddbc62, 0xeb2189f734aa831d, 0xf712b443bbd52b7b, 0xa5e9ec7501d523e4, 0x9a6bb0aa55653b2d, 0x47b233c92125366e, 0xc1069cd4eabe89f8, 0x999ec0bb696e840a,
0xf148440a256e2c76, 0xc00670ea43ca250d, 0x96cd2a865764dbca, 0x380406926a5e5728, 0xbc807527ed3e12bc, 0xc605083704f5ecf2, 0xeba09271e88d976b, 0xf7864a44c633682e,
0x93445b8731587ea3, 0x7ab3ee6afbe0211d, 0xb8157268fdae9e4c, 0x5960ea05bad82964, 0xe61acf033d1a45df, 0x6fb92487298e33bd, 0x8fd0c16206306bab, 0xa5d3b6d479f8e056,
0xb3c4f1ba87bc8696, 0x8f48a4899877186c, 0xe0b62e2929aba83c, 0x331acdabfe94de87, 0x8c71dcd9ba0b4925, 0x9ff0c08b7f1d0b14, 0xaf8e5410288e1b6f, 0x7ecf0ae5ee44dd9,
0xdb71e91432b1a24a, 0xc9e82cd9f69d6150, 0x892731ac9faf056e, 0xbe311c083a225cd2, 0xab70fe17c79ac6ca, 0x6dbd630a48aaf406, 0xd64d3d9db981787d, 0x92cbbccdad5b108,
0x85f0468293f0eb4e, 0x25bbf56008c58ea5, 0xa76c582338ed2621, 0xaf2af2b80af6f24e, 0xd1476e2c07286faa, 0x1af5af660db4aee1, 0x82cca4db847945ca, 0x50d98d9fc890ed4d,
0xa37fce126597973c, 0xe50ff107bab528a0, 0xcc5fc196fefd7d0c, 0x1e53ed49a96272c8, 0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7a, 0x9faacf3df73609b1, 0x77b191618c54e9ac,
0xc795830d75038c1d, 0xd59df5b9ef6a2417, 0xf97ae3d0d2446f25, 0x4b0573286b44ad1d, 0x9becce62836ac577, 0x4ee367f9430aec32, 0xc2e801fb244576d5, 0x229c41f793cda73f,
0xf3a20279ed56d48a, 0x6b43527578c1110f, 0x9845418c345644d6, 0x830a13896b78aaa9, 0xbe5691ef416bd60c, 0x23cc986bc656d553, 0xedec366b11c6cb8f, 0x2cbfbe86b7ec8aa8,
0x94b3a202eb1c3f39, 0x7bf7d71432f3d6a9, 0xb9e08a83a5e34f07, 0xdaf5ccd93fb0cc53, 0xe858ad248f5c22c9, 0xd1b3400f8f9cff68, 0x91376c36d99995be, 0x23100809b9c21fa1,
0xb58547448ffffb2d, 0xabd40a0c2832a78a, 0xe2e69915b3fff9f9, 0x16c90c8f323f516c, 0x8dd01fad907ffc3b, 0xae3da7d97f6792e3, 0xb1442798f49ffb4a, 0x99cd11cfdf41779c,
0xdd95317f31c7fa1d, 0x40405643d711d583, 0x8a7d3eef7f1cfc52, 0x482835ea666b2572, 0xad1c8eab5ee43b66, 0xda3243650005eecf, 0xd863b256369d4a40, 0x90bed43e40076a82,
0x873e4f75e2224e68, 0x5a7744a6e804a291, 0xa90de3535aaae202, 0x711515d0a205cb36, 0xd3515c2831559a83, 0xd5a5b44ca873e03, 0x8412d9991ed58091, 0xe858790afe9486c2,
0xa5178fff668ae0b6, 0x626e974dbe39a872, 0xce5d73ff402d98e3, 0xfb0a3d212dc8128f, 0x80fa687f881c7f8e, 0x7ce66634bc9d0b99, 0xa139029f6a239f72, 0x1c1fffc1ebc44e80,
0xc987434744ac874e, 0xa327ffb266b56220, 0xfbe9141915d7a922, 0x4bf1ff9f0062baa8, 0x9d71ac8fada6c9b5, 0x6f773fc3603db4a9, 0xc4ce17b399107c22, 0xcb550fb4384d21d3,
0xf6019da07f549b2b, 0x7e2a53a146606a48, 0x99c102844f94e0fb, 0x2eda7444cbfc426d, 0xc0314325637a1939, 0xfa911155fefb5308, 0xf03d93eebc589f88, 0x793555ab7eba27ca,
0x96267c7535b763b5, 0x4bc1558b2f3458de, 0xbbb01b9283253ca2, 0x9eb1aaedfb016f16, 0xea9c227723ee8bcb, 0x465e15a979c1cadc, 0x92a1958a7675175f, 0xbfacd89ec191ec9,
0xb749faed14125d36, 0xcef980ec671f667b, 0xe51c79a85916f484, 0x82b7e12780e7401a, 0x8f31cc0937ae58d2, 0xd1b2ecb8b0908810, 0xb2fe3f0b8599ef07, 0x861fa7e6dcb4aa15,
0xdfbdcece67006ac9, 0x67a791e093e1d49a, 0x8bd6a141006042bd, 0xe0c8bb2c5c6d24e0, 0xaecc49914078536d, 0x58fae9f773886e18, 0xda7f5bf590966848, 0xaf39a475506a899e,
0x888f99797a5e012d, 0x6d8406c952429603, 0xaab37fd7d8f58178, 0xc8e5087ba6d33b83, 0xd5605fcdcf32e1d6, 0xfb1e4a9a90880a64, 0x855c3be0a17fcd26, 0x5cf2eea09a55067f,
0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481e, 0xd0601d8efc57b08b, 0xf13b94daf124da26, 0x823c12795db6ce57, 0x76c53d08d6b70858, 0xa2cb1717b52481ed, 0x54768c4b0c64ca6e,
0xcb7ddcdda26da268, 0xa9942f5dcf7dfd09, 0xfe5d54150b090b02, 0xd3f93b35435d7c4c, 0x9efa548d26e5a6e1, 0xc47bc5014a1a6daf, 0xc6b8e9b0709f109a, 0x359ab6419ca1091b,
0xf867241c8cc6d4c0, 0xc30163d203c94b62, 0x9b407691d7fc44f8, 0x79e0de63425dcf1d, 0xc21094364dfb5636, 0x985915fc12f542e4, 0xf294b943e17a2bc4, 0x3e6f5b7b17b2939d,
0x979cf3ca6cec5b5a, 0xa705992ceecf9c42, 0xbd8430bd08277231, 0x50c6ff782a838353, 0xece53cec4a314ebd, 0xa4f8bf5635246428, 0x940f4613ae5ed136, 0x871b7795e136be99,
0xb913179899f68584, 0x28e2557b59846e3f, 0xe757dd7ec07426e5, 0x331aeada2fe589cf, 0x9096ea6f3848984f, 0x3ff0d2c85def7621, 0xb4bca50b065abe63, 0xfed077a756b53a9,
0xe1ebce4dc7f16dfb, 0xd3e8495912c62894, 0x8d3360f09cf6e4bd, 0x64712dd7abbbd95c, 0xb080392cc4349dec, 0xbd8d794d96aacfb3, 0xdca04777f541c567, 0xecf0d7a0fc5583a0,
0x89e42caaf9491b60, 0xf41686c49db57244, 0xac5d37d5b79b6239, 0x311c2875c522ced5, 0xd77485cb25823ac7, 0x7d633293366b828b, 0x86a8d39ef77164bc, 0xae5dff9c02033197,
0xa8530886b54dbdeb, 0xd9f57f830283fdfc, 0xd267caa862a12d66, 0xd072df63c324fd7b, 0x8380dea93da4bc60, 0x4247cb9e59f71e6d, 0xa46116538d0deb78, 0x52d9be85f074e608,
0xcd795be870516656, 0x67902e276c921f8b, 0x806bd9714632dff6, 0xba1cd8a3db53b6, 0xa086cfcd97bf97f3, 0x80e8a40eccd228a4, 0xc8a883c0fdaf7df0, 0x6122cd128006b2cd,
0xfad2a4b13d1b5d6c, 0x796b805720085f81, 0x9cc3a6eec6311a63, 0xcbe3303674053bb0, 0xc3f490aa77bd60fc, 0xbedbfc4411068a9c, 0xf4f1b4d515acb93b, 0xee92fb5515482d44,
0x991711052d8bf3c5, 0x751bdd152d4d1c4a, 0xbf5cd54678eef0b6, 0xd262d45a78a0635d, 0xef340a98172aace4, 0x86fb897116c87c34, 0x9580869f0e7aac0e, 0xd45d35e6ae3d4da0,
0xbae0a846d2195712, 0x8974836059cca109, 0xe998d258869facd7, 0x2bd1a438703fc94b, 0x91ff83775423cc06, 0x7b6306a34627ddcf, 0xb67f6455292cbf08, 0x1a3bc84c17b1d542,
0xe41f3d6a7377eeca, 0x20caba5f1d9e4a93, 0x8e938662882af53e, 0x547eb47b7282ee9c, 0xb23867fb2a35b28d, 0xe99e619a4f23aa43, 0xdec681f9f4c31f31, 0x6405fa00e2ec94d4,
0x8b3c113c38f9f37e, 0xde83bc408dd3dd04, 0xae0b158b4738705e, 0x9624ab50b148d445, 0xd98ddaee19068c76, 0x3badd624dd9b0957, 0x87f8a8d4cfa417c9, 0xe54ca5d70a80e5d6,
0xa9f6d30a038d1dbc, 0x5e9fcf4ccd211f4c, 0xd47487cc8470652b, 0x7647c3200069671f, 0x84c8d4dfd2c63f3b, 0x29ecd9f40041e073, 0xa5fb0a17c777cf09, 0xf468107100525890,
0xcf79cc9db955c2cc, 0x7182148d4066eeb4, 0x81ac1fe293d599bf, 0xc6f14cd848405530, 0xa21727db38cb002f, 0xb8ada00e5a506a7c, 0xca9cf1d206fdc03b, 0xa6d90811f0e4851c,
0xfd442e4688bd304a, 0x908f4a166d1da663, 0x9e4a9cec15763e2e, 0x9a598e4e043287fe, 0xc5dd44271ad3cdba, 0x40eff1e1853f29fd, 0xf7549530e188c128, 0xd12bee59e68ef47c,
0x9a94dd3e8cf578b9, 0x82bb74f8301958ce, 0xc13a148e3032d6e7, 0xe36a52363c1faf01, 0xf18899b1bc3f8ca1, 0xdc44e6c3cb279ac1, 0x96f5600f15a7b7e5, 0x29ab103a5ef8c0b9,
0xbcb2b812db11a5de, 0x7415d448f6b6f0e7, 0xebdf661791d60f56, 0x111b495b3464ad21, 0x936b9fcebb25c995, 0xcab10dd900beec34, 0xb84687c269ef3bfb, 0x3d5d514f40eea742,
0xe65829b3046b0afa, 0xcb4a5a3112a5112, 0x8ff71a0fe2c2e6dc, 0x47f0e785eaba72ab, 0xb3f4e093db73a093, 0x59ed216765690f56, 0xe0f218b8d25088b8, 0x306869c13ec3532c,
0x8c974f7383725573, 0x1e414218c73a13fb, 0xafbd2350644eeacf, 0xe5d1929ef90898fa, 0xdbac6c247d62a583, 0xdf45f746b74abf39, 0x894bc396ce5da772, 0x6b8bba8c328eb783,
0xab9eb47c81f5114f, 0x66ea92f3f326564, 0xd686619ba27255a2, 0xc80a537b0efefebd, 0x8613fd0145877585, 0xbd06742ce95f5f36, 0xa798fc4196e952e7, 0x2c48113823b73704,
0xd17f3b51fca3a7a0, 0xf75a15862ca504c5, 0x82ef85133de648c4, 0x9a984d73dbe722fb, 0xa3ab66580d5fdaf5, 0xc13e60d0d2e0ebba, 0xcc963fee10b7d1b3, 0x318df905079926a8,
0xffbbcfe994e5c61f, 0xfdf17746497f7052, 0x9fd561f1fd0f9bd3, 0xfeb6ea8bedefa633, 0xc7caba6e7c5382c8, 0xfe64a52ee96b8fc0, 0xf9bd690a1b68637b, 0x3dfdce7aa3c673b0,
0x9c1661a651213e2d, 0x6bea10ca65c084e, 0xc31bfa0fe5698db8, 0x486e494fcff30a62, 0xf3e2f893dec3f126, 0x5a89dba3c3efccfa, 0x986ddb5c6b3a76b7, 0xf89629465a75e01c,
0xbe89523386091465, 0xf6bbb397f1135823, 0xee2ba6c0678b597f, 0x746aa07ded582e2c, 0x94db483840b717ef, 0xa8c2a44eb4571cdc, 0xba121a4650e4ddeb, 0x92f34d62616ce413,
0xe896a0d7e51e1566, 0x77b020baf9c81d17, 0x915e2486ef32cd60, 0xace1474dc1d122e, 0xb5b5ada8aaff80b8, 0xd819992132456ba, 0xe3231912d5bf60e6, 0x10e1fff697ed6c69,
0x8df5efabc5979c8f, 0xca8d3ffa1ef463c1, 0xb1736b96b6fd83b3, 0xbd308ff8a6b17cb2, 0xddd0467c64bce4a0, 0xac7cb3f6d05ddbde, 0x8aa22c0dbef60ee4, 0x6bcdf07a423aa96b,
0xad4ab7112eb3929d, 0x86c16c98d2c953c6, 0xd89d64d57a607744, 0xe871c7bf077ba8b7, 0x87625f056c7c4a8b, 0x11471cd764ad4972, 0xa93af6c6c79b5d2d, 0xd598e40d3dd89bcf,
0xd389b47879823479, 0x4aff1d108d4ec2c3, 0x843610cb4bf160cb, 0xcedf722a585139ba, 0xa54394fe1eedb8fe, 0xc2974eb4ee658828, 0xce947a3da6a9273e, 0x733d226229feea32,
0x811ccc668829b887, 0x806357d5a3f525f, 0xa163ff802a3426a8, 0xca07c2dcb0cf26f7, 0xc9bcff6034c13052, 0xfc89b393dd02f0b5, 0xfc2c3f3841f17c67, 0xbbac2078d443ace2,
0x9d9ba7832936edc0, 0xd54b944b84aa4c0d, 0xc5029163f384a931, 0xa9e795e65d4df11, 0xf64335bcf065d37d, 0x4d4617b5ff4a16d5, 0x99ea0196163fa42e, 0x504bced1bf8e4e45,
0xc06481fb9bcf8d39, 0xe45ec2862f71e1d6, 0xf07da27a82c37088, 0x5d767327bb4e5a4c, 0x964e858c91ba2655, 0x3a6a07f8d510f86f, 0xbbe226efb628afea, 0x890489f70a55368b,
0xeadab0aba3b2dbe5, 0x2b45ac74ccea842e, 0x92c8ae6b464fc96f, 0x3b0b8bc90012929d, 0xb77ada0617e3bbcb, 0x9ce6ebb40173744, 0xe55990879ddcaabd, 0xcc420a6a101d0515,
0x8f57fa54c2a9eab6, 0x9fa946824a12232d, 0xb32df8e9f3546564, 0x47939822dc96abf9, 0xdff9772470297ebd, 0x59787e2b93bc56f7, 0x8bfbea76c619ef36, 0x57eb4edb3c55b65a,
0xaefae51477a06b03, 0xede622920b6b23f1, 0xdab99e59958885c4, 0xe95fab368e45eced, 0x88b402f7fd75539b, 0x11dbcb0218ebb414, 0xaae103b5fcd2a881, 0xd652bdc29f26a119,
0xd59944a37c0752a2, 0x4be76d3346f0495f, 0x857fcae62d8493a5, 0x6f70a4400c562ddb, 0xa6dfbd9fb8e5b88e, 0xcb4ccd500f6bb952, 0xd097ad07a71f26b2, 0x7e2000a41346a7a7,
0x825ecc24c873782f, 0x8ed400668c0c28c8, 0xa2f67f2dfa90563b, 0x728900802f0f32fa, 0xcbb41ef979346bca, 0x4f2b40a03ad2ffb9, 0xfea126b7d78186bc, 0xe2f610c84987bfa8,
0x9f24b832e6b0f436, 0xdd9ca7d2df4d7c9, 0xc6ede63fa05d3143, 0x91503d1c79720dbb, 0xf8a95fcf88747d94, 0x75a44c6397ce912a, 0x9b69dbe1b548ce7c, 0xc986afbe3ee11aba,
0xc24452da229b021b, 0xfbe85badce996168, 0xf2d56790ab41c2a2, 0xfae27299423fb9c3, 0x97c560ba6b0919a5, 0xdccd879fc967d41a, 0xbdb6b8e905cb600f, 0x5400e987bbc1c920,
0xed246723473e3813, 0x290123e9aab23b68, 0x9436c0760c86e30b, 0xf9a0b6720aaf6521, 0xb94470938fa89bce, 0xf808e40e8d5b3e69, 0xe7958cb87392c2c2, 0xb60b1d1230b20e04,
0x90bd77f3483bb9b9, 0xb1c6f22b5e6f48c2, 0xb4ecd5f01a4aa828, 0x1e38aeb6360b1af3, 0xe2280b6c20dd5232, 0x25c6da63c38de1b0, 0x8d590723948a535f, 0x579c487e5a38ad0e,
0xb0af48ec79ace837, 0x2d835a9df0c6d851, 0xdcdb1b2798182244, 0xf8e431456cf88e65, 0x8a08f0f8bf0f156b, 0x1b8e9ecb641b58ff, 0xac8b2d36eed2dac5, 0xe272467e3d222f3f,
0xd7adf884aa879177, 0x5b0ed81dcc6abb0f, 0x86ccbb52ea94baea, 0x98e947129fc2b4e9, 0xa87fea27a539e9a5, 0x3f2398d747b36224, 0xd29fe4b18e88640e, 0x8eec7f0d19a03aad,
0x83a3eeeef9153e89, 0x1953cf68300424ac, 0xa48ceaaab75a8e2b, 0x5fa8c3423c052dd7, 0xcdb02555653131b6, 0x3792f412cb06794d, 0x808e17555f3ebf11, 0xe2bbd88bbee40bd0,
0xa0b19d2ab70e6ed6, 0x5b6aceaeae9d0ec4, 0xc8de047564d20a8b, 0xf245825a5a445275, 0xfb158592be068d2e, 0xeed6e2f0f0d56712, 0x9ced737bb6c4183d, 0x55464dd69685606b,
0xc428d05aa4751e4c, 0xaa97e14c3c26b886, 0xf53304714d9265df, 0xd53dd99f4b3066a8, 0x993fe2c6d07b7fab, 0xe546a8038efe4029, 0xbf8fdb78849a5f96, 0xde98520472bdd033,
0xef73d256a5c0f77c, 0x963e66858f6d4440, 0x95a8637627989aad, 0xdde7001379a44aa8, 0xbb127c53b17ec159, 0x5560c018580d5d52, 0xe9d71b689dde71af, 0xaab8f01e6e10b4a6,
0x9226712162ab070d, 0xcab3961304ca70e8, 0xb6b00d69bb55c8d1, 0x3d607b97c5fd0d22, 0xe45c10c42a2b3b05, 0x8cb89a7db77c506a, 0x8eb98a7a9a5b04e3, 0x77f3608e92adb242,
0xb267ed1940f1c61c, 0x55f038b237591ed3, 0xdf01e85f912e37a3, 0x6b6c46dec52f6688, 0x8b61313bbabce2c6, 0x2323ac4b3b3da015, 0xae397d8aa96c1b77, 0xabec975e0a0d081a,
0xd9c7dced53c72255, 0x96e7bd358c904a21, 0x881cea14545c7575, 0x7e50d64177da2e54, 0xaa242499697392d2, 0xdde50bd1d5d0b9e9, 0xd4ad2dbfc3d07787, 0x955e4ec64b44e864,
0x84ec3c97da624ab4, 0xbd5af13bef0b113e, 0xa6274bbdd0fadd61, 0xecb1ad8aeacdd58e, 0xcfb11ead453994ba, 0x67de18eda5814af2, 0x81ceb32c4b43fcf4, 0x80eacf948770ced7,
0xa2425ff75e14fc31, 0xa1258379a94d028d, 0xcad2f7f5359a3b3e, 0x96ee45813a04330, 0xfd87b5f28300ca0d, 0x8bca9d6e188853fc, 0x9e74d1b791e07e48, 0x775ea264cf55347e,
0xc612062576589dda, 0x95364afe032a819e, 0xf79687aed3eec551, 0x3a83ddbd83f52205, 0x9abe14cd44753b52, 0xc4926a9672793543, 0xc16d9a0095928a27, 0x75b7053c0f178294,
0xf1c90080baf72cb1, 0x5324c68b12dd6339, 0x971da05074da7bee, 0xd3f6fc16ebca5e04, 0xbce5086492111aea, 0x88f4bb1ca6bcf585, 0xec1e4a7db69561a5, 0x2b31e9e3d06c32e6,
0x9392ee8e921d5d07, 0x3aff322e62439fd0, 0xb877aa3236a4b449, 0x9befeb9fad487c3, 0xe69594bec44de15b, 0x4c2ebe687989a9b4, 0x901d7cf73ab0acd9, 0xf9d37014bf60a11,
0xb424dc35095cd80f, 0x538484c19ef38c95, 0xe12e13424bb40e13, 0x2865a5f206b06fba, 0x8cbccc096f5088cb, 0xf93f87b7442e45d4, 0xafebff0bcb24aafe, 0xf78f69a51539d749,
0xdbe6fecebdedd5be, 0xb573440e5a884d1c, 0x89705f4136b4a597, 0x31680a88f8953031, 0xabcc77118461cefc, 0xfdc20d2b36ba7c3e, 0xd6bf94d5e57a42bc, 0x3d32907604691b4d,
0x8637bd05af6c69b5, 0xa63f9a49c2c1b110, 0xa7c5ac471b478423, 0xfcf80dc33721d54, 0xd1b71758e219652b, 0xd3c36113404ea4a9, 0x83126e978d4fdf3b, 0x645a1cac083126ea,
0xa3d70a3d70a3d70a, 0x3d70a3d70a3d70a4, 0xcccccccccccccccc, 0xcccccccccccccccd, 0x8000000000000000, 0x0, 0xa000000000000000, 0x0,
0xc800000000000000, 0x0, 0xfa00000000000000, 0x0, 0x9c40000000000000, 0x0, 0xc350000000000000, 0x0,
0xf424000000000000, 0x0, 0x9896800000000000, 0x0, 0xbebc200000000000, 0x0, 0xee6b280000000000, 0x0,
0x9502f90000000000, 0x0, 0xba43b74000000000, 0x0, 0xe8d4a51000000000, 0x0, 0x9184e72a00000000, 0x0,
0xb5e620f480000000, 0x0, 0xe35fa931a0000000, 0x0, 0x8e1bc9bf04000000, 0x0, 0xb1a2bc2ec5000000, 0x0,
0xde0b6b3a76400000, 0x0, 0x8ac7230489e80000, 0x0, 0xad78ebc5ac620000, 0x0, 0xd8d726b7177a8000, 0x0,
0x878678326eac9000, 0x0, 0xa968163f0a57b400, 0x0, 0xd3c21bcecceda100, 0x0, 0x84595161401484a0, 0x0,
0xa56fa5b99019a5c8, 0x0, 0xcecb8f27f4200f3a, 0x0, 0x813f3978f8940984, 0x4000000000000000, 0xa18f07d736b90be5, 0x5000000000000000,
0xc9f2c9cd04674ede, 0xa400000000000000, 0xfc6f7c4045812296, 0x4d00000000000000, 0x9dc5ada82b70b59d, 0xf020000000000000, 0xc5371912364ce305, 0x6c28000000000000,
0xf684df56c3e01bc6, 0xc732000000000000, 0x9a130b963a6c115c, 0x3c7f400000000000, 0xc097ce7bc90715b3, 0x4b9f100000000000, 0xf0bdc21abb48db20, 0x1e86d40000000000,
0x96769950b50d88f4, 0x1314448000000000, 0xbc143fa4e250eb31, 0x17d955a000000000, 0xeb194f8e1ae525fd, 0x5dcfab0800000000, 0x92efd1b8d0cf37be, 0x5aa1cae500000000,
0xb7abc627050305ad, 0xf14a3d9e40000000, 0xe596b7b0c643c719, 0x6d9ccd05d0000000, 0x8f7e32ce7bea5c6f, 0xe4820023a2000000, 0xb35dbf821ae4f38b, 0xdda2802c8a800000,
0xe0352f62a19e306e, 0xd50b2037ad200000, 0x8c213d9da502de45, 0x4526f422cc340000, 0xaf298d050e4395d6, 0x9670b12b7f410000, 0xdaf3f04651d47b4c, 0x3c0cdd765f114000,
0x88d8762bf324cd0f, 0xa5880a69fb6ac800, 0xab0e93b6efee0053, 0x8eea0d047a457a00, 0xd5d238a4abe98068, 0x72a4904598d6d880, 0x85a36366eb71f041, 0x47a6da2b7f864750,
0xa70c3c40a64e6c51, 0x999090b65f67d924, 0xd0cf4b50cfe20765, 0xfff4b4e3f741cf6d, 0x82818f1281ed449f, 0xbff8f10e7a8921a4, 0xa321f2d7226895c7, 0xaff72d52192b6a0d,
0xcbea6f8ceb02bb39, 0x9bf4f8a69f764490, 0xfee50b7025c36a08, 0x2f236d04753d5b4, 0x9f4f2726179a2245, 0x1d762422c946590, 0xc722f0ef9d80aad6, 0x424d3ad2b7b97ef5,
0xf8ebad2b84e0d58b, 0xd2e0898765a7deb2, 0x9b934c3b330c8577, 0x63cc55f49f88eb2f, 0xc2781f49ffcfa6d5, 0x3cbf6b71c76b25fb, 0xf316271c7fc3908a, 0x8bef464e3945ef7a,
0x97edd871cfda3a56, 0x97758bf0e3cbb5ac, 0xbde94e8e43d0c8ec, 0x3d52eeed1cbea317, 0xed63a231d4c4fb27, 0x4ca7aaa863ee4bdd, 0x945e455f24fb1cf8, 0x8fe8caa93e74ef6a,
0xb975d6b6ee39e436, 0xb3e2fd538e122b44, 0xe7d34c64a9c85d44, 0x60dbbca87196b616, 0x90e40fbeea1d3a4a, 0xbc8955e946fe31cd, 0xb51d13aea4a488dd, 0x6babab6398bdbe41,
0xe264589a4dcdab14, 0xc696963c7eed2dd1, 0x8d7eb76070a08aec, 0xfc1e1de5cf543ca2, 0xb0de65388cc8ada8, 0x3b25a55f43294bcb, 0xdd15fe86affad912, 0x49ef0eb713f39ebe,
0x8a2dbf142dfcc7ab, 0x6e3569326c784337, 0xacb92ed9397bf996, 0x49c2c37f07965404, 0xd7e77a8f87daf7fb, 0xdc33745ec97be906, 0x86f0ac99b4e8dafd, 0x69a028bb3ded71a3,
0xa8acd7c0222311bc, 0xc40832ea0d68ce0c, 0xd2d80db02aabd62b, 0xf50a3fa490c30190, 0x83c7088e1aab65db, 0x792667c6da79e0fa, 0xa4b8cab1a1563f52, 0x577001b891185938,
0xcde6fd5e09abcf26, 0xed4c0226b55e6f86, 0x80b05e5ac60b6178, 0x544f8158315b05b4, 0xa0dc75f1778e39d6, 0x696361ae3db1c721, 0xc913936dd571c84c, 0x3bc3a19cd1e38e9,
0xfb5878494ace3a5f, 0x4ab48a04065c723, 0x9d174b2dcec0e47b, 0x62eb0d64283f9c76, 0xc45d1df942711d9a, 0x3ba5d0bd324f8394, 0xf5746577930d6500, 0xca8f44ec7ee36479,
0x9968bf6abbe85f20, 0x7e998b13cf4e1ecb, 0xbfc2ef456ae276e8, 0x9e3fedd8c321a67e, 0xefb3ab16c59b14a2, 0xc5cfe94ef3ea101e, 0x95d04aee3b80ece5, 0xbba1f1d158724a12,
0xbb445da9ca61281f, 0x2a8a6e45ae8edc97, 0xea1575143cf97226, 0xf52d09d71a3293bd, 0x924d692ca61be758, 0x593c2626705f9c56, 0xb6e0c377cfa2e12e, 0x6f8b2fb00c77836c,
0xe498f455c38b997a, 0xb6dfb9c0f956447, 0x8edf98b59a373fec, 0x4724bd4189bd5eac, 0xb2977ee300c50fe7, 0x58edec91ec2cb657, 0xdf3d5e9bc0f653e1, 0x2f2967b66737e3ed,
0x8b865b215899f46c, 0xbd79e0d20082ee74, 0xae67f1e9aec07187, 0xecd8590680a3aa11, 0xda01ee641a708de9, 0xe80e6f4820cc9495, 0x884134fe908658b2, 0x3109058d147fdcdd,
0xaa51823e34a7eede, 0xbd4b46f0599fd415, 0xd4e5e2cdc1d1ea96, 0x6c9e18ac7007c91a, 0x850fadc09923329e, 0x3e2cf6bc604ddb0, 0xa6539930bf6bff45, 0x84db8346b786151c,
0xcfe87f7cef46ff16, 0xe612641865679a63, 0x81f14fae158c5f6e, 0x4fcb7e8f3f60c07e, 0xa26da3999aef7749, 0xe3be5e330f38f09d, 0xcb090c8001ab551c, 0x5cadf5bfd3072cc5,
0xfdcb4fa002162a63, 0x73d9732fc7c8f7f6, 0x9e9f11c4014dda7e, 0x2867e7fddcdd9afa, 0xc646d63501a1511d, 0xb281e1fd541501b8, 0xf7d88bc24209a565, 0x1f225a7ca91a4226,
0x9ae757596946075f, 0x3375788de9b06958, 0xc1a12d2fc3978937, 0x52d6b1641c83ae, 0xf209787bb47d6b84, 0xc0678c5dbd23a49a, 0x9745eb4d50ce6332, 0xf840b7ba963646e0,
0xbd176620a501fbff, 0xb650e5a93bc3d898, 0xec5d3fa8ce427aff, 0xa3e51f138ab4cebe, 0x93ba47c980e98cdf, 0xc66f336c36b10137, 0xb8a8d9bbe123f017, 0xb80b0047445d4184,
0xe6d3102ad96cec1d, 0xa60dc059157491e5, 0x9043ea1ac7e41392, 0x87c89837ad68db2f, 0xb454e4a179dd1877, 0x29babe4598c311fb, 0xe16a1dc9d8545e94, 0xf4296dd6fef3d67a,
0x8ce2529e2734bb1d, 0x1899e4a65f58660c, 0xb01ae745b101e9e4, 0x5ec05dcff72e7f8f, 0xdc21a1171d42645d, 0x76707543f4fa1f73, 0x899504ae72497eba, 0x6a06494a791c53a8,
0xabfa45da0edbde69, 0x487db9d17636892, 0xd6f8d7509292d603, 0x45a9d2845d3c42b6, 0x865b86925b9bc5c2, 0xb8a2392ba45a9b2, 0xa7f26836f282b732, 0x8e6cac7768d7141e,
0xd1ef0244af2364ff, 0x3207d795430cd926, 0x8335616aed761f1f, 0x7f44e6bd49e807b8, 0xa402b9c5a8d3a6e7, 0x5f16206c9c6209a6, 0xcd036837130890a1, 0x36dba887c37a8c0f,
0x802221226be55a64, 0xc2494954da2c9789, 0xa02aa96b06deb0fd, 0xf2db9baa10b7bd6c, 0xc83553c5c8965d3d, 0x6f92829494e5acc7, 0xfa42a8b73abbf48c, 0xcb772339ba1f17f9,
0x9c69a97284b578d7, 0xff2a760414536efb, 0xc38413cf25e2d70d, 0xfef5138519684aba, 0xf46518c2ef5b8cd1, 0x7eb258665fc25d69, 0x98bf2f79d5993802, 0xef2f773ffbd97a61,
0xbeeefb584aff8603, 0xaafb550ffacfd8fa, 0xeeaaba2e5dbf6784, 0x95ba2a53f983cf38, 0x952ab45cfa97a0b2, 0xdd945a747bf26183, 0xba756174393d88df, 0x94f971119aeef9e4,
0xe912b9d1478ceb17, 0x7a37cd5601aab85d, 0x91abb422ccb812ee, 0xac62e055c10ab33a, 0xb616a12b7fe617aa, 0x577b986b314d6009, 0xe39c49765fdf9d94, 0xed5a7e85fda0b80b,
0x8e41ade9fbebc27d, 0x14588f13be847307, 0xb1d219647ae6b31c, 0x596eb2d8ae258fc8, 0xde469fbd99a05fe3, 0x6fca5f8ed9aef3bb, 0x8aec23d680043bee, 0x25de7bb9480d5854,
0xada72ccc20054ae9, 0xaf561aa79a10ae6a, 0xd910f7ff28069da4, 0x1b2ba1518094da04, 0x87aa9aff79042286, 0x90fb44d2f05d0842, 0xa99541bf57452b28, 0x353a1607ac744a53,
0xd3fa922f2d1675f2, 0x42889b8997915ce8, 0x847c9b5d7c2e09b7, 0x69956135febada11, 0xa59bc234db398c25, 0x43fab9837e699095, 0xcf02b2c21207ef2e, 0x94f967e45e03f4bb,
0x8161afb94b44f57d, 0x1d1be0eebac278f5, 0xa1ba1ba79e1632dc, 0x6462d92a69731732, 0xca28a291859bbf93, 0x7d7b8f7503cfdcfe, 0xfcb2cb35e702af78, 0x5cda735244c3d43e,
0x9defbf01b061adab, 0x3a0888136afa64a7, 0xc56baec21c7a1916, 0x88aaa1845b8fdd0, 0xf6c69a72a3989f5b, 0x8aad549e57273d45, 0x9a3c2087a63f6399, 0x36ac54e2f678864b,
0xc0cb28a98fcf3c7f, 0x84576a1bb416a7dd, 0xf0fdf2d3f3c30b9f, 0x656d44a2a11c51d5, 0x969eb7c47859e743, 0x9f644ae5a4b1b325, 0xbc4665b596706114, 0x873d5d9f0dde1fee,
0xeb57ff22fc0c7959, 0xa90cb506d155a7ea, 0x9316ff75dd87cbd8, 0x9a7f12442d588f2, 0xb7dcbf5354e9bece, 0xc11ed6d538aeb2f, 0xe5d3ef282a242e81, 0x8f1668c8a86da5fa,
0x8fa475791a569d10, 0xf96e017d694487bc, 0xb38d92d760ec4455, 0x37c981dcc395a9ac, 0xe070f78d3927556a, 0x85bbe253f47b1417, 0x8c469ab843b89562, 0x93956d7478ccec8e,
0xaf58416654a6babb, 0x387ac8d1970027b2, 0xdb2e51bfe9d0696a, 0x6997b05fcc0319e, 0x88fcf317f22241e2, 0x441fece3bdf81f03, 0xab3c2fddeeaad25a, 0xd527e81cad7626c3,
0xd60b3bd56a5586f1, 0x8a71e223d8d3b074, 0x85c7056562757456, 0xf6872d5667844e49, 0xa738c6bebb12d16c, 0xb428f8ac016561db, 0xd106f86e69d785c7, 0xe13336d701beba52,
0x82a45b450226b39c, 0xecc0024661173473, 0xa34d721642b06084, 0x27f002d7f95d0190, 0xcc20ce9bd35c78a5, 0x31ec038df7b441f4, 0xff290242c83396ce, 0x7e67047175a15271,
0x9f79a169bd203e41, 0xf0062c6e984d386, 0xc75809c42c684dd1, 0x52c07b78a3e60868, 0xf92e0c3537826145, 0xa7709a56ccdf8a82, 0x9bbcc7a142b17ccb, 0x88a66076400bb691,
0xc2abf989935ddbfe, 0x6acff893d00ea435, 0xf356f7ebf83552fe, 0x583f6b8c4124d43, 0x98165af37b2153de, 0xc3727a337a8b704a, 0xbe1bf1b059e9a8d6, 0x744f18c0592e4c5c,
0xeda2ee1c7064130c, 0x1162def06f79df73, 0x9485d4d1c63e8be7, 0x8addcb5645ac2ba8, 0xb9a74a0637ce2ee1, 0x6d953e2bd7173692, 0xe8111c87c5c1ba99, 0xc8fa8db6ccdd0437,
0x910ab1d4db9914a0, 0x1d9c9892400a22a2, 0xb54d5e4a127f59c8, 0x2503beb6d00cab4b, 0xe2a0b5dc971f303a, 0x2e44ae64840fd61d, 0x8da471a9de737e24, 0x5ceaecfed289e5d2,
0xb10d8e1456105dad, 0x7425a83e872c5f47, 0xdd50f1996b947518, 0xd12f124e28f77719, 0x8a5296ffe33cc92f, 0x82bd6b70d99aaa6f, 0xace73cbfdc0bfb7b, 0x636cc64d1001550b,
0xd8210befd30efa5a, 0x3c47f7e05401aa4e, 0x8714a775e3e95c78, 0x65acfaec34810a71, 0xa8d9d1535ce3b396, 0x7f1839a741a14d0d, 0xd31045a8341ca07c, 0x1ede48111209a050,
0x83ea2b892091e44d, 0x934aed0aab460432, 0xa4e4b66b68b65d60, 0xf81da84d5617853f, 0xce1de40642e3f4b9, 0x36251260ab9d668e, 0x80d2ae83e9ce78f3, 0xc1d72b7c6b426019,
0xa1075a24e4421730, 0xb24cf65b8612f81f, 0xc94930ae1d529cfc, 0xdee033f26797b627, 0xfb9b7cd9a4a7443c, 0x169840ef017da3b1, 0x9d412e0806e88aa5, 0x8e1f289560ee864e,
0xc491798a08a2ad4e, 0xf1a6f2bab92a27e2, 0xf5b5d7ec8acb58a2, 0xae10af696774b1db, 0x9991a6f3d6bf1765, 0xacca6da1e0a8ef29, 0xbff610b0cc6edd3f, 0x17fd090a58d32af3,
0xeff394dcff8a948e, 0xddfc4b4cef07f5b0, 0x95f83d0a1fb69cd9, 0x4abdaf101564f98e, 0xbb764c4ca7a4440f, 0x9d6d1ad41abe37f1, 0xea53df5fd18d5513, 0x84c86189216dc5ed,
0x92746b9be2f8552c, 0x32fd3cf5b4e49bb4, 0xb7118682dbb66a77, 0x3fbc8c33221dc2a1, 0xe4d5e82392a40515, 0xfabaf3feaa5334a, 0x8f05b1163ba6832d, 0x29cb4d87f2a7400e,
0xb2c71d5bca9023f8, 0x743e20e9ef511012, 0xdf78e4b2bd342cf6, 0x914da9246b255416, 0x8bab8eefb6409c1a, 0x1ad089b6c2f7548e, 0xae9672aba3d0c320, 0xa184ac2473b529b1,
0xda3c0f568cc4f3e8, 0xc9e5d72d90a2741e, 0x8865899617fb1871, 0x7e2fa67c7a658892, 0xaa7eebfb9df9de8d, 0xddbb901b98feeab7, 0xd51ea6fa85785631, 0x552a74227f3ea565,
0x8533285c936b35de, 0xd53a88958f87275f, 0xa67ff273b8460356, 0x8a892abaf368f137, 0xd01fef10a657842c, 0x2d2b7569b0432d85, 0x8213f56a67f6b29b, 0x9c3b29620e29fc73,
0xa298f2c501f45f42, 0x8349f3ba91b47b8f, 0xcb3f2f7642717713, 0x241c70a936219a73, 0xfe0efb53d30dd4d7, 0xed238cd383aa0110, 0x9ec95d1463e8a506, 0xf4363804324a40aa,
0xc67bb4597ce2ce48, 0xb143c6053edcd0d5, 0xf81aa16fdc1b81da, 0xdd94b7868e94050a, 0x9b10a4e5e9913128, 0xca7cf2b4191c8326, 0xc1d4ce1f63f57d72, 0xfd1c2f611f63a3f0,
0xf24a01a73cf2dccf, 0xbc633b39673c8cec, 0x976e41088617ca01, 0xd5be0503e085d813, 0xbd49d14aa79dbc82, 0x4b2d8644d8a74e18, 0xec9c459d51852ba2, 0xddf8e7d60ed1219e,
0x93e1ab8252f33b45, 0xcabb90e5c942b503, 0xb8da1662e7b00a17, 0x3d6a751f3b936243, 0xe7109bfba19c0c9d, 0xcc512670a783ad4, 0x906a617d450187e2, 0x27fb2b80668b24c5,
0xb484f9dc9641e9da, 0xb1f9f660802dedf6, 0xe1a63853bbd26451, 0x5e7873f8a0396973, 0x8d07e33455637eb2, 0xdb0b487b6423e1e8, 0xb049dc016abc5e5f, 0x91ce1a9a3d2cda62,
0xdc5c5301c56b75f7, 0x7641a140cc7810fb, 0x89b9b3e11b6329ba, 0xa9e904c87fcb0a9d, 0xac2820d9623bf429, 0x546345fa9fbdcd44, 0xd732290fbacaf133, 0xa97c177947ad4095,
0x867f59a9d4bed6c0, 0x49ed8eabcccc485d, 0xa81f301449ee8c70, 0x5c68f256bfff5a74, 0xd226fc195c6a2f8c, 0x73832eec6fff3111, 0x83585d8fd9c25db7, 0xc831fd53c5ff7eab,
0xa42e74f3d032f525, 0xba3e7ca8b77f5e55, 0xcd3a1230c43fb26f, 0x28ce1bd2e55f35eb, 0x80444b5e7aa7cf85, 0x7980d163cf5b81b3, 0xa0555e361951c366, 0xd7e105bcc332621f,
0xc86ab5c39fa63440, 0x8dd9472bf3fefaa7, 0xfa856334878fc150, 0xb14f98f6f0feb951, 0x9c935e00d4b9d8d2, 0x6ed1bf9a569f33d3, 0xc3b8358109e84f07, 0xa862f80ec4700c8,
0xf4a642e14c6262c8, 0xcd27bb612758c0fa, 0x98e7e9cccfbd7dbd, 0x8038d51cb897789c, 0xbf21e44003acdd2c, 0xe0470a63e6bd56c3, 0xeeea5d5004981478, 0x1858ccfce06cac74,
0x95527a5202df0ccb, 0xf37801e0c43ebc8, 0xbaa718e68396cffd, 0xd30560258f54e6ba, 0xe950df20247c83fd, 0x47c6b82ef32a2069, 0x91d28b7416cdd27e, 0x4cdc331d57fa5441,
0xb6472e511c81471d, 0xe0133fe4adf8e952, 0xe3d8f9e563a198e5, 0x58180fddd97723a6, 0x8e679c2f5e44ff8f, 0x570f09eaa7ea7648,
};
/* Maximum mantissa for fast path: 2^53 */
#define MAX_MANTISSA_FAST_PATH 9007199254740992ULL /* 2^53 */
@ -159,6 +348,190 @@ static inline uint32_t parse_eight_digits_swar(uint64_t val) {
return (uint32_t)val;
}
/* ----------------------------------------------------------------------------
* Eisel-Lemire algorithm core (compute_float / am_to_double).
*
* Given a decimal mantissa `w` ( 19 digits, fits in uint64) and exponent `q`,
* compute the correctly-rounded `double` representing `w * 10^q`. Internally:
*
* 1. Shift `w` so its leading bit is set (full 64-bit mantissa).
* 2. Multiply by the 128-bit precomputed power-of-five entry above.
* 3. Extract the 53-bit mantissa from the high 64 bits of the product, with
* one extra bit for round-to-nearest-even.
* 4. Apply the round-half-to-even rule, including the rare power-of-2 tie
* case that needs a second-pass check.
*
* For the 19-digit / |q| 22 input range the result is provably bit-exact
* with strtod() (Mushtak & Lemire, "Fast Number Parsing Without Fallback").
* The caller falls back to strtod() if compute_float() signals indeterminate
* (we never trigger that branch with parse_number_string's bounded inputs).
*
* Ported from fast_float by Daniel Lemire & Joao Paulo Magalhaes
* (MIT-licensed, https://github.com/fastfloat/fast_float — decimal_to_binary.h
* and float_common.h). C++ template machinery dropped in favour of a
* double-only specialisation; struct layouts kept to ease future review.
* ---------------------------------------------------------------------------- */
/* IEEE-754 binary64 constants (mirrors fast_float's binary_format<double>). */
#define DOUBLE_MANTISSA_EXPLICIT_BITS 52
#define DOUBLE_MIN_EXPONENT_ROUND_EVEN -4
#define DOUBLE_MAX_EXPONENT_ROUND_EVEN 23
#define DOUBLE_MINIMUM_EXPONENT -1023
#define DOUBLE_INFINITE_POWER 0x7FF
/* 128-bit unsigned, little-endian: low holds bits [0..63]. */
typedef struct {
uint64_t low;
uint64_t high;
} value128;
/* Result of compute_float(): a 53-bit mantissa and a biased binary exponent.
* power2 < 0 signals indeterminate (caller should fall back to strtod()). */
typedef struct {
uint64_t mantissa;
int32_t power2;
} adjusted_mantissa;
/* `__builtin_clzll` is undefined on input 0 — caller guarantees v > 0. */
static inline int leading_zeroes_u64(uint64_t v) {
return __builtin_clzll(v);
}
/* 64x64 -> 128 multiplication. __uint128_t is available on every 64-bit
* target Redis supports (gated explicitly in the call site). */
static inline value128 full_multiplication(uint64_t a, uint64_t b) {
value128 r;
#ifdef __SIZEOF_INT128__
__uint128_t prod = (__uint128_t)a * (__uint128_t)b;
r.low = (uint64_t)prod;
r.high = (uint64_t)(prod >> 64);
#else
/* 32-bit fallback: split each operand into two 32-bit halves. */
uint64_t a_lo = (uint32_t)a, a_hi = a >> 32;
uint64_t b_lo = (uint32_t)b, b_hi = b >> 32;
uint64_t ll = a_lo * b_lo;
uint64_t lh = a_lo * b_hi;
uint64_t hl = a_hi * b_lo;
uint64_t hh = a_hi * b_hi;
uint64_t mid = (ll >> 32) + (uint32_t)lh + (uint32_t)hl;
r.low = (mid << 32) | (uint32_t)ll;
r.high = hh + (lh >> 32) + (hl >> 32) + (mid >> 32);
#endif
return r;
}
/* For q in (-400, 350), this approximates floor(log2(5^q)) + q + 63
* (or -ceil(log2(5^|q|)) + q + 63 for negative q). Used to derive power2. */
static inline int32_t eisel_lemire_power(int32_t q) {
return (((152170 + 65536) * q) >> 16) + 63;
}
/* 128-bit approximation of `w * 5^q`. The optional fixup multiplies by the
* second (extension) entry of the power-of-five table when the high half is
* close to a rounding boundary. Mathematical proof of sufficiency: see
* Mushtak & Lemire, "Fast Number Parsing Without Fallback". */
static inline value128 compute_product_approximation_d(int64_t q, uint64_t w) {
int index = 2 * (int)(q - EISEL_LEMIRE_SMALLEST_POWER_OF_FIVE);
value128 firstproduct = full_multiplication(w, power_of_five_128[index]);
/* For double, bit_precision = mantissa_explicit_bits (52) + 3 = 55. */
const uint64_t precision_mask =
(uint64_t)0xFFFFFFFFFFFFFFFFULL >> 55;
if ((firstproduct.high & precision_mask) == precision_mask) {
value128 secondproduct =
full_multiplication(w, power_of_five_128[index + 1]);
firstproduct.low += secondproduct.high;
if (secondproduct.high > firstproduct.low) {
firstproduct.high++;
}
}
return firstproduct;
}
/* Eisel-Lemire main: compute a correctly-rounded representation of w * 10^q.
* Returns an `adjusted_mantissa`. Special outputs:
* - mantissa == 0 && power2 == 0: result is +/-0
* - power2 == DOUBLE_INFINITE_POWER && mantissa == 0: result is infinity
* - power2 < 0: indeterminate (caller should fall back to strtod()). With
* parse_number_string()'s bounded mantissa (<= 19 digits), this branch
* is unreachable, but we keep the signature for safety.
*/
static adjusted_mantissa compute_float_d(int64_t q, uint64_t w) {
adjusted_mantissa answer;
if (w == 0 || q < EISEL_LEMIRE_SMALLEST_POWER_OF_FIVE) {
answer.power2 = 0;
answer.mantissa = 0;
return answer;
}
if (q > EISEL_LEMIRE_LARGEST_POWER_OF_FIVE) {
answer.power2 = DOUBLE_INFINITE_POWER;
answer.mantissa = 0;
return answer;
}
/* Renormalise w so its top bit is set. */
int lz = leading_zeroes_u64(w);
w <<= lz;
value128 product = compute_product_approximation_d(q, w);
int upperbit = (int)(product.high >> 63);
int shift = upperbit + 64 - DOUBLE_MANTISSA_EXPLICIT_BITS - 3;
answer.mantissa = product.high >> shift;
answer.power2 = (int32_t)(eisel_lemire_power((int32_t)q) + upperbit - lz - DOUBLE_MINIMUM_EXPONENT);
if (answer.power2 <= 0) {
/* Subnormal path. */
if (-answer.power2 + 1 >= 64) {
/* More than 64 bits below minimum exponent — definitely zero. */
answer.power2 = 0;
answer.mantissa = 0;
return answer;
}
/* Safe: -answer.power2 + 1 < 64. */
answer.mantissa >>= -answer.power2 + 1;
answer.mantissa += (answer.mantissa & 1); /* round up */
answer.mantissa >>= 1;
/* If post-rounding the value crosses back into the normal range, mark
* it normal (power2 = 1) rather than subnormal (power2 = 0). */
answer.power2 = (answer.mantissa < ((uint64_t)1 << DOUBLE_MANTISSA_EXPLICIT_BITS)) ? 0 : 1;
return answer;
}
/* Normal path: handle the round-half-to-even tie case. */
if ((product.low <= 1) &&
(q >= DOUBLE_MIN_EXPONENT_ROUND_EVEN) &&
(q <= DOUBLE_MAX_EXPONENT_ROUND_EVEN) &&
((answer.mantissa & 3) == 1)) {
if ((answer.mantissa << shift) == product.high) {
answer.mantissa &= ~(uint64_t)1; /* clear LSB so we round down */
}
}
answer.mantissa += (answer.mantissa & 1);
answer.mantissa >>= 1;
if (answer.mantissa >= ((uint64_t)2 << DOUBLE_MANTISSA_EXPLICIT_BITS)) {
answer.mantissa = (uint64_t)1 << DOUBLE_MANTISSA_EXPLICIT_BITS;
answer.power2++;
}
answer.mantissa &= ~((uint64_t)1 << DOUBLE_MANTISSA_EXPLICIT_BITS);
if (answer.power2 >= DOUBLE_INFINITE_POWER) {
answer.power2 = DOUBLE_INFINITE_POWER;
answer.mantissa = 0;
}
return answer;
}
/* Pack adjusted_mantissa back to a double via IEEE-754 bit layout. */
static inline double am_to_double(int negative, adjusted_mantissa am) {
uint64_t word = am.mantissa;
word |= (uint64_t)am.power2 << DOUBLE_MANTISSA_EXPLICIT_BITS;
if (negative) word |= (uint64_t)1 << 63;
double value;
memcpy(&value, &word, sizeof(value));
return value;
}
/* Parse a decimal number string into components.
* This follows the fast_float algorithm closely. */
static inline int parse_number_string(const char *p, const char *pend, double *result, const char **endptr) {
@ -261,65 +634,42 @@ static inline int parse_number_string(const char *p, const char *pend, double *r
if (digit_count > MAX_DIGITS) return 0;
}
/* Check if we're within fast path bounds */
if (exponent < MIN_EXPONENT_FAST_PATH) return 0;
if (exponent > MAX_EXPONENT_FAST_PATH) return 0;
/* Pick the conversion path. Two regimes:
* Clinger fast path: small mantissa (<= 2^53) and small |exp| (<= 22).
* One double multiply or divide; cheapest, exact by construction.
* Eisel-Lemire: large mantissa or wide exponent range (full double
* domain). Slightly slower per call (128-bit multiply + table lookup)
* but correctly-rounded by the Mushtak-Lemire proof.
* Inputs outside both ranges fall back to strtod() (caller of this fn). */
double value;
if (mantissa <= MAX_MANTISSA_FAST_PATH) {
if (mantissa <= MAX_MANTISSA_FAST_PATH &&
exponent >= MIN_EXPONENT_FAST_PATH &&
exponent <= MAX_EXPONENT_FAST_PATH)
{
/* Clinger fast path: all operands exact in double precision,
* single multiply/divide produces a correctly-rounded result. */
value = (double)mantissa;
if (exponent < 0) value = value / powers_of_ten[-exponent];
else if (exponent > 0) value = value * powers_of_ten[exponent];
if (negative) value = -value;
} else {
#ifdef __SIZEOF_INT128__
/* Widened fast path for 17-19 significant-digit mantissas.
*
* (double)mantissa alone loses up to 11 bits when mantissa > 2^53,
* so the existing Clinger path would yield up to 1 ULP vs strtod.
* We recover full precision by doing the multiply/divide in 128-bit
* integer arithmetic (correctly-rounded by construction). Cases
* outside the supported exponent range fall through to strtod.
*
* Requires __uint128_t (GCC/Clang builtin, available on every 64-bit
* target Redis supports). 32-bit builds take the strtod() fallback. */
if (exponent < -19 || exponent > 19) return 0;
/* Eisel-Lemire path. Replaces a previously hand-rolled widened branch
* (`(double)hi * 2^64 + (double)lo` shortcut) that produced ±1 ULP
* mismatches vs strtod() on inputs like 9007199255094284e-19 and
* 2489830482329185244e1. compute_float_d is bit-exact with strtod()
* for every input parse_number_string can produce. */
if (exponent < EISEL_LEMIRE_SMALLEST_POWER_OF_FIVE || exponent > EISEL_LEMIRE_LARGEST_POWER_OF_FIVE)
return 0;
if (exponent >= 0) {
/* (mantissa * 10^e) fits in 128 bits. Convert exactly: the
* single (double) cast from __uint128_t rounds to nearest. */
__uint128_t prod = (__uint128_t)mantissa * (uint64_t)powers_of_ten[exponent];
uint64_t hi = (uint64_t)(prod >> 64);
uint64_t lo = (uint64_t)prod;
/* (double)hi * 2^64 has no rounding error (hi up to 2^64-1 rounds
* once, then * 2^64 is exact). Adding lo rounds once. Total:
* matches strtod on every tested case with e in [0,19]. */
value = (double)hi * 18446744073709551616.0 + (double)lo;
} else {
/* mantissa / 10^|e|: scale numerator up by 2^64 before integer
* division to preserve precision, then descale by multiplying by
* 2^-64 (exact power-of-two scaling, does not round). The single
* (double) cast of the integer quotient produces IEEE round-to-
* nearest-even, matching strtod() bit-exactly for every tested
* 16-19 significant digit case. */
uint64_t divisor = (uint64_t)powers_of_ten[-exponent];
__uint128_t scaled = (__uint128_t)mantissa << 64;
__uint128_t q = scaled / divisor;
uint64_t hi = (uint64_t)(q >> 64);
uint64_t lo = (uint64_t)q;
value = ((double)hi * 18446744073709551616.0 + (double)lo)
* 5.421010862427522170037e-20; /* 2^-64 */
}
#else
/* 32-bit target without __uint128_t: fall through to the strtod()
* fallback. Correctness is preserved (it's the same path that shipped
* in 8.8-M02); only the perf gain is 64-bit-target-specific. */
return 0;
#endif
adjusted_mantissa am = compute_float_d(exponent, mantissa);
/* power2 < 0 would mean indeterminate (caller should fall back to
* strtod). With our bounded mantissa (<= 19 digits) this branch is
* unreachable per the Mushtak-Lemire proof, but we keep the guard so
* any future caller that supplies a larger mantissa stays correct. */
if (am.power2 < 0) return 0;
value = am_to_double(negative, am);
}
if (negative) value = -value;
*result = value;
return 1;
}
@ -524,9 +874,87 @@ int fastFloatTest(int argc, char **argv, int flags) {
/* Negative numbers exercising the widened path */
{"-0.49606648747577575", -0.49606648747577575},
{"-9007199254740993", -9007199254740992.0},
/* Eisel-Lemire rounding-boundary cases.
* Reported by @vitahlin on #14661 against the previous
* `(double)hi * 2^64 + (double)lo` widened branch which
* double-rounded the 128-bit product. Both must now match
* strtod() exactly. */
{"9007199255094284e-19", 9007199255094284e-19}, /* was -1 ULP */
{"2489830482329185244e1", 2489830482329185244e1}, /* was +1 ULP */
/* Subnormal boundaries (Eisel-Lemire's subnormal branch). */
{"5e-324", 5e-324}, /* smallest pos subnormal */
{"4.9e-324", 5e-324}, /* below half: rounds up */
{"2.2250738585072009e-308", 2.2250738585072009e-308}, /* largest subnormal */
{"2.2250738585072014e-308", 2.2250738585072014e-308}, /* smallest normal */
{"1e-323", 1e-323},
/* Round-half-to-even ties: post-Clinger range, hits compute_float_d
* tie path (product.low <= 1, q in [-4, 23], mantissa & 3 == 1). */
{"5497558138880", 5497558138880.0}, /* 2^42 + 2^33 boundary */
{"5e-22", 5e-22},
{"7.038531e-26", 7.038531e-26},
{"4503599627475501e-10", 4503599627475501e-10}, /* near 2^52 */
/* Largest finite double + overflow. */
{"1.7976931348623157e308", 1.7976931348623157e308}, /* DBL_MAX */
{"1.7976931348623158e308", 1.7976931348623157e308}, /* nearest is DBL_MAX */
{"1e308", 1e308},
/* Wide exponent range now reachable via Eisel-Lemire (previously
* fell to strtod). */
{"1.234567890123456e100", 1.234567890123456e100},
{"9.999999999999999e99", 9.999999999999999e99},
{"1e-300", 1e-300},
{"1.7e-300", 1.7e-300},
/* Repunit / many-9 mantissas — adjacent-double tie territory. */
{"9999999999999998", 9999999999999998.0},
{"99999999999999999", 1e17},
};
run_ff_tests(decimal_ok, COUNTOF(decimal_ok), 0);
/* Differential cross-check: every accepted input must produce the
* exact same bits as libc strtod(). Hand-picked hard cases covering
* every code path in compute_float_d (subnormal branch, round-half-
* to-even tie path, near-infinity, repunit mantissa, wide exponent). */
{
static const char *diff_inputs[] = {
/* Boundary classics around 2^53. */
"9007199254740992", "9007199254740993", "9007199254740994",
"9007199254740995", "9007199254740996",
/* Limits of finite double. */
"1.7976931348623157e308", "2.2250738585072014e-308",
"5e-324", "1e-323", "4.9406564584124654e-324",
/* The two reproducer inputs the previous widened branch missed. */
"9007199255094284e-19", "2489830482329185244e1",
/* Mushtak-Lemire stress range — 19-digit mantissas. */
"1234567890123456789e0", "1234567890123456789e-5",
"1234567890123456789e5", "9999999999999999e19",
/* Common scientific constants — mid-exponent sanity. */
"3.141592653589793", "2.718281828459045",
"1.4142135623730951e150", "6.022140857e23",
"1.602176634e-19", "9.10938356e-31",
};
for (int i = 0; i < COUNTOF(diff_inputs); i++) {
const char *s = diff_inputs[i];
char *fend, *lend;
errno = 0;
double got = fast_float_strtod(s, strlen(s), &fend);
errno = 0;
double libc = strtod(s, &lend);
uint64_t gb, lb;
memcpy(&gb, &got, sizeof(gb));
memcpy(&lb, &libc, sizeof(lb));
char descr[160];
snprintf(descr, sizeof(descr),
"differential vs strtod: \"%s\" ff=0x%016llx libc=0x%016llx",
s, (unsigned long long)gb, (unsigned long long)lb);
test_cond(descr, gb == lb);
}
}
/* No valid prefix for full buffer, or trailing junk. */
ff_testcase decimal_bad[] = {
{"1abc", 1.0},

View file

@ -9,6 +9,8 @@
#include "server.h"
#include <math.h>
#ifdef ENABLE_GCRA
/* GCRA algorithm for rate limiting.
* Implementation is heavily based on the implementation of (redis-cell)
* [https://github.com/brandur/redis-cell] by (brandur)[https://github.com/brandur].
@ -278,3 +280,5 @@ robj *gcraDup(robj *o) {
getLongLongFromGCRAObject(o, &val);
return createGCRAObject(val);
}
#endif /* ENABLE_GCRA */

View file

@ -13,11 +13,6 @@
#include "cluster.h"
#include <sys/resource.h>
static inline int nearestNextPowerOf2(unsigned int count) {
if (count <= 1) return 1;
return 1 << (32 - __builtin_clz(count-1));
}
/* Comparison function for qsort to sort slot indices */
static inline int slotCompare(const void *a, const void *b) {
return (*(const int *)a) - (*(const int *)b);

View file

@ -207,6 +207,9 @@ size_t lazyfreeGetFreeEffort(robj *key, robj *obj, int dbid) {
/* If the module's free_effort returns 0, we will use asynchronous free
* memory by default. */
return effort == 0 ? ULONG_MAX : effort;
} else if (obj->type == OBJ_ARRAY) {
redisArray *ar = obj->ptr;
return arCount(ar);
} else {
return 1; /* Everything else is a single allocation. */
}

View file

@ -4254,7 +4254,10 @@ int RM_KeyType(RedisModuleKey *key) {
case OBJ_HASH: return REDISMODULE_KEYTYPE_HASH;
case OBJ_MODULE: return REDISMODULE_KEYTYPE_MODULE;
case OBJ_STREAM: return REDISMODULE_KEYTYPE_STREAM;
#ifdef ENABLE_GCRA
case OBJ_GCRA: return REDISMODULE_KEYTYPE_GCRA;
#endif
case OBJ_ARRAY: return REDISMODULE_KEYTYPE_ARRAY;
default: return REDISMODULE_KEYTYPE_EMPTY;
}
}

View file

@ -1181,6 +1181,18 @@ void addReplyLongLongFromStr(client *c, robj *str) {
addReplyProto(c,"\r\n",2);
}
/* Reply with unsigned 64-bit value. Uses integer reply when value fits in
* signed long long, otherwise big number (RESP3) or bulk string (RESP2). */
void addReplyUnsignedLongLong(client *c, uint64_t v) {
if (v <= (uint64_t)LLONG_MAX) {
addReplyLongLong(c, (long long)v);
} else {
char buf[LONG_STR_SIZE];
int len = ull2string(buf, sizeof(buf), v);
addReplyBigNum(c, buf, len);
}
}
void addReplyAggregateLen(client *c, long length, int prefix) {
serverAssert(length >= 0);
if (_prepareClientToWrite(c) != C_OK) return;

View file

@ -37,10 +37,13 @@ int keyspaceEventsStringToFlags(char *classes) {
case 't': flags |= NOTIFY_STREAM; break;
case 'm': flags |= NOTIFY_KEY_MISS; break;
case 'd': flags |= NOTIFY_MODULE; break;
case 'a': flags |= NOTIFY_ARRAY; break;
case 'n': flags |= NOTIFY_NEW; break;
case 'o': flags |= NOTIFY_OVERWRITTEN; break;
case 'c': flags |= NOTIFY_TYPE_CHANGED; break;
#ifdef ENABLE_GCRA
case 'r': flags |= NOTIFY_RATE_LIMIT; break;
#endif
case 'S': flags |= NOTIFY_SUBKEYSPACE; break;
case 'T': flags |= NOTIFY_SUBKEYEVENT; break;
case 'I': flags |= NOTIFY_SUBKEYSPACEITEM; break;
@ -72,10 +75,13 @@ sds keyspaceEventsFlagsToString(int flags) {
if (flags & NOTIFY_EVICTED) res = sdscatlen(res,"e",1);
if (flags & NOTIFY_STREAM) res = sdscatlen(res,"t",1);
if (flags & NOTIFY_MODULE) res = sdscatlen(res,"d",1);
if (flags & NOTIFY_ARRAY) res = sdscatlen(res,"a",1);
if (flags & NOTIFY_NEW) res = sdscatlen(res,"n",1);
if (flags & NOTIFY_OVERWRITTEN) res = sdscatlen(res,"o",1);
if (flags & NOTIFY_TYPE_CHANGED) res = sdscatlen(res,"c",1);
#ifdef ENABLE_GCRA
if (flags & NOTIFY_RATE_LIMIT) res = sdscatlen(res,"r",1);
#endif
}
if (flags & NOTIFY_KEYSPACE) res = sdscatlen(res,"K",1);
if (flags & NOTIFY_KEYEVENT) res = sdscatlen(res,"E",1);

View file

@ -514,6 +514,7 @@ robj *createStreamObject(void) {
return o;
}
#ifdef ENABLE_GCRA
robj *createGCRAObject(long long value) {
/* NOTE: for 32-bit systems we can't use integer encoding (as OBJ_STRING does)
* as the GCRA object is a unixtime value in microseconds, which as of the
@ -530,6 +531,14 @@ robj *createGCRAObject(long long value) {
o->encoding = OBJ_ENCODING_INT;
return o;
}
#endif
robj *createArrayObject(void) {
redisArray *ar = arNew();
robj *o = createObject(OBJ_ARRAY, ar);
o->encoding = OBJ_ENCODING_SLICED_ARRAY;
return o;
}
robj *createModuleObject(moduleType *mt, void *value) {
moduleValue *mv = zmalloc(sizeof(*mv));
@ -603,6 +612,7 @@ void freeStreamObject(robj *o) {
freeStream(o->ptr);
}
#ifdef ENABLE_GCRA
void freeGCRAObject(robj *o) {
#if UINTPTR_MAX == 0xffffffff
zfree(o->ptr);
@ -610,6 +620,11 @@ void freeGCRAObject(robj *o) {
(void)o;
#endif
}
#endif
void freeArrayObject(robj *o) {
arFree(o->ptr);
}
void incrRefCount(robj *o) {
if (o->refcount < OBJ_FIRST_SPECIAL_REFCOUNT - 1) {
@ -662,7 +677,10 @@ void decrRefCount(robj *o) {
case OBJ_HASH: freeHashObject(o); break;
case OBJ_MODULE: freeModuleObject(o); break;
case OBJ_STREAM: freeStreamObject(o); break;
#ifdef ENABLE_GCRA
case OBJ_GCRA: freeGCRAObject(o); break;
#endif
case OBJ_ARRAY: freeArrayObject(o); break;
default: serverPanic("Unknown object type"); break;
}
}
@ -810,12 +828,19 @@ void dismissStreamObject(robj *o, size_t size_hint) {
}
}
/* See dismissObject() */
void dismissArrayObject(robj *o, size_t size_hint) {
arDismiss(o->ptr, size_hint);
}
#ifdef ENABLE_GCRA
void dismissGCRAObject(robj *o, size_t size_hint) {
/* GCRA is a single allocation of a long long thus way smaller than a
* page-size. The dismiss mechanism is not needed for it - hence NOOP.*/
(void)o;
(void)size_hint;
}
#endif
/* When creating a snapshot in a fork child process, the main process and child
* process share the same physical memory pages, and if / when the parent
@ -845,7 +870,10 @@ void dismissObject(robj *o, size_t size_hint) {
case OBJ_ZSET: dismissZsetObject(o, size_hint); break;
case OBJ_HASH: dismissHashObject(o, size_hint); break;
case OBJ_STREAM: dismissStreamObject(o, size_hint); break;
#ifdef ENABLE_GCRA
case OBJ_GCRA: dismissGCRAObject(o, size_hint); break;
#endif
case OBJ_ARRAY: dismissArrayObject(o, size_hint); break;
default: break;
}
#else
@ -967,7 +995,10 @@ size_t getObjectLength(robj *o) {
case OBJ_ZSET: return zsetLength(o);
case OBJ_HASH: return hashTypeLength(o, 0);
case OBJ_STREAM: return streamLength(o);
#ifdef ENABLE_GCRA
case OBJ_GCRA: return gcraObjectLength(o);
#endif
case OBJ_ARRAY: return arCount(o->ptr);
default: return 0;
}
}
@ -1176,6 +1207,7 @@ int getLongLongFromObject(robj *o, long long *target) {
return C_OK;
}
#ifdef ENABLE_GCRA
int getLongLongFromGCRAObject(robj *o, long long *target) {
long long res;
serverAssertWithInfo(NULL, o, o->type == OBJ_GCRA);
@ -1191,6 +1223,7 @@ int getLongLongFromGCRAObject(robj *o, long long *target) {
*target = res;
return C_OK;
}
#endif
int getLongLongFromObjectOrReply(client *c, robj *o, long long *target, const char *msg) {
long long value;
@ -1265,6 +1298,7 @@ char *strEncoding(int encoding) {
case OBJ_ENCODING_SKIPLIST: return "skiplist";
case OBJ_ENCODING_EMBSTR: return "embstr";
case OBJ_ENCODING_STREAM: return "stream";
case OBJ_ENCODING_SLICED_ARRAY: return "sliced-array";
default: return "unknown";
}
}
@ -1283,7 +1317,10 @@ size_t kvobjComputeSize(robj *key, kvobj *o, size_t sample_size, int dbid) {
o->type == OBJ_ZSET ||
o->type == OBJ_HASH ||
o->type == OBJ_STREAM ||
o->type == OBJ_GCRA)
#ifdef ENABLE_GCRA
o->type == OBJ_GCRA ||
#endif
o->type == OBJ_ARRAY)
{
return kvobjAllocSize(o);
} else if (o->type == OBJ_MODULE) {
@ -1309,14 +1346,20 @@ size_t kvobjAllocSize(kvobj *o) {
} else if (o->type == OBJ_STREAM) {
stream *s = o->ptr;
asize += s->alloc_size;
#ifdef ENABLE_GCRA
} else if (o->type == OBJ_GCRA) {
asize += gcraTypeAllocSize(o);
#endif
} else if (o->type == OBJ_ARRAY) {
redisArray *ar = o->ptr;
asize += ar->alloc_size;
} else if (o->type == OBJ_MODULE) {
/* TODO: Provide moduleGetAllocSize() module API for O(1) allocation size retrieval */
}
return asize;
}
#ifdef ENABLE_GCRA
size_t gcraTypeAllocSize(robj *o) {
(void)o;
#if UINTPTR_MAX == 0xffffffff
@ -1333,6 +1376,7 @@ size_t gcraObjectLength(robj *o) {
(void)o;
return 1;
}
#endif
/* Release data obtained with getMemoryOverheadData(). */
void freeMemoryOverheadData(struct redisMemOverhead *mh) {

View file

@ -5,7 +5,7 @@
* values of different logical types (strings, lists, sets, hashes, sorted sets,
* streams, modules, ...). It contains:
* - type: one of OBJ_STRING, OBJ_LIST, OBJ_SET, OBJ_ZSET, OBJ_HASH, OBJ_STREAM,
* OBJ_GCRA, OBJ_MODULE, ...
* OBJ_MODULE, ...
* - encoding: an implementation detail of how the value is represented in
* memory for the given type (see OBJ_ENCODING_* below). For example,
* strings may be RAW/EMBSTR/INT, sets may be INTSET or HT, etc.
@ -85,6 +85,7 @@ struct RedisModuleType;
#define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */
#define OBJ_ENCODING_LISTPACK 11 /* Encoded as a listpack */
#define OBJ_ENCODING_LISTPACK_EX 12 /* Encoded as listpack, extended with metadata */
#define OBJ_ENCODING_SLICED_ARRAY 13 /* Encoded as sliced array */
#define LRU_BITS 24
#define LRU_CLOCK_MAX ((1<<LRU_BITS)-1) /* Max value of obj->lru */
@ -163,6 +164,7 @@ robj *createZsetListpackObject(void);
robj *createStreamObject(void);
robj *createGCRAObject(long long value);
robj *createModuleObject(struct RedisModuleType *mt, void *value);
robj *createArrayObject(void);
int getLongFromObjectOrReply(struct client *c, robj *o, long *target, const char *msg);
int getPositiveLongFromObjectOrReply(struct client *c, robj *o, long *target, const char *msg);
int getRangeLongFromObjectOrReply(struct client *c, robj *o, long min, long max, long *target, const char *msg);

267
src/rdb.c
View file

@ -124,33 +124,42 @@ time_t rdbLoadTime(rio *rdb) {
return (time_t)t32;
}
ssize_t rdbSaveMillisecondTime(rio *rdb, long long t) {
int64_t t64 = (int64_t) t;
memrev64ifbe(&t64); /* Store in little endian. */
return rdbWriteRaw(rdb,&t64,8);
/* Save a signed 64-bit integer in little-endian format. */
ssize_t rdbSaveSignedInteger(rio *rdb, int64_t val) {
memrev64ifbe(&val); /* Store in little endian. */
return rdbWriteRaw(rdb, &val, 8);
}
/* This function loads a time from the RDB file. It gets the version of the
* RDB because, unfortunately, before Redis 5 (RDB version 9), the function
* failed to convert data to/from little endian, so RDB files with keys having
* expires could not be shared between big endian and little endian systems
* (because the expire time will be totally wrong). The fix for this is just
* to call memrev64ifbe(), however if we fix this for all the RDB versions,
/* This function loads a signed 64-bit integer from the RDB file. It gets the
* version of the RDB because, unfortunately, before Redis 5 (RDB version 9),
* the function failed to convert data to/from little endian, so RDB files with
* keys having expires could not be shared between big endian and little endian
* systems (because the expire time will be totally wrong). The fix for this is
* just to call memrev64ifbe(), however if we fix this for all the RDB versions,
* this call will introduce an incompatibility for big endian systems:
* after upgrading to Redis version 5 they will no longer be able to load their
* own old RDB files. Because of that, we instead fix the function only for new
* RDB versions, and load older RDB versions as we used to do in the past,
* allowing big endian systems to load their own old RDB files.
*
* On I/O error the function returns LLONG_MAX, however if this is also a
* On I/O error the function returns INT64_MAX, however if this is also a
* valid stored value, the caller should use rioGetReadError() to check for
* errors after calling this function. */
long long rdbLoadMillisecondTime(rio *rdb, int rdbver) {
int64_t t64;
if (rioRead(rdb,&t64,8) == 0) return LLONG_MAX;
int64_t rdbLoadSignedInteger(rio *rdb, int rdbver) {
int64_t val;
if (rioRead(rdb, &val, 8) == 0) return INT64_MAX;
if (rdbver >= 9) /* Check the top comment of this function. */
memrev64ifbe(&t64); /* Convert in big endian if the system is BE. */
return (long long)t64;
memrev64ifbe(&val); /* Convert in big endian if the system is BE. */
return val;
}
/* Wrappers for millisecond time - these just call the signed integer functions */
ssize_t rdbSaveMillisecondTime(rio *rdb, long long t) {
return rdbSaveSignedInteger(rdb, (int64_t)t);
}
long long rdbLoadMillisecondTime(rio *rdb, int rdbver) {
return (long long)rdbLoadSignedInteger(rdb, rdbver);
}
/* Saves an encoded length. The first two bits in the first byte are used to
@ -714,10 +723,14 @@ int rdbSaveObjectType(rio *rdb, robj *o) {
serverPanic("Unknown hash encoding");
case OBJ_STREAM:
return rdbSaveType(rdb,RDB_TYPE_STREAM_LISTPACKS_5);
#ifdef ENABLE_GCRA
case OBJ_GCRA:
return rdbSaveType(rdb,RDB_TYPE_GCRA);
#endif
case OBJ_MODULE:
return rdbSaveType(rdb,RDB_TYPE_MODULE_2);
case OBJ_ARRAY:
return rdbSaveType(rdb,RDB_TYPE_ARRAY);
default:
serverPanic("Unknown object type");
}
@ -1040,6 +1053,68 @@ size_t rdbSaveStreamConsumers(rio *rdb, streamCG *cg) {
/* Save a Redis object.
* Returns -1 on error, number of bytes written on success. */
static ssize_t rdbSaveArrayElement(rio *rdb, uint64_t idx, void *v) {
ssize_t n, nwritten = 0;
if ((n = rdbSaveLen(rdb, idx)) == -1) return -1;
nwritten += n;
if (arIsInt(v)) {
if ((n = rdbSaveLen(rdb, AR_RDB_TAG_INT)) == -1) return -1;
nwritten += n;
int64_t ival = arToInt(v);
if ((n = rdbSaveSignedInteger(rdb, ival)) == -1) return -1;
nwritten += n;
} else if (arIsFloat(v)) {
if ((n = rdbSaveLen(rdb, AR_RDB_TAG_FLOAT)) == -1) return -1;
nwritten += n;
double d = arToDouble(v);
if (rdbSaveBinaryDoubleValue(rdb, d) == -1) return -1;
nwritten += 8;
} else if (arIsSmallStr(v)) {
char buf[AR_SMALLSTR_MAXLEN + 1];
int len = arToSmallStr(v, buf);
if ((n = rdbSaveLen(rdb, AR_RDB_TAG_SMALLSTR)) == -1) return -1;
nwritten += n;
if ((n = rdbSaveRawString(rdb, (unsigned char *)buf, len)) == -1) return -1;
nwritten += n;
} else {
if ((n = rdbSaveLen(rdb, AR_RDB_TAG_SDS)) == -1) return -1;
nwritten += n;
if ((n = rdbSaveRawString(rdb, (unsigned char *)arStringData(v), arStringLen(v))) == -1) return -1;
nwritten += n;
}
return nwritten;
}
static ssize_t rdbSaveArraySlice(rio *rdb, arSlice *s, uint64_t slice_id,
uint32_t slice_size) {
ssize_t n, nwritten = 0;
if (s->encoding == AR_SLICE_DENSE) {
for (uint32_t i = 0; i < s->layout.dense.winsize; i++) {
void *v = s->layout.dense.items[i];
if (arIsEmpty(v)) continue;
uint64_t idx = arMakeIdx(slice_id, s->layout.dense.offset + i, slice_size);
if ((n = rdbSaveArrayElement(rdb, idx, v)) == -1) return -1;
nwritten += n;
}
} else {
uint16_t *offsets = s->layout.sparse.offsets;
void **values = s->layout.sparse.values;
for (uint32_t i = 0; i < s->count; i++) {
uint64_t idx = arMakeIdx(slice_id, offsets[i], slice_size);
if ((n = rdbSaveArrayElement(rdb, idx, values[i])) == -1) return -1;
nwritten += n;
}
}
return nwritten;
}
ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key, int dbid) {
ssize_t n = 0, nwritten = 0;
@ -1402,11 +1477,13 @@ ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key, int dbid) {
/* Save the all-time count of duplicate IIDs detected. */
if ((n = rdbSaveLen(rdb,s->iids_duplicates)) == -1) return -1;
nwritten += n;
#ifdef ENABLE_GCRA
} else if (o->type == OBJ_GCRA) {
long long t;
getLongLongFromGCRAObject(o, &t);
if ((n = rdbSaveLen(rdb,t)) == -1) return -1;
nwritten += n;
#endif
} else if (o->type == OBJ_MODULE) {
/* Save a module-specific value. */
RedisModuleIO io;
@ -1433,6 +1510,57 @@ ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key, int dbid) {
zfree(io.ctx);
}
return io.error ? -1 : (ssize_t)io.bytes;
} else if (o->type == OBJ_ARRAY) {
/* Save an array value. We persist only elements and insert_idx - no
* implementation details like slice_size. Arrays are loaded using
* the current ar_slice_size config. */
redisArray *ar = o->ptr;
/* Save count */
if ((n = rdbSaveLen(rdb, ar->count)) == -1) return -1;
nwritten += n;
/* Save insert_idx: 0 = none, 1 = has value followed by actual value.
* We can't save UINT64_MAX directly with rdbSaveLen/rdbLoadLen because
* rdbLoadLen returns UINT64_MAX (RDB_LENERR) to signal an error, making
* it impossible to distinguish a valid UINT64_MAX value from an error. */
if (ar->insert_idx == AR_INSERT_IDX_NONE) {
if ((n = rdbSaveLen(rdb, 0)) == -1) return -1;
nwritten += n;
} else {
if ((n = rdbSaveLen(rdb, 1)) == -1) return -1;
nwritten += n;
if ((n = rdbSaveLen(rdb, ar->insert_idx)) == -1) return -1;
nwritten += n;
}
/* Save elements in index order.
* We need to iterate through all slices, handling both flat directory
* mode and superdir mode. In superdir mode, blocks are sorted by
* block_id, so we iterate through blocks in order. */
if (ar->superdir) {
/* Superdir mode: iterate through blocks */
for (uint32_t bi = 0; bi < ar->sdir_len; bi++) {
arSDirEntry *e = ar->superdir + bi;
uint64_t block_base = e->block_id * AR_SUPER_BLOCK_SLOTS;
for (uint32_t si = 0; si < AR_SUPER_BLOCK_SLOTS; si++) {
arSlice *s = e->slots[si];
if (!s) continue;
uint64_t slice_id = block_base + si;
if ((n = rdbSaveArraySlice(rdb, s, slice_id, ar->slice_size)) == -1) return -1;
nwritten += n;
}
}
} else {
/* Flat directory mode */
for (uint64_t slice_id = 0; slice_id <= ar->dir_highest_used && slice_id < ar->dir_alloc; slice_id++) {
arSlice *s = ar->dir[slice_id];
if (!s) continue;
if ((n = rdbSaveArraySlice(rdb, s, slice_id, ar->slice_size)) == -1) return -1;
nwritten += n;
}
}
} else {
serverPanic("Unknown object type");
}
@ -2935,13 +3063,13 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error)
/* search for duplicate records */
sds field = sdstrynewlen(fstr, flen);
int field_added = (field != NULL && dictAdd(dupSearchDict, field, NULL) == DICT_OK);
if (!field_added || !lpSafeToAdd(lp, (size_t)flen + vlen)) {
if (!field || !lpSafeToAdd(lp, (size_t)flen + vlen) ||
dictAdd(dupSearchDict, field, NULL) != DICT_OK) {
rdbReportCorruptRDB("Hash zipmap with dup elements, or big length (%u)", flen);
/* If field was not added to dict, we still own it.
* If it was added, dict owns it and dictRelease will free it. */
if (!field_added) sdsfree(field);
dictRelease(dupSearchDict);
sdsfree(field);
lpFree(lp);
zfree(encoded);
o->ptr = NULL;
@ -3550,7 +3678,6 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error)
rdbReportCorruptRDB("Duplicated consumer PEL entry "
" loading a stream consumer "
"group");
streamFreeNACK(s, nack);
decrRefCount(o);
return NULL;
}
@ -3736,6 +3863,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error)
return NULL;
}
o = createModuleObject(mt, ptr);
#ifdef ENABLE_GCRA
} else if (rdbtype == RDB_TYPE_GCRA) {
uint64_t time = rdbLoadLen(rdb, NULL);
if (time == RDB_LENERR || time > LLONG_MAX) {
@ -3743,6 +3871,105 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error)
return NULL;
}
o = createGCRAObject((long long)time);
#endif
} else if (rdbtype == RDB_TYPE_ARRAY) {
/* Load array value. We only persist elements and insert_idx - no
* implementation details. Arrays use current ar_slice_size config. */
uint64_t count;
if ((count = rdbLoadLen(rdb, NULL)) == RDB_LENERR) return NULL;
if (count == 0) {
rdbReportCorruptRDB("Empty array (count == 0) is invalid");
return NULL;
}
/* Load insert_idx: 0 = none, 1 = has value followed by actual value */
uint64_t insert_idx_flag;
if ((insert_idx_flag = rdbLoadLen(rdb, NULL)) == RDB_LENERR) return NULL;
if (insert_idx_flag > 1) {
rdbReportCorruptRDB("Invalid array insert_idx_flag %llu",
(unsigned long long)insert_idx_flag);
return NULL;
}
uint64_t insert_idx;
if (insert_idx_flag == 0) {
insert_idx = AR_INSERT_IDX_NONE;
} else {
if ((insert_idx = rdbLoadLen(rdb, NULL)) == RDB_LENERR) return NULL;
}
o = createArrayObject();
redisArray *ar = o->ptr;
ar->insert_idx = insert_idx;
/* Load elements */
for (uint64_t i = 0; i < count; i++) {
uint64_t idx;
int idx_isencoded;
if (rdbLoadLenByRef(rdb, &idx_isencoded, &idx) == -1) {
decrRefCount(o);
return NULL;
}
if (idx_isencoded || idx == UINT64_MAX) {
decrRefCount(o);
rdbReportCorruptRDB("Invalid array index %llu",
(unsigned long long)idx);
return NULL;
}
uint64_t type_tag;
if ((type_tag = rdbLoadLen(rdb, NULL)) == RDB_LENERR) {
decrRefCount(o);
return NULL;
}
void *v;
if (type_tag == AR_RDB_TAG_INT) {
int64_t ival = rdbLoadSignedInteger(rdb, RDB_VERSION);
if (ival == INT64_MAX && rioGetReadError(rdb)) {
decrRefCount(o);
return NULL;
}
v = arValueFromRdbInt(ival);
} else if (type_tag == AR_RDB_TAG_FLOAT) {
double d;
if (rdbLoadBinaryDoubleValue(rdb, &d) == -1) {
decrRefCount(o);
return NULL;
}
v = arValueFromRdbFloat(d);
} else if (type_tag == AR_RDB_TAG_SMALLSTR) {
sds str;
if ((str = rdbGenericLoadStringObject(rdb, RDB_LOAD_SDS, NULL)) == NULL) {
decrRefCount(o);
return NULL;
}
size_t len = sdslen(str);
if (len > AR_SMALLSTR_MAXLEN) {
sdsfree(str);
decrRefCount(o);
rdbReportCorruptRDB("Invalid small string length %zu in array", len);
return NULL;
}
v = arValueFromRdbSmallStr(str, sdslen(str));
sdsfree(str);
} else if (type_tag == AR_RDB_TAG_SDS) {
/* arString */
sds str;
if ((str = rdbGenericLoadStringObject(rdb, RDB_LOAD_SDS, NULL)) == NULL) {
decrRefCount(o);
return NULL;
}
v = arEncode(str, sdslen(str));
sdsfree(str);
} else {
decrRefCount(o);
rdbReportCorruptRDB("Unknown array element type_tag %llu",
(unsigned long long)type_tag);
return NULL;
}
arSet(ar, idx, v);
}
} else {
rdbReportReadError("Unknown RDB encoding type %d",rdbtype);
return NULL;

View file

@ -80,11 +80,18 @@
#define RDB_TYPE_HASH_LISTPACK_EX 25 /* Hash LP with HFEs. Attach min TTL at start */
#define RDB_TYPE_STREAM_LISTPACKS_4 26 /* Stream with IDMP support */
#define RDB_TYPE_STREAM_LISTPACKS_5 27 /* Stream with XNACK support (NACKed entries) */
#define RDB_TYPE_GCRA 28 /* GCRA object */
#define RDB_TYPE_ARRAY 28 /* Array data type */
#ifdef ENABLE_GCRA
#define RDB_TYPE_GCRA 29 /* GCRA object */
#endif
/* NOTE: WHEN ADDING NEW RDB TYPE, UPDATE rdbIsObjectType(), and rdb_type_string[] */
/* Test if a type is an object type. */
#ifdef ENABLE_GCRA
#define rdbIsObjectType(t) (((t) >= 0 && (t) <= 7) || ((t) >= 9 && (t) <= 29))
#else
#define rdbIsObjectType(t) (((t) >= 0 && (t) <= 7) || ((t) >= 9 && (t) <= 28))
#endif
/* Special RDB opcodes (saved/loaded with rdbSaveType/rdbLoadType). */
#define RDB_OPCODE_KEY_META 243 /* Key metadata (module metadata classes). */
@ -133,6 +140,8 @@ int rdbSaveType(rio *rdb, unsigned char type);
int rdbLoadType(rio *rdb);
time_t rdbLoadTime(rio *rdb);
int rdbSaveLen(rio *rdb, uint64_t len);
ssize_t rdbSaveSignedInteger(rio *rdb, int64_t val);
int64_t rdbLoadSignedInteger(rio *rdb, int rdbver);
ssize_t rdbSaveMillisecondTime(rio *rdb, long long t);
long long rdbLoadMillisecondTime(rio *rdb, int rdbver);
uint64_t rdbLoadLen(rio *rdb, int *isencoded);

View file

@ -88,7 +88,10 @@ char *rdb_type_string[] = {
"hash-listpack-md",
"stream-v4",
"stream-v5",
"array",
#ifdef ENABLE_GCRA
"gcra",
#endif
};
/* Show a few stats collected into 'rdbstate' */

View file

@ -89,7 +89,7 @@ typedef long long ustime_t;
#define REDISMODULE_KEYTYPE_ZSET 5
#define REDISMODULE_KEYTYPE_MODULE 6
#define REDISMODULE_KEYTYPE_STREAM 7
#define REDISMODULE_KEYTYPE_GCRA 8
#define REDISMODULE_KEYTYPE_ARRAY 8
/* Reply types. */
#define REDISMODULE_REPLY_UNKNOWN -1
@ -248,24 +248,31 @@ This flag should not be used directly by the module.
#define REDISMODULE_NOTIFY_OVERWRITTEN (1<<15) /* o, key overwrite notification */
#define REDISMODULE_NOTIFY_TYPE_CHANGED (1<<16) /* c, key type changed notification */
#define REDISMODULE_NOTIFY_KEY_TRIMMED (1<<17) /* module only key space notification, indicates a key trimmed during slot migration */
#define REDISMODULE_NOTIFY_RATE_LIMIT (1<<18) /* r, rate limit event */
#define REDISMODULE_NOTIFY_SUBKEYSPACE (1<<19) /* S */
#define REDISMODULE_NOTIFY_SUBKEYEVENT (1<<20) /* T */
#define REDISMODULE_NOTIFY_SUBKEYSPACEITEM (1<<21) /* I */
#define REDISMODULE_NOTIFY_SUBKEYSPACEEVENT (1<<22) /* V */
#define REDISMODULE_NOTIFY_ARRAY (1<<23) /* a, array key space notification */
#ifdef ENABLE_GCRA
#define REDISMODULE_NOTIFY_RATE_LIMIT (1<<24) /* r, rate limit event */
#endif
/* Next notification flag, must be updated when adding new flags above!
This flag should not be used directly by the module.
* Use RedisModule_GetKeyspaceNotificationFlagsAll instead. */
#define _REDISMODULE_NOTIFY_NEXT (1<<23)
#ifdef ENABLE_GCRA
#define _REDISMODULE_NOTIFY_NEXT (1<<25)
#else
#define _REDISMODULE_NOTIFY_NEXT (1<<24)
#endif
/* Delivery flags for RM_SubscribeToKeyspaceEventsWithSubkeys.
* These are passed in the 'flags' parameter, not in 'types'. */
#define REDISMODULE_NOTIFY_FLAG_NONE 0 /* Invoke callback for all matching events */
#define REDISMODULE_NOTIFY_FLAG_SUBKEYS_REQUIRED (1<<0) /* Only invoke callback when subkeys are present */
#define REDISMODULE_NOTIFY_ALL (REDISMODULE_NOTIFY_GENERIC | REDISMODULE_NOTIFY_STRING | REDISMODULE_NOTIFY_LIST | REDISMODULE_NOTIFY_SET | REDISMODULE_NOTIFY_HASH | REDISMODULE_NOTIFY_ZSET | REDISMODULE_NOTIFY_EXPIRED | REDISMODULE_NOTIFY_EVICTED | REDISMODULE_NOTIFY_STREAM | REDISMODULE_NOTIFY_MODULE) /* A */
#define REDISMODULE_NOTIFY_ALL (REDISMODULE_NOTIFY_GENERIC | REDISMODULE_NOTIFY_STRING | REDISMODULE_NOTIFY_LIST | REDISMODULE_NOTIFY_SET | REDISMODULE_NOTIFY_HASH | REDISMODULE_NOTIFY_ZSET | REDISMODULE_NOTIFY_EXPIRED | REDISMODULE_NOTIFY_EVICTED | REDISMODULE_NOTIFY_STREAM | REDISMODULE_NOTIFY_MODULE | REDISMODULE_NOTIFY_ARRAY) /* A */
/* A special pointer that we can use between the core and the module to signal
* field deletion, and that is impossible to be a valid pointer. */

View file

@ -2251,6 +2251,11 @@ void replicationAttachToNewMaster(void) {
/* Asynchronously read the SYNC payload we receive from a master */
#define REPL_MAX_WRITTEN_BEFORE_FSYNC (1024*1024*8) /* 8 MB */
void readSyncBulkPayload(connection *conn) {
/* During full sync, the functions engine is freed right before loading
* the RDB. To avoid this happening while a function is still running,
* delay full sync processing until it finishes. */
if (isInsideYieldingLongCommand()) return;
char buf[PROTO_IOBUF_LEN];
ssize_t nread, readlen, nwritten;
int use_diskless_load = useDisklessLoad();

View file

@ -105,7 +105,14 @@ sds _sdsnewlen(const void *init, size_t initlen, int trymalloc) {
int hdrlen = sdsHdrSize(type);
size_t bufsize;
assert(initlen + hdrlen + 1 > initlen); /* Catch size_t overflow */
if (trymalloc) {
/* protect against size_t overflow */
if (initlen + hdrlen + 1 <= initlen)
return NULL;
} else {
assert(initlen + hdrlen + 1 > initlen); /* Catch size_t overflow */
}
sh = trymalloc?
s_trymalloc_usable(hdrlen+initlen+1, &bufsize) :
s_malloc_usable(hdrlen+initlen+1, &bufsize);

View file

@ -22,6 +22,7 @@
#include "atomicvar.h"
#include "commands.h"
#include "object.h"
#include "sparsearray.h"
#include <stdio.h>
#include <stdlib.h>
@ -287,7 +288,10 @@ extern int configOOMScoreAdjValuesDefaults[CONFIG_OOM_COUNT];
#define ACL_CATEGORY_CONNECTION (1ULL<<18)
#define ACL_CATEGORY_TRANSACTION (1ULL<<19)
#define ACL_CATEGORY_SCRIPTING (1ULL<<20)
#define ACL_CATEGORY_RATE_LIMIT (1ULL<<21)
#define ACL_CATEGORY_ARRAY (1ULL<<21)
#ifdef ENABLE_GCRA
#define ACL_CATEGORY_RATE_LIMIT (1ULL<<22)
#endif
/* Key-spec flags *
* -------------- */
@ -796,12 +800,15 @@ typedef enum {
#define NOTIFY_OVERWRITTEN (1<<15) /* o, key overwrite notification (Note: excluded from NOTIFY_ALL) */
#define NOTIFY_TYPE_CHANGED (1<<16) /* c, key type changed notification (Note: excluded from NOTIFY_ALL) */
#define NOTIFY_KEY_TRIMMED (1<<17) /* module only key space notification, indicates a key trimmed during slot migration */
#define NOTIFY_RATE_LIMIT (1<<18) /* r, notify rate limit event (Note: excluded from NOTIFY_ALL)*/
#define NOTIFY_SUBKEYSPACE (1<<19) /* S, subkey-level keyspace notification */
#define NOTIFY_SUBKEYEVENT (1<<20) /* T, subkey-level keyevent notification */
#define NOTIFY_SUBKEYSPACEITEM (1<<21) /* I, subkey-level notification per item: channel=key\nsubkey */
#define NOTIFY_SUBKEYSPACEEVENT (1<<22) /* V, subkey-level notification: channel=event|key */
#define NOTIFY_ALL (NOTIFY_GENERIC | NOTIFY_STRING | NOTIFY_LIST | NOTIFY_SET | NOTIFY_HASH | NOTIFY_ZSET | NOTIFY_EXPIRED | NOTIFY_EVICTED | NOTIFY_STREAM | NOTIFY_MODULE) /* A flag */
#define NOTIFY_ARRAY (1<<23) /* a, array notification */
#ifdef ENABLE_GCRA
#define NOTIFY_RATE_LIMIT (1<<24) /* r, notify rate limit event (Note: excluded from NOTIFY_ALL)*/
#endif
#define NOTIFY_ALL (NOTIFY_GENERIC | NOTIFY_STRING | NOTIFY_LIST | NOTIFY_SET | NOTIFY_HASH | NOTIFY_ZSET | NOTIFY_EXPIRED | NOTIFY_EVICTED | NOTIFY_STREAM | NOTIFY_MODULE | NOTIFY_ARRAY) /* A flag */
/* Using the following macro you can run code inside serverCron() with the
* specified period, specified in milliseconds.
@ -863,10 +870,18 @@ typedef enum {
* by a 64 bit module type ID, which has a 54 bits module-specific signature
* in order to dispatch the loading to the right module, plus a 10 bits
* encoding version. */
/* Code related to GCRA is disabled by default.
* Build with -DENABLE_GCRA to compile it back in. */
#define OBJ_MODULE 5 /* Module object. */
#define OBJ_STREAM 6 /* Stream object. */
#define OBJ_GCRA 7 /* GCRA object. */
#define OBJ_ARRAY 7 /* Array object. */
#ifdef ENABLE_GCRA
#define OBJ_GCRA 8 /* GCRA object. */
#define OBJ_TYPE_MAX 9 /* Maximum number of object types */
#else
#define OBJ_TYPE_MAX 8 /* Maximum number of object types */
#endif
/* NOTE: adding a new object requires changes in the following places:
* - rdb.c - save/load (also bump RDB_VERSION if needed)
@ -2442,6 +2457,10 @@ struct redisServer {
/* Stream IDMP parameters */
long long stream_idmp_duration; /* Default IDMP duration in seconds. */
long long stream_idmp_maxsize; /* Default IDMP max entries. */
/* Array parameters */
uint32_t array_slice_size; /* Slice size for new arrays */
uint32_t array_sparse_kmax; /* Max elements before sparse->dense */
uint32_t array_sparse_kmin; /* Min elements before dense->sparse */
/* List parameters */
int list_max_listpack_size;
int list_compress_depth;
@ -2801,8 +2820,11 @@ typedef enum {
COMMAND_GROUP_GEO,
COMMAND_GROUP_STREAM,
COMMAND_GROUP_BITMAP,
COMMAND_GROUP_ARRAY,
COMMAND_GROUP_MODULE,
#ifdef ENABLE_GCRA
COMMAND_GROUP_RATE_LIMIT,
#endif
} redisCommandGroup;
typedef void redisCommandProc(client *c);
@ -3213,6 +3235,7 @@ void addReplyBigNum(client *c, const char *num, size_t len);
void addReplyHumanLongDouble(client *c, long double d);
void addReplyLongLong(client *c, long long ll);
void addReplyLongLongFromStr(client *c, robj* str);
void addReplyUnsignedLongLong(client *c, uint64_t v);
void addReplyArrayLen(client *c, long length);
void addReplyMapLen(client *c, long length);
void addReplySetLen(client *c, long length);
@ -3844,6 +3867,9 @@ struct listpackEx *listpackExCreate(void);
void listpackExAddNew(robj *o, char *field, size_t flen,
char *value, size_t vlen, uint64_t expireAt);
/* Array data type. */
robj *arrayTypeDup(robj *o);
/* Pub / Sub */
int pubsubUnsubscribeAllChannels(client *c, int notify);
int pubsubUnsubscribeShardAllChannels(client *c, int notify);
@ -4511,6 +4537,26 @@ void digestCommand(client *c);
void gcraCommand(client *c);
void gcraSetValueCommand(client *c);
/* Array commands (t_array.c) */
void arsetCommand(client *c);
void argetCommand(client *c);
void ardelCommand(client *c);
void ardelrangeCommand(client *c);
void arlenCommand(client *c);
void arcountCommand(client *c);
void argetrangeCommand(client *c);
void arscanCommand(client *c);
void argrepCommand(client *c);
void aropCommand(client *c);
void arinsertCommand(client *c);
void arringCommand(client *c);
void arnextCommand(client *c);
void arseekCommand(client *c);
void arlastitemsCommand(client *c);
void arinfoCommand(client *c);
void armsetCommand(client *c);
void armgetCommand(client *c);
#if defined(__GNUC__)
void *calloc(size_t count, size_t size) __attribute__ ((deprecated));
void free(void *ptr) __attribute__ ((deprecated));

2080
src/sparsearray.c Normal file

File diff suppressed because it is too large Load diff

312
src/sparsearray.h Normal file
View file

@ -0,0 +1,312 @@
/*
* Copyright (c) 2026-Present, Redis Ltd.
* All rights reserved.
*
* Licensed under your choice of (a) the Redis Source Available License 2.0
* (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the
* GNU Affero General Public License v3 (AGPLv3).
*
* Sparse Array - A memory-efficient sparse array with 64-bit index space.
*
* This data structure was designed and implemented by Salvatore Sanfilippo.
*/
#ifndef __SPARSEARRAY_H
#define __SPARSEARRAY_H
#include <stdint.h>
#include <stddef.h>
#include <string.h>
/* ============================================================================
* SPARSE ARRAY OVERVIEW
* ============================================================================
*
* Sparse arrays are random-access sequences indexed by non-negative 64-bit
* integers. They support O(1) get/set operations and efficient iteration.
*
* MEMORY LAYOUT
* -------------
* The array uses a two-level structure: a directory pointing to "slices",
* which contain just a range of elements. For very large/sparse arrays, a
* three-level "superdir" structure is used.
*
* SLICE TYPES
* -----------
* Each slice holds up to slice_size elements and can be:
*
* - Sparse: Sorted array of (offset, value) pairs. Memory-efficient when
* elements are scattered within the slice.
*
* - Dense: Contiguous array with a sliding window. Used when the slice
* has many elements.
*
* VALUE ENCODING (Tagged Pointers)
* --------------------------------
* Values are stored in tagged pointer-sized words, using the low 2 bits as a
* tag. The exact immediate encoding depends on pointer width:
*
* 64-bit builds:
* Tag 00: arString pointer (heap-allocated, 8+ byte strings)
* Tag 01: Immediate signed integer in the 62-bit payload
* Tag 10: Immediate double (low 2 bits of the IEEE-754 payload cleared)
* Tag 11: Inline small string (0-7 bytes)
*
* 32-bit builds:
* Tag 00: arString pointer
* Tag 01: Immediate signed integer in the 30-bit payload
* Tag 10: Immediate float (low 2 bits of the IEEE-754 payload cleared)
* Tag 11: Inline small string (0-3 bytes)
*
* RDB persistence is architecture-neutral: values are saved as logical ints,
* doubles and strings, never as raw tagged words.
* ========================================================================== */
/* ----------------------------------------------------------------------------
* Configuration defaults
* -------------------------------------------------------------------------- */
#define AR_SLICE_SIZE_DEFAULT 4096
#define AR_SLICE_SIZE_MIN 256
#define AR_SLICE_SIZE_MAX 65536
#define AR_SPARSE_KMAX_DEFAULT 10
#define AR_SPARSE_KMIN_DEFAULT 5
/* Superdir: fixed-size blocks of slice pointers. Each block holds 2048
* pointers to actual array slices, which uses about 8 KB on 32-bit builds
* and 16 KB on 64-bit builds. This keeps very large indices from forcing
* catastrophic flat-directory growth. */
#define AR_SUPER_BLOCK_SLOTS 2048
/* Internal constants */
#define AR_SLICE_MIN_ALLOC 8 /* Initial dense window allocation */
#define AR_INSERT_IDX_NONE UINT64_MAX /* No insert performed yet */
/* Slice encoding types */
#define AR_SLICE_DENSE 0
#define AR_SLICE_SPARSE 1
/* Tagged value encoding (low 2 bits). NULL (0) means empty slot. */
#define AR_TAG_PTR ((uintptr_t)0) /* arString pointer (low 2 bits = 00) */
#define AR_TAG_INT ((uintptr_t)1) /* Immediate signed integer (01) */
#define AR_TAG_FLOAT ((uintptr_t)2) /* Immediate float (10) */
#define AR_TAG_STR ((uintptr_t)3) /* Inline small string (11) */
#define AR_TAG_MASK ((uintptr_t)3)
#if UINTPTR_MAX == UINT64_MAX
#define AR_SMALLSTR_MAXLEN 7
#define AR_SMALLSTR_LEN_MASK 0x7u
#elif UINTPTR_MAX == UINT32_MAX
#define AR_SMALLSTR_MAXLEN 3
#define AR_SMALLSTR_LEN_MASK 0x3u
#else
#error "Unsupported pointer size"
#endif
/* RDB type tags for array elements */
#define AR_RDB_TAG_SDS 0
#define AR_RDB_TAG_INT 1
#define AR_RDB_TAG_FLOAT 2
#define AR_RDB_TAG_SMALLSTR 3
/* Buffer size for inline types (int/float/smallstr) */
#define AR_INLINE_BUFSIZE 64
/* ----------------------------------------------------------------------------
* Data structures
* -------------------------------------------------------------------------- */
/* Array slice: holds a range of elements. Single allocation with payload. */
typedef struct arSlice {
uint8_t encoding; /* 0=dense, 1=sparse */
uint8_t _pad1[3];
uint32_t count; /* Non-empty items in this slice */
union {
struct {
uint32_t offset; /* First logical offset in window */
uint32_t winsize; /* Window size (power of two) */
uint32_t max_idx; /* Highest offset with a value */
void **items; /* Points into payload */
} dense;
struct {
uint32_t cap; /* Capacity */
uint16_t *offsets; /* Points into payload */
void **values; /* Points into payload (aligned) */
} sparse;
} layout;
} arSlice;
/* Super-directory entry: groups slices into fixed-size pointer blocks. */
typedef struct arSDirEntry {
uint64_t block_id; /* slice_id / AR_SUPER_BLOCK_SLOTS */
uint32_t count; /* Non-NULL slots in this block */
uint32_t _pad;
arSlice **slots; /* AR_SUPER_BLOCK_SLOTS pointers to slices */
} arSDirEntry;
/* Array header */
typedef struct redisArray {
uint64_t count; /* Total non-empty items */
uint64_t insert_idx; /* Last insert index, or UINT64_MAX if none */
uint64_t dir_alloc; /* Flat directory length (flat mode) */
uint64_t dir_highest_used; /* Highest non-NULL slice index */
uint64_t num_slices; /* Number of allocated slices */
size_t alloc_size; /* Tracked total allocation (for slot stats) */
uint32_t slice_size; /* Slice size (power of two) */
uint32_t sdir_len; /* Superdir entries count */
uint32_t sdir_cap; /* Superdir capacity */
uint32_t _pad;
arSlice **dir; /* Flat directory or NULL */
arSDirEntry *superdir; /* Super-directory or NULL */
} redisArray;
/* ----------------------------------------------------------------------------
* Inline helpers: index arithmetic
* -------------------------------------------------------------------------- */
/* Compute bits needed to address elements within a slice. */
static inline int arSliceBits(uint32_t slice_size) {
if (slice_size == 4096) return 12; /* Fast path for default */
int bits = 0;
uint32_t x = slice_size;
while (x > 1) { x >>= 1; bits++; }
return bits;
}
static inline uint64_t arSliceId(uint64_t idx, uint32_t slice_size) {
return idx >> arSliceBits(slice_size);
}
static inline uint32_t arSliceOff(uint64_t idx, uint32_t slice_size) {
return (uint32_t)(idx & (slice_size - 1));
}
static inline uint64_t arMakeIdx(uint64_t slice_id, uint32_t off, uint32_t slice_size) {
return (slice_id << arSliceBits(slice_size)) | off;
}
/* ----------------------------------------------------------------------------
* Inline helpers: tagged value encoding
* -------------------------------------------------------------------------- */
static inline int arIsEmpty(void *v) { return v == NULL; }
static inline int arIsPtr(void *v) {
return v != NULL && ((uintptr_t)v & AR_TAG_MASK) == AR_TAG_PTR;
}
static inline int arIsInt(void *v) {
return ((uintptr_t)v & AR_TAG_MASK) == AR_TAG_INT;
}
static inline int64_t arToInt(void *v) {
return (int64_t)(intptr_t)v >> 2; /* Arithmetic shift preserves sign */
}
static inline void *arFromInt(int64_t ival) {
return (void *)(((uintptr_t)ival << 2) | AR_TAG_INT);
}
static inline int arIntFits(int64_t ival) {
#if UINTPTR_MAX == UINT64_MAX
return ival >= -(1LL << 61) && ival <= (1LL << 61) - 1;
#else
return ival >= -(1LL << 29) && ival <= (1LL << 29) - 1;
#endif
}
static inline int arIsFloat(void *v) {
return ((uintptr_t)v & AR_TAG_MASK) == AR_TAG_FLOAT;
}
static inline double arToDouble(void *v) {
#if UINTPTR_MAX == UINT64_MAX
uint64_t bits = (uintptr_t)v & ~AR_TAG_MASK;
double d;
memcpy(&d, &bits, sizeof(d));
return d;
#else
uint32_t bits = (uint32_t)((uintptr_t)v & ~(uintptr_t)AR_TAG_MASK);
float f;
memcpy(&f, &bits, sizeof(f));
return (double)f;
#endif
}
static inline void *arFromFloatBits(uint64_t bits_trunc) {
#if UINTPTR_MAX == UINT64_MAX
return (void *)((bits_trunc & ~AR_TAG_MASK) | AR_TAG_FLOAT);
#else
uint32_t bits32 = (uint32_t)bits_trunc;
return (void *)(uintptr_t)((bits32 & ~(uint32_t)AR_TAG_MASK) | AR_TAG_FLOAT);
#endif
}
static inline int arIsSmallStr(void *v) {
return ((uintptr_t)v & AR_TAG_MASK) == AR_TAG_STR;
}
static inline int arSmallStrLen(void *v) {
return (int)(((uintptr_t)v >> 2) & AR_SMALLSTR_LEN_MASK);
}
static inline int arToSmallStr(void *v, char *buf) {
int len = arSmallStrLen(v);
uintptr_t val = (uintptr_t)v;
for (int i = 0; i < len; i++) {
buf[i] = (char)((val >> (8 * (i + 1))) & 0xFF);
}
buf[len] = '\0';
return len;
}
static inline void *arFromSmallStr(const char *s, int len) {
uintptr_t v = AR_TAG_STR | ((uintptr_t)len << 2);
for (int i = 0; i < len; i++) {
v |= ((uintptr_t)(uint8_t)s[i]) << (8 * (i + 1));
}
return (void *)v;
}
/* ----------------------------------------------------------------------------
* Public API
* -------------------------------------------------------------------------- */
/* Lifecycle */
redisArray *arNew(void);
void arFree(redisArray *ar);
redisArray *arDup(redisArray *ar);
void arDismiss(redisArray *ar, size_t size_hint);
/* Element access */
void *arGet(redisArray *ar, uint64_t idx);
void arSet(redisArray *ar, uint64_t idx, void *v);
int arDel(redisArray *ar, uint64_t idx);
/* Value encoding/decoding */
void *arEncode(const char *s, size_t len);
const char *arDecode(void *v, char *buf, size_t bufsize, size_t *outlen);
int arFormatFloat(double d, char *buf, size_t bufsize);
size_t arStringLen(const void *ptr);
const char *arStringData(const void *ptr);
void *arValueFromRdbInt(int64_t ival);
void *arValueFromRdbFloat(double d);
void *arValueFromRdbSmallStr(const char *s, size_t len);
/* Queries */
uint64_t arCount(redisArray *ar);
uint64_t arLen(redisArray *ar);
/* Bulk operations */
uint64_t arDeleteRange(redisArray *ar, uint64_t lo, uint64_t hi);
void arTruncate(redisArray *ar, uint64_t limit);
void arMayPromoteToDenseForRangeSet(redisArray *ar, uint64_t lo, uint64_t hi);
/* Utilities */
uint32_t arSparseFindPos(arSlice *s, uint16_t rel_idx, int *found);
uint32_t arSuperDirFind(redisArray *ar, uint64_t block_id, int *found);
redisArray *arDefrag(redisArray *ar, void *(*defragfn)(void *));
unsigned long arDefragIncremental(redisArray **arref, unsigned long cursor,
void *(*defragfn)(void *));
#endif /* __SPARSEARRAY_H */

2021
src/t_array.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -91,6 +91,12 @@ static inline int log2ceil(size_t x) {
#endif
}
/* Return the smallest power of 2 >= count (e.g. 5 -> 8, 8 -> 8). */
static inline int nearestNextPowerOf2(unsigned int count) {
if (count <= 1) return 1;
return 1 << (32 - __builtin_clz(count-1));
}
/* Check for __builtin_add_overflow() */
#ifndef __has_builtin
#define __has_builtin(x) 0

View file

@ -387,6 +387,10 @@ int zipmapValidateIntegrity(unsigned char *zm, size_t size, int deep) {
/* read the field name length */
l = zipmapDecodeLength(p);
/* Sanity check: length < 254 must be encoded in 1 byte, not 5 bytes */
if (l < ZIPMAP_BIGLEN && s != 1)
return 0;
p += s; /* skip the encoded field size */
p += l; /* skip the field */
@ -402,6 +406,9 @@ int zipmapValidateIntegrity(unsigned char *zm, size_t size, int deep) {
/* read the value length */
l = zipmapDecodeLength(p);
/* Sanity check: length < 254 must be encoded in 1 byte, not 5 bytes */
if (l < ZIPMAP_BIGLEN && s != 1)
return 0;
p += s; /* skip the encoded value size*/
e = *p++; /* skip the encoded free space (always encoded in one byte) */
p += l+e; /* skip the value and free space */

Binary file not shown.

View file

@ -15,7 +15,7 @@ if { ! [ catch {
proc generate_collections {suffix elements} {
set rd [redis_deferring_client]
set numcmd 7
set numcmd 8 ;# base commands including array
set has_vsets [server_has_command vadd]
if {$has_vsets} {incr numcmd}
@ -29,6 +29,15 @@ proc generate_collections {suffix elements} {
$rd zadd zset$suffix $j $val
$rd sadd set$suffix $val
$rd xadd stream$suffix * item 1 value $val
# Array with sparse indices and mixed value types (int, float, string)
set idx [expr {$j * 100 + int(rand() * 50)}] ;# sparse indices
if {$j % 3 == 0} {
$rd arset array$suffix $idx $j ;# integer value
} elseif {$j % 3 == 1} {
$rd arset array$suffix $idx [format "%.5f" [expr {rand() * 1000}]] ;# float value
} else {
$rd arset array$suffix $idx "str_$val" ;# string value
}
if {$has_vsets} {
$rd vadd vset$suffix VALUES 3 1 1 1 $j
}
@ -59,7 +68,9 @@ proc generate_types {} {
# create other non-collection types
r incr int
r set string str
if 0 {
r gcra gcra 10 5 60000
}
# create bigger objects with 10 items (more than a single ziplist / listpack)
generate_collections big 10

Some files were not shown because too many files have changed in this diff Show more