{"record":{"id":"35b49a8c53ac562e","repo":"XINCGer/Unity3DTraining","slug":"string-contains-low-surrogate-not-followed-by-high","errorCode":null,"errorMessage":"String contains low surrogate not followed by high surrogate","messagePattern":"String contains low surrogate not followed by high surrogate","errorType":"exception","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"NetWorkAndResources/Socket_Protobuff/Assets/Plugins/Google.Protobuf/JsonFormatter.cs","lineNumber":702,"sourceCode":"        internal static void WriteString(TextWriter writer, string text)\n        {\n            writer.Write('\"');\n            for (int i = 0; i < text.Length; i++)\n            {\n                char c = text[i];\n                if (c < 0xa0)\n                {\n                    writer.Write(CommonRepresentations[c]);\n                    continue;\n                }\n                if (char.IsHighSurrogate(c))\n                {\n                    // Encountered first part of a surrogate pair.\n                    // Check that we have the whole pair, and encode both parts as hex.\n                    i++;\n                    if (i == text.Length || !char.IsLowSurrogate(text[i]))\n                    {\n                        throw new ArgumentException(\"String contains low surrogate not followed by high surrogate\");\n                    }\n                    HexEncodeUtf16CodeUnit(writer, c);\n                    HexEncodeUtf16CodeUnit(writer, text[i]);\n                    continue;\n                }\n                else if (char.IsLowSurrogate(c))\n                {\n                    throw new ArgumentException(\"String contains high surrogate not preceded by low surrogate\");\n                }\n                switch ((uint)c)\n                {\n                    // These are not required by json spec\n                    // but used to prevent security bugs in javascript.\n                    case 0xfeff:  // Zero width no-break space\n                    case 0xfff9:  // Interlinear annotation anchor\n                    case 0xfffa:  // Interlinear annotation separator\n                    case 0xfffb:  // Interlinear annotation terminator\n","sourceCodeStart":684,"sourceCodeEnd":720,"githubUrl":"https://github.com/XINCGer/Unity3DTraining/blob/016f98412ea5e11756b286736f479b66ab6216d5/NetWorkAndResources/Socket_Protobuff/Assets/Plugins/Google.Protobuf/JsonFormatter.cs#L684-L720","documentation":"When JSON-escaping a string, JsonFormatter encounters a high surrogate (first half of a UTF-16 pair) but the next char is missing or is not the matching low surrogate. Since the pair cannot be encoded as a valid Unicode scalar, it throws ArgumentException rather than emit malformed JSON.","triggerScenarios":"Calling JsonFormatter.Format on a message whose string field contains a lone/stray high surrogate — typically from corrupted input, byte-level string surgery, decoding UTF-8 bytes as UTF-16 incorrectly, or truncating a string mid-surrogate-pair.","commonSituations":"Strings truncated with Substring/StringBuilder at arbitrary byte counts; data decoded with the wrong encoding; binary data stored in string fields; interop with systems producing malformed UTF-16.","solutions":["Fix the source data so string fields contain well-formed UTF-16 (validate with char.IsSurrogatePair before assigning)","Validate/repair strings before setting them on the message (e.g. Encoding.UTF8 round-trip with throwOnInvalidBytes)","Check any truncation logic and cut on char boundaries, never mid pair","Catch ArgumentException in the serialization boundary and log/replace the offending field"],"exampleFix":"// before\nstring bad = raw.Substring(0, 10); // may split a surrogate pair\nmsg.Name = bad;\n// after\nstring safe = new string(raw.Substring(0, 10).Where((c, i) => !char.IsHighSurrogate(c) || i + 1 < 10 && char.IsLowSurrogate(raw[i + 1])).ToArray());\nmsg.Name = safe;","handlingStrategy":"validation","validationCode":"static bool IsWellFormedUtf16(string s) { for (int i = 0; i < s.Length; i++) { if (char.IsHighSurrogate(s[i])) { if (i + 1 >= s.Length || !char.IsLowSurrogate(s[i + 1])) return false; i++; } else if (char.IsLowSurrogate(s[i])) return false; } return true; }","typeGuard":"static bool IsWellFormedUtf16(string s) { for (int i = 0; i < s.Length; i++) { if (char.IsHighSurrogate(s[i])) { if (i + 1 >= s.Length || !char.IsLowSurrogate(s[i + 1])) return false; i++; } else if (char.IsLowSurrogate(s[i])) return false; } return true; }","tryCatchPattern":"try { return formatter.Format(message); } catch (ArgumentException ex) when (ex.Message.Contains(\"low surrogate\")) { log.Warn(\"Message contains malformed surrogate pair\"); return null; }","preventionTips":["Validate strings decoded from external bytes are well-formed UTF-16","Never truncate strings at arbitrary indexes; respect surrogate boundaries","Decode network payloads with the correct encoding instead of raw char casts"],"tags":["protobuf","json-serialization","utf-16","surrogate-pair","string-validation"],"backgroundTag":"invalid-argument-format","analyzedSha":"016f98412ea5e11756b286736f479b66ab6216d5","analyzedAt":"2026-09-12T01:08:58.711Z","contentChangedAt":"2026-09-12T01:08:58.711Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}