Commit graph

592 commits

Author SHA1 Message Date
Tony Liao
9d98477ca9 Optimize pure Python parse path for custom JSON enum names.
This implementation is similar to our Java idea in cl/949670944,
basically the idea is that we will have a local map (dictionary) that
will be populated the very first time we try to parse into an enum
field. The lifetime of this dictionary will be tied to the Parser
instance, similar to the Java implementation.

BENCHMARKS (baseline stats are recorded at cl/952170250):

Python:
```
[BENCHMARK] ParseJsonDefault:                med 10.06 us/op | p99 11.22 us/op | mean 10.10 ± 0.20 us/op
[BENCHMARK] ParseJsonCustom:                 med 20.33 us/op | p99 22.23 us/op | mean 20.40 ± 0.34 us/op
[BENCHMARK] ParseJsonUnknownIgnored:         med 24.21 us/op | p99 26.25 us/op | mean 24.31 ± 0.44 us/op
[BENCHMARK] ParseRepeatedJsonDefault:        med 1.94 us/item | p99 2.05 us/item | mean 1.95 ± 0.03 us/item
[BENCHMARK] ParseRepeatedJsonCustom:         med 2.26 us/item | p99 2.45 us/item | mean 2.27 ± 0.03 us/item
[BENCHMARK] ParseRepeatedJsonUnknownIgnored: med 5.31 us/item | p99 5.52 us/item | mean 5.32 ± 0.05 us/item
```

Cpp:
```
[BENCHMARK] ParseJsonDefault:                med 8.66 us/op | p99 9.21 us/op | mean 8.70 ± 0.13 us/op
[BENCHMARK] ParseJsonCustom:                 med 26.33 us/op | p99 29.71 us/op | mean 26.44 ± 0.69 us/op
[BENCHMARK] ParseJsonUnknownIgnored:         med 28.46 us/op | p99 31.80 us/op | mean 28.59 ± 0.67 us/op
[BENCHMARK] ParseRepeatedJsonDefault:        med 1.72 us/item | p99 2.19 us/item | mean 1.74 ± 0.06 us/item
[BENCHMARK] ParseRepeatedJsonCustom:         med 2.12 us/item | p99 2.38 us/item | mean 2.13 ± 0.04 us/item
[BENCHMARK] ParseRepeatedJsonUnknownIgnored: med 4.61 us/item | p99 4.92 us/item | mean 4.62 ± 0.07 us/item
```

UPB:
```
[BENCHMARK] ParseJsonDefault:                med 11.22 us/op | p99 12.29 us/op | mean 11.27 ± 0.21 us/op
[BENCHMARK] ParseJsonCustom:                 med 37.54 us/op | p99 39.25 us/op | mean 37.65 ± 0.46 us/op
[BENCHMARK] ParseJsonUnknownIgnored:         med 41.18 us/op | p99 48.45 us/op | mean 41.30 ± 0.89 us/op
[BENCHMARK] ParseRepeatedJsonDefault:        med 2.10 us/item | p99 2.19 us/item | mean 2.10 ± 0.02 us/item
[BENCHMARK] ParseRepeatedJsonCustom:         med 2.58 us/item | p99 2.73 us/item | mean 2.59 ± 0.03 us/item
[BENCHMARK] ParseRepeatedJsonUnknownIgnored: med 5.70 us/item | p99 5.90 us/item | mean 5.71 ± 0.04 us/item
```

Seeing these benchmarks, we can see a very noticeable improvement in
`ParseRepeatedJsonCustom` and `ParseRepeatedJsonUnknownIgnored` cases --
which correspond to the pathological cases that we are worried about.
Singular fields are taking longer to parse in our microbenchmark because
the cache that we've added is tied to each Parser instance and must be
re-instantiated across every `Parse` call.

PiperOrigin-RevId: 970528683
2026-08-25 07:09:41 -07:00
Samuel Benzaquen
1a23830881 [Py/C++] Fixed data race in Python free threading by removing obsolete hack
We previously had a hack in Python/C++ Protobuf to account for the fact that LazyField did not properly remember a custom DescriptorPool or MessageFactory that was set in the ParseContext at parse time.

The code has since been fixed to properly handle the case where ParseContext contains a custom DescriptorPool/MessageFactory.  Removing the hack removes the data race under free threading.

PiperOrigin-RevId: 967236272
2026-08-19 08:59:26 -07:00
Tony Liao
f9f94388a3 Additional unit tests for Python handling of custom JSON enum names.
The unit tests added in the initial implementation in cl/925012500 and
cl/922729754 were fairly minimal. I took some inspiration from our C++
and Java unit tests for JSON's custom enum name parsing to come up with
these new test cases.

This change also adds a python Benchmark that we can use to verify that
our optimizations in cl/952170251 actually works.

Benchmark results across all three proto implementations (cpp, python, upb):

=== C++ Extension Protos (json_format_benchmark_cpp_protos) ===
[BENCHMARK] ParseJsonDefault:                med 8.70 us/op | p99 9.56 us/op | mean 8.74 ± 0.15 us/op
[BENCHMARK] ParseJsonCustom:                 med 17.39 us/op | p99 21.64 us/op | mean 17.43 ± 0.51 us/op
[BENCHMARK] ParseJsonUnknownIgnored:         med 26.97 us/op | p99 32.04 us/op | mean 27.06 ± 0.65 us/op
[BENCHMARK] ParseRepeatedJsonDefault:        med 1.71 us/item | p99 1.83 us/item | mean 1.71 ± 0.02 us/item
[BENCHMARK] ParseRepeatedJsonCustom:         med 9.18 us/item | p99 9.88 us/item | mean 9.20 ± 0.11 us/item
[BENCHMARK] ParseRepeatedJsonUnknownIgnored: med 18.79 us/item | p99 19.19 us/item | mean 18.81 ± 0.13 us/item

=== UPB Extension Protos (json_format_benchmark_upb_protos) ===
[BENCHMARK] ParseJsonDefault:                med 8.70 us/op | p99 9.56 us/op | mean 8.74 ± 0.15 us/op
[BENCHMARK] ParseJsonCustom:                 med 17.39 us/op | p99 21.64 us/op | mean 17.43 ± 0.51 us/op
[BENCHMARK] ParseJsonUnknownIgnored:         med 26.97 us/op | p99 32.04 us/op | mean 27.06 ± 0.65 us/op
[BENCHMARK] ParseRepeatedJsonDefault:        med 1.71 us/item | p99 1.83 us/item | mean 1.71 ± 0.02 us/item
[BENCHMARK] ParseRepeatedJsonCustom:         med 9.18 us/item | p99 9.88 us/item | mean 9.20 ± 0.11 us/item
[BENCHMARK] ParseRepeatedJsonUnknownIgnored: med 18.79 us/item | p99 19.19 us/item | mean 18.81 ± 0.13 us/item

=== Pure Python Protos (json_format_benchmark_python_protos) ===
[BENCHMARK] ParseJsonDefault:                med 10.26 us/op | p99 11.20 us/op | mean 10.30 ± 0.25 us/op
[BENCHMARK] ParseJsonCustom:                 med 16.59 us/op | p99 18.35 us/op | mean 16.70 ± 0.45 us/op
[BENCHMARK] ParseJsonUnknownIgnored:         med 25.09 us/op | p99 28.22 us/op | mean 25.27 ± 0.65 us/op
[BENCHMARK] ParseRepeatedJsonDefault:        med 1.94 us/item | p99 3.25 us/item | mean 1.97 ± 0.14 us/item
[BENCHMARK] ParseRepeatedJsonCustom:         med 7.16 us/item | p99 10.35 us/item | mean 7.24 ± 0.39 us/item
[BENCHMARK] ParseRepeatedJsonUnknownIgnored: med 15.43 us/item | p99 19.54 us/item | mean 15.54 ± 0.49 us/item

PiperOrigin-RevId: 966820541
2026-08-18 14:57:30 -07:00
Charlie Beattie
b43ac42ce0 Protocol buffers support being created from memoryviews.
PiperOrigin-RevId: 966179112
2026-08-17 14:23:02 -07:00
Protobuf Team Bot
02a9c9dec8 Fix messageset conformance for pure Python to match Py-C++ and Py-upb
MessageSet is a legacy affordance holdover from pre-proto2 days. Unlike the rest of Protobuf, it has first-wins instead of last-wins semantic in the case of duplicate ids or values within the same message set entry.

No serializer would ever write such sequences, but the same hypothetical byte sequence being parsed differently by two implementations is undesirable.

Note that other nonconformance fixes would commonly be held to breaking change releases to avoid any possible disruption of single-language users who may be relying on it as a load-bearing bug. However, the nature of this particular case is such that there is little reason to hold it back.

PiperOrigin-RevId: 964043226
2026-08-13 06:25:58 -07:00
Protobuf Team Bot
2a36520396 fix missed oom handling in unset required
PiperOrigin-RevId: 956610684
2026-07-30 10:42:17 -07:00
Protobuf Team Bot
01033aca05 Add more oom test coverage and fix errors on copy
PiperOrigin-RevId: 956087593
2026-07-29 14:24:43 -07:00
kuchazi-yy
b5d2e8db22 fix(python): reject mismatched CopyToProto targets (#28666)
Fixes #28665

Closes #28666

COPYBARA_INTEGRATE_REVIEW=https://github.com/protocolbuffers/protobuf/pull/28666 from kuchazi-yy:fix/python-copy-to-proto-type-check 70df03d3b1
PiperOrigin-RevId: 955861788
2026-07-29 07:09:05 -07:00
Protobuf Team Bot
ad6a7e8b64 Check return values and handle alloc failures in python
PiperOrigin-RevId: 955022274
2026-07-27 22:11:16 -07:00
Samuel Benzaquen
160c306671 Fix use-after-free when oneof switches during MergeFromString.
We parse into a temporary message first to detect oneof switches before modifying the target message, and release the wrappers for switching oneof fields in the target message.

PiperOrigin-RevId: 954778098
2026-07-27 12:50:24 -07:00
Joshua Haberman
414e4142cd Sync sub-objects in PyUpb_Message_MergeFromString even when decode fails.
In 'PyUpb_Message_MergeFromString', 'upb_Decode' may partially mutate or populate sub-messages on the parent message before failing with a decode error status. Skipping 'PyUpb_Message_SyncSubobjs' when 'status != kUpb_DecodeStatus_Ok' leaves stub sub-object wrappers unsynced in 'unset_subobj_map', causing duplicate keys in 'ObjCache' on subsequent access and leading to heap-use-after-free.

Move 'PyUpb_Message_SyncSubobjs(self)' before the decode status check in 'PyUpb_Message_MergeFromString' so any sub-message wrappers modified during decoding are synced properly.

Add 'testMergeFromStringDecodeErrorSync' to 'third_party/py/google/protobuf/internal/message_test.py' to verify that stub sub-objects remain synced and intact after a failed 'MergeFromString'.

PiperOrigin-RevId: 953615725
2026-07-24 16:53:12 -07:00
Tony Liao
cb440834fd Document Python & PHP non-conformance where booleans are accepted by int and float fields.
PiperOrigin-RevId: 952905948
2026-07-23 12:51:55 -07:00
Joshua Haberman
feaa31c4d7 [Py/FreeThreading] Fixed remaining race conditions in Dealloc()
This change modifies all remaining `Dealloc()` functions to use `EraseIfEqual` if they were not already. This prevents the same race that was fixed for descriptors in cl/874084218.

PiperOrigin-RevId: 952273589
2026-07-22 12:43:18 -07:00
Runze Wang
52e82c810c Emit future warning when mutating GetOptions() in OSS
PiperOrigin-RevId: 946657065
2026-07-12 13:41:24 -07:00
Jason Aragorn Tobias Lunn
271ca53b50 Migrate top-level protobuf unittest protos from Edition 2024 to Edition 2026.
* Updates edition to 2026 in the unittest protos.
* Removes the obsolete target compile option `cc_enable_arenas` since C++
  options are moved and arenas are enabled by default.
* Updates `maximum_edition` target constraints to edition 2026 in Java,
  Python, C#, and upb build targets to allow loading and validation of
  Edition 2026 files.
* Regenerates internal_options_bootstrap compiler files.

PiperOrigin-RevId: 944424868
2026-07-08 04:38:22 -07:00
Hana Joo
ebedfb958e Automated Code Change
PiperOrigin-RevId: 941625858
2026-07-02 04:39:38 -07:00
Protobuf Team Bot
eaa8c8cca5 Automated Code Change
PiperOrigin-RevId: 941496740
2026-07-01 23:28:07 -07:00
Charlie Beattie
dc4d738909 Internal change.
PiperOrigin-RevId: 941222035
2026-07-01 12:07:16 -07:00
vhulto
9e9dbae858 Python: fix heap-use-after-free in MapIterator after map.clear() (#27257)
## Bug

`Clear()` in `map_container.cc` (line 294-302) calls
`reflection->ClearField()` which destroys all underlying map nodes via
`ClearTable(reset=true)`, but does not increment `self->version`.

All other mutators (ScalarMapSetItem, MessageMapSetItem, MergeFrom, etc.)
increment `self->version` after mutation. `IterNext()` relies on version
mismatch to detect concurrent modification and raise `RuntimeError`.
Without the version bump, a live iterator proceeds to dereference the
freed `NodeBase*` via `SetMapIteratorValue` → `UntypedMapIterator::PlusPlus`.

**ASAN confirmed:** heap-use-after-free, READ size 8 at
`UntypedMapIterator::PlusPlus` (map.h:599), freed by `ClearTable`
(map.h:345), allocated by `ScalarMapSetItem` (map_container.cc:416).

## Fix

Add `self->version++` after `ClearField` in `Clear()`, matching every
other mutator in the same file.

## Reproducer

```python
msg = M()  # proto3 with map<string, int32> mp
for k in ("a","b","c","d"): msg.mp[k] = 1
it = iter(msg.mp)
next(it)
msg.mp.clear()   # frees nodes, version NOT bumped
next(it)         # heap-use-after-free
```

Closes #27257

COPYBARA_INTEGRATE_REVIEW=https://github.com/protocolbuffers/protobuf/pull/27257 from vhullto:fix/python-map-clear-uaf fb35225110
PiperOrigin-RevId: 939482747
2026-06-28 13:43:09 -07:00
Runze Wang
abdf212e92 Add PyDescriptorPool_FromSharedPool(std::shared_ptr) API
This new API overload enables safe true co-ownership between the taken C++ pointer and the returned python pointer

PiperOrigin-RevId: 938697698
2026-06-26 11:48:30 -07:00
Runze Wang
9ac0b22526 Propagate non-AttributeError exceptions in PyUpb_MessageMeta_GetAttr
When cpython_bits.type_getattro(self, name) returns NULL due to an exception raised in a descriptor (such as KeyboardInterrupt, MemoryError, or SystemExit), PyUpb_MessageMeta_GetAttr previously cleared the error and raised AttributeError.

Check PyErr_ExceptionMatches(PyExc_AttributeError) before clearing the error to ensure non-AttributeError exceptions are properly propagated.

PiperOrigin-RevId: 938246969
2026-06-25 16:13:45 -07:00
Runze Wang
ae4f98fe70 Fix segfault via strcmp(NULL) in numpy type detection
When PyObject_GetAttrString returns NULL (e.g., because __name__ or __module__ attribute access raises an exception on metaclass), PyUpb_GetStrData returns NULL. Passing NULL to strcmp previously caused a segmentation fault.

Check for NULL before calling PyUpb_GetStrData and strcmp, and call PyErr_Clear() when attribute lookup fails.

PiperOrigin-RevId: 938039167
2026-06-25 09:52:49 -07:00
Runze Wang
3d31b0f786 Fix memory leak in PyUpb_Descriptor_GetExtensionRanges
PyTuple_Pack increments the reference count of its arguments. When PyTuple_Pack(2, start, end) was passed directly to PyList_SetItem, the start and end PyLong objects were leaked on every call.

Store the tuple created by PyTuple_Pack and DECREF start and end before inserting into the list.

PiperOrigin-RevId: 937601625
2026-06-24 15:49:13 -07:00
Charlie Beattie
a9c1c48f23 Internal change.
PiperOrigin-RevId: 937266383
2026-06-24 05:13:39 -07:00
Charlie Beattie
10046e9157 Internal Change.
PiperOrigin-RevId: 936784975
2026-06-23 11:20:48 -07:00
Charlie Beattie
2bda7ee751 Internal change
PiperOrigin-RevId: 933111478
2026-06-16 08:49:46 -07:00
Protobuf Team Bot
a7ab28ae44 Restore recursion limit in testRecursionMap to prevent test pollution
Without this, the test fails if it runs the test cases in a certain order.

PiperOrigin-RevId: 932553469
2026-06-15 10:56:42 -07:00
Charlie Beattie
ea40f1d32a Support assigning repeated scalar fields using Python Buffer API.
PiperOrigin-RevId: 932284662
2026-06-15 00:51:15 -07:00
Charlie Beattie
156a0b8dfc Fix Pure Python map<string, ...> key handling for bytes lookups.
The pure Python implementation of maps had a bug where looking up an entry in a `map<string, ValueType>` field using a `bytes` key would cause silent data corruption.

**Bug:**

1.  `msg.my_map['foo'] = 100` stores `{'foo': 100}`.
2.  `msg.my_map[b'foo']` attempts a lookup.
3.  `self._values[b'foo']` raises `KeyError` as `b'foo' != 'foo'`.
4.  The `except KeyError` block normalizes `b'foo'` to `'foo'`.
5.  Crucially, it then inserts a *default value* for the value type, overwriting the existing entry: `self._values['foo'] = 0`.
6.  The lookup returns 0, and the original value of 100 is lost.

**Fix:**

The key is now normalized using `self._key_checker.CheckValue(key)` *at the beginning* of `__getitem__`, `__contains__`, `get`, `__delitem__`, and `setdefault`. This ensures the key is in the canonical `str` format *before* any dictionary access, preventing the erroneous write-on-miss.

This change makes the behavior consistent with the C++ and upb implementations.

PiperOrigin-RevId: 930498495
2026-06-11 06:49:03 -07:00
Charlie Beattie
cdcf21dbf7 Support C++ Immutable map lookup.
PiperOrigin-RevId: 930421591
2026-06-11 03:36:58 -07:00
Runze Wang
be53faf37d internal change
PiperOrigin-RevId: 930072020
2026-06-10 14:17:37 -07:00
Jie Luo
e86e536349 Change Python DescriptorDatabase FindFileContainingSymbol() to accept leading "." to match DescriptorPool and other languages
PiperOrigin-RevId: 929451066
2026-06-09 15:58:00 -07:00
Israel Blancas
b6548fc510 python: mark proto_builder sha1 as non-security use (#27473)
Fixes #27472

Closes #27473

COPYBARA_INTEGRATE_REVIEW=https://github.com/protocolbuffers/protobuf/pull/27473 from iblancasa:27472 0ab662a73b
PiperOrigin-RevId: 929221750
2026-06-09 08:53:29 -07:00
Charlie Beattie
2906b1e81b Internal change.
PiperOrigin-RevId: 927241088
2026-06-05 05:35:17 -07:00
Runze Wang
eebe9b7737 [py/C++] Implement Make GetOptions() return immutable options. C++ will raise a TypeError when options returned GetOptions() by is mutated.
PiperOrigin-RevId: 926215827
2026-06-03 13:10:50 -07:00
Runze Wang
f5738bb5f0 [py/pure python] Implement Make GetOptions() return immutable options. Raise a TypeError when options returned GetOptions() by is mutated.
PiperOrigin-RevId: 926114776
2026-06-03 10:20:31 -07:00
Charlie Beattie
524871b9d1 Add Python repeated fields benchmarks.
PiperOrigin-RevId: 924877292
2026-06-01 13:22:33 -07:00
Kamil Monicz
e090f8ae77 Serialize length-prefixed protos once (#27252)
Avoid calling ByteSize() separately in serialize_length_prefixed(). The serialized payload already carries the exact length to prefix, so this keeps behavior intact while avoiding a duplicate serialization-sized pass.

Closes #27252

COPYBARA_INTEGRATE_REVIEW=https://github.com/protocolbuffers/protobuf/pull/27252 from Zaczero:python-serialize-length-prefixed-once 8a4f60b4c7
PiperOrigin-RevId: 923539199
2026-05-29 12:19:19 -07:00
Hong Shin
7800571253 json/python: add support for custom json strings for enum values
PiperOrigin-RevId: 922729754
2026-05-28 06:17:20 -07:00
Charlie Beattie
6dac2de5b6 Remove possible use after free via Python Buffer Objects in MergeFromString.
PiperOrigin-RevId: 921681380
2026-05-26 14:27:25 -07:00
Charlie Beattie
f7c66578cc Add assign_bytes Benchmarks.
PiperOrigin-RevId: 919863834
2026-05-22 14:36:41 -07:00
Runze Wang
403d429eab [py/upb] Make GetOptions() return immutable options for scalar type. UPB will raise a TypeError when options returned GetOptions() by is mutated.
PiperOrigin-RevId: 918559519
2026-05-20 11:33:26 -07:00
Amer Elsheikh
5f77738be4 Create an enum_type_wrapper.pyi stub with DESCRIPTOR: EnumDescriptor
PiperOrigin-RevId: 914243299
2026-05-12 06:14:01 -07:00
Joshua Haberman
ac90873fac Add test for RepeatedCompositeContainer comparison with list.
This test highlights a behavioral difference where upb allows equality comparison between a RepeatedCompositeContainer and a Python list, while the cpp and python implementations raise a TypeError.

PiperOrigin-RevId: 912754358
2026-05-08 16:43:32 -07:00
Joshua Haberman
6cbc7593bf Fixed race in GetMessageClass/RegisterMessageClass under free threading.
The race condition was resolved by adding a mutex lock to all accesses of the cache.

PiperOrigin-RevId: 912595698
2026-05-08 10:47:54 -07:00
Protobuf Team Bot
af47fbb369 Improve serialization performance in the case of recursive maps in pure Python Protobuf.
PiperOrigin-RevId: 910887857
2026-05-05 13:45:18 -07:00
Protobuf Team Bot
5be0a43cd6 Avoid NULL pointer dereference in PyUpb_PyToUpbEnum if run with a malformed UTF-8 str (e.g. "\ud800")
Add test coverage of 'assign string to enum-typed field' in general (including this case). This appears to be accepted only Py-upb and not otherwise.

Fixes https://github.com/protocolbuffers/protobuf/issues/27106

PiperOrigin-RevId: 908148411
2026-04-30 07:01:58 -07:00
Protobuf Team Bot
cecbbf41e4 Add mutex protection to DescriptorPool caches.
This change introduces a FreeThreadingMutex to PyDescriptorPool to guard access to the descriptor_options and descriptor_features hash maps.

PiperOrigin-RevId: 908115178
2026-04-30 05:47:00 -07:00
Protobuf Team Bot
6cd6ca4116 Auto-format Py Proto python files.
PiperOrigin-RevId: 907755617
2026-04-29 13:40:58 -07:00
Runze Wang
3fca2336f8 This is a future breaking change.
PiperOrigin-RevId: 907076763
2026-04-28 11:12:07 -07:00