Harden C extension for memory/pointer safety, GIL=0, and speed#96
Merged
Conversation
…release, faster findall/UTF-8
Contributor
Author
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
…ffset_to_index overhead
…-extension __version__ to 0.4.0 and document in README.\n- Add coverage-focused tests for pcre.re_compat, pcre.threads, pcre.cache\n (global strategy), and pcre.pcre compile/match/finditer/parallel_map/sub\n corner cases.\n- Enhance existing cache and error tests for cache limits, unhashable keys,\n and error-code handling.\n- Coverage for the Python layer reaches 99% (1316 statements, 14 unreachable/\n environment-specific lines).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Harden the C extension and Python wrapper for
pypcre, make hot paths faster, and release v0.4.0 with expanded test coverage.Memory safety / bounds / refcount
create_match_objectvalidates ovector allocation size againstSIZE_MAX, rejectsovec_count == 0, and checksovector != NULL.match_resolve_spanandmatch_get_group_valueusesize_tindexing to avoid overflow on large group indices.create_groupindex_dictbounds name length withstrnleninstead of relying on PCRE2 null termination.pcre_memory_initializeis now atomic/lock-safe usingatomic_flagandATOMIC_VAR;pcre_malloc/pcre_free/pcre_memory_allocator_nameuse acquire loads.Free-threaded / GIL=0
Py_MOD_GIL_NOT_USED/PyUnstable_Module_SetGILstays in place.PCRE2_CALL_MAYBE_RELEASE_GILare no-ops underPy_GIL_DISABLED, so the module remains safe forPYTHON_GIL=0.Performance
PCRE2_CALL_MAYBE_RELEASE_GILonly drops the GIL when the match subject is larger than 256 KiB, avoiding thePyEval_{Save,Restore}Threadoverhead on small calls.utf8_offset_to_indexby counting UTF-8 starter bytes in 8-byte chunks withpopcountll.utf8_index_to_offsetby skipping 8-byte chunks whose starter count is smaller than the remaining index.match_get_group_valuekeeps the fastPyUnicode_DecodeUTF8path for group extraction; the vectorizedutf8_offset_to_indexis used only forspan()/start()/end().PCRE2_NO_UTF_CHECK) for Python strings and fully validated bytes subjects.FindIterso a JITNOMATCHno longer falls through to a redundantpcre2_matchcall.findallimplementation (Pattern_findall,module_findall) that builds the result list directly, bypassing the Python per-match iterator overhead.Bug/security lapses fixed
pcre_memory_initializewas not thread-safe; two threads could race throughdlopen/pointer assignment.FindIter_iternextranpcre2_matchafter every JITNOMATCH, doubling work.utf8_offset_to_indexallocated a temporaryPyUnicodeobject for everyspan()/start()/end()call.create_groupindex_dictread names with unboundedPyUnicode_FromString.Build/CI
releaseworkflow'ssetuptoolsinstall to thepyproject.tomlbuild-system range and fix theuv pip isntalltypo.README
re/regexresults and addedbenchmarks/competitor_bench.pyso the numbers are reproducible.Version / test coverage
pyproject.tomlandpcre_ext/pcre2.c__version__, with a README release note.tests/test_re_compat.py,tests/test_threads.py,tests/test_cache_global.py,tests/test_cache_global_inproc.py, andtests/test_coverage_gaps.pyto cover the remaining Python-layer corners (compile flags,finditer/findall/subfallback loops,re_compathelpers, thread-pool lifecycle, and the global cache strategy).sre_parsefallback unavailable on CPython 3.14).Benchmarks
All numbers are from a free-threaded Python 3.14.5 build with
sys._is_gil_enabled() == False, branch vs.main.Pattern.search(..., pos=i)over every position (tests/test_bench_string.py, 50 iterations)The large 2-byte/4-byte wins come from vectorizing
utf8_index_to_offset; ASCII and latin1 are unchanged within run-to-run noise.Match.span(0)+Match.group(0)micro-benchmark (1000 calls, median of 10)Pattern.findall(...)(\w+over 20k/10k words, median of 30)Head-to-head vs
reandregex(README highlight workloads)Measured on Python 3.14.5 free-threaded x86_64 Linux, compiled-pattern reuse, JIT enabled, best-of-7. Only workloads where PyPcre is decisively faster are shown.
findallre(ms)regex(ms)WARN/ERRORlines (^(?:WARN|ERROR).*?$,MULTILINE)0.6029.3530.85re, 52x vsregex^[A-Z][a-z]+ [A-Z][a-z]+,MULTILINE)1.0229.5015.95re, 16x vsregex(?:(?<=foo)bar|baz)(?!qux))3.7811.8110.42re, 2.8x vsregexfinditerre(ms)regex(ms)WARN/ERRORlines0.6029.3331.23re, 52x vsregex1.0230.0916.25re, 16x vsregexMulti-threaded
findall(8 threads)re(ms)regex(ms)WARN/ERRORlines3.2632.5734.41re, 10.6x vsregex3.1831.8417.31re, 5.4x vsregexValidation
pytestpasses on free-threaded Python 3.14.5: 275 passed, 14 skipped, 545 subtests passed.bash format/format.shpasses.1316statements,14missed: unreachable/dead code orsre_parsefallback unavailable on CPython 3.14).3.14t) on Ubuntu/macOS/Windows.Link to Devin session: https://app.devin.ai/sessions/bd880d99fc244c188c268e96d90ac233
Requested by: @Qubitium