)]}'
{
  "commit": "f254bef8bc32efc21eb0bd22017d318c8c1459eb",
  "tree": "dfaa86f35761590290d0a7033f4f088bd7df1c83",
  "parents": [
    "a58b9f59343af001b543e6c00e773c47bd73c579"
  ],
  "author": {
    "name": "m00dy",
    "email": "professor.moody@pm.me",
    "time": "Thu Nov 05 18:18:52 2026 -0800"
  },
  "committer": {
    "name": "Copybara-Service",
    "email": "copybara-worker@google.com",
    "time": "Thu Nov 05 18:21:25 2026 -0800"
  },
  "message": "fix(upb): escape field key names in jsonenc_fieldval when encoding (#31010)\n\n### Summary\n\n`jsonenc_fieldval` in `upb/json/encode.c` writes an object key with `jsonenc_printf(e, \"\\\"%s\\\":\", name)`, where `name` comes from `upb_FieldDef_JsonName` (or `upb_FieldDef_Name` with `upb_JsonEncode_UseProtoNames`). Field names are checked as identifiers when a descriptor is added to a def pool, but `json_name` is copied from the `FieldDescriptorProto` unchanged. A `json_name` that contains a quote, a backslash or a control character therefore reaches the output unescaped. With the tests added here, a field whose `json_name` is\n\n```\na\":1,\"b\n```\n\nis encoded as `{\"a\":1,\"b\":1}`, which is a two-key object, and a `json_name` containing a newline, a backslash or a tab produces invalid JSON. Values, map keys, struct keys and enum names in the same encoder already go through `jsonenc_string` or `jsonenc_stringbody`.\n\n### Fix\n\nIn `jsonenc_fieldval`, emit the key with `jsonenc_string`, followed by `:`. The extension branch, which formats the full name in brackets, now passes the name through `jsonenc_stringbody`. Extension full names are identifier-checked by the def builder, so the extension change has no observable effect for descriptors the def pool accepts; it only makes the two branches consistent. It can be dropped if you prefer the smaller change.\n\nThis change does not touch `jsonenc_fieldpath`.\n\n### Relationship to #28678\n\n#28678 makes the same `json_name` change in `jsonenc_fieldval`. It also adds a character allow-list to `jsonenc_fieldpath`. Applied to current main, that allow-list makes the existing `EncodeFieldMaskEscapedPaths` fail, because that test expects paths containing a newline, a tab and a quote to encode and be escaped. This change leaves `jsonenc_fieldpath` alone and `EncodeFieldMaskEscapedPaths` keeps passing. If #28678 lands first, the `jsonenc_fieldval` part of this change is redundant.\n\n### Testing\n\nAdded to `upb/json/encode_test.cc`, with a new `JsonNameEscape` message in `upb/json/test.proto`:\n\n- `EncodeJsonNamePositiveControl` (also with `upb_JsonEncode_UseProtoNames`)\n- `EncodeJsonNameWithQuotesAndBackslashesIsEscaped` (also decodes the output with `upb_JsonDecode` and checks the field round-trips)\n- `EncodeJsonNameWithControlCharactersIsEscaped`\n\n```\nbazel test //upb/json:encode_test\n```\n\npasses with the change (22 tests, including `EncodeFieldMaskEscapedPaths`). Without the change, the two escaping tests fail and the others pass. Built with clang 22.1.8 and Bazel 8.7.0 on x86_64 Linux, also under ASan and UBSan.\n\nAI assistance was used in drafting and validating this change; the patch and tests were reviewed and verified by the author.\n\nCloses #31010\n\nCOPYBARA_INTEGRATE_REVIEW\u003dhttps://github.com/protocolbuffers/protobuf/pull/31010 from professor-moody:fix-upb-json-encode-field-key-escaping ff14f061c3b397112c241067e99e1e80aa265c28\nPiperOrigin-RevId: 1015144727\n",
  "tree_diff": [
    {
      "type": "modify",
      "old_id": "7498c89c85bfa26a74518a1e6ff8576a0f060e40",
      "old_mode": 33188,
      "old_path": "upb/json/encode.c",
      "new_id": "813a64819fb9442e84a58f714fb80d967ef66951",
      "new_mode": 33188,
      "new_path": "upb/json/encode.c"
    },
    {
      "type": "modify",
      "old_id": "b22d1a4aefa4f0388b30bfd75c5f25d747875ce7",
      "old_mode": 33188,
      "old_path": "upb/json/encode_test.cc",
      "new_id": "2780405d59640e18955fe0863a47e6c6091adb0b",
      "new_mode": 33188,
      "new_path": "upb/json/encode_test.cc"
    },
    {
      "type": "modify",
      "old_id": "d7ea2d44708936458a2e5bbd8058817c386e9429",
      "old_mode": 33188,
      "old_path": "upb/json/test.proto",
      "new_id": "0661e18b1869acd0e73f173cad82f833cd29b822",
      "new_mode": 33188,
      "new_path": "upb/json/test.proto"
    }
  ]
}
