)]}'
{
  "commit": "b5715a46ec35c5193837bedfa5cbe8f6d0dfeeec",
  "tree": "67c8164c3f10836f5727024782882d84c1242b9e",
  "parents": [
    "d871abc7ed56912002057b6f1f9e3e1b0c42a045"
  ],
  "author": {
    "name": "Joshua Haberman",
    "email": "haberman@google.com",
    "time": "Thu Nov 05 10:47:17 2026 -0800"
  },
  "committer": {
    "name": "Copybara-Service",
    "email": "copybara-worker@google.com",
    "time": "Thu Nov 05 11:43:15 2026 -0800"
  },
  "message": "Copy `memoryview` contents in pyext `CheckString()` instead of keeping a view into them.\n\nFor a `memoryview` value of a string/bytes field, `CheckString()` acquired a buffer, made a `string_view` into it, and released the buffer before returning, assuming the memoryview keeps the data alive. But once the buffer is released, the memoryview can be `release()`d and its exporter (e.g. a `bytearray`) resized, which frees the storage the `string_view` points to. `extend()` holds these views until all elements have been converted, and iterating a generator runs arbitrary Python in between, so `extend()` with a generator could read freed memory even without threads (see the new test). With free threading, every caller is exposed to another thread doing the same.\n\n`CheckString()` now copies the contents into a new `bytes` object while it still holds the buffer, and returns a view into that copy, which the caller keeps alive alongside `arg`. upb already raises `ValueError` in this case, because it converts the elements only after iterating.\n\nPiperOrigin-RevId: 1014829811\n",
  "tree_diff": [
    {
      "type": "modify",
      "old_id": "afec4a285d766234aeeed0326acde28416c11419",
      "old_mode": 33261,
      "old_path": "python/google/protobuf/internal/message_test.py",
      "new_id": "f6e7e69648d68d3515c6172231021a6a8ea8737b",
      "new_mode": 33261,
      "new_path": "python/google/protobuf/internal/message_test.py"
    },
    {
      "type": "modify",
      "old_id": "a9db2bd3aeaeb17c5d736439cf2014287747d594",
      "old_mode": 33188,
      "old_path": "python/google/protobuf/pyext/map_container.cc",
      "new_id": "01a90ad296af5904d8eb6776a36f9ce96e50df02",
      "new_mode": 33188,
      "new_path": "python/google/protobuf/pyext/map_container.cc"
    },
    {
      "type": "modify",
      "old_id": "bd4190b1c670cd49562f93d85c8d7879b4fd401c",
      "old_mode": 33188,
      "old_path": "python/google/protobuf/pyext/message.cc",
      "new_id": "687fb2108e42868255bb460d9ecc184c3d0a568a",
      "new_mode": 33188,
      "new_path": "python/google/protobuf/pyext/message.cc"
    },
    {
      "type": "modify",
      "old_id": "d2031836023b7cf5eb655bb85088f7156d4c868c",
      "old_mode": 33188,
      "old_path": "python/google/protobuf/pyext/message.h",
      "new_id": "7eaa0f3b5c00d6f2e7cfaff8b7e49a3719890016",
      "new_mode": 33188,
      "new_path": "python/google/protobuf/pyext/message.h"
    },
    {
      "type": "modify",
      "old_id": "e5ea091d896fcd42db58dd7d2448751fb6bfbb59",
      "old_mode": 33188,
      "old_path": "python/google/protobuf/pyext/repeated_scalar_container.cc",
      "new_id": "50f74c0fe634cac51b0e3188d6f1d4cbb8d996e3",
      "new_mode": 33188,
      "new_path": "python/google/protobuf/pyext/repeated_scalar_container.cc"
    }
  ]
}
