schemagen: escape control characters in generated C++ string literals
CI / pre-commit (pull_request) Successful in 53s
CI / build (-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++, clang-arm64, ubuntu-latest-arm64, true) (pull_request) Successful in 53s
CI / build (-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++, clang-amd64, ubuntu-latest-amd64, true) (pull_request) Successful in 1m32s
CI / build (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc-arm64, ubuntu-latest-arm64, false) (pull_request) Successful in 52s
CI / build (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc-amd64, ubuntu-latest-amd64, false) (pull_request) Successful in 1m28s
CI / pre-commit (pull_request) Successful in 53s
CI / build (-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++, clang-arm64, ubuntu-latest-arm64, true) (pull_request) Successful in 53s
CI / build (-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++, clang-amd64, ubuntu-latest-amd64, true) (pull_request) Successful in 1m32s
CI / build (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc-arm64, ubuntu-latest-arm64, false) (pull_request) Successful in 52s
CI / build (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc-amd64, ubuntu-latest-amd64, false) (pull_request) Successful in 1m28s
Fixes #35. Add a helper to escape C++ string literals so that JSON control characters (\n, \r, \t, and other bytes below 0x20) are emitted as escape sequences instead of raw bytes. Use it for: - field comments that include the JSON property key - object key comparison literals in matchKey() - enum name arrays Also add regression tests that generate and syntax-check headers for schemas containing newlines and other control characters in property keys and enum values.
This commit is contained in:
@@ -18,6 +18,43 @@ import keyword
|
||||
import sys
|
||||
|
||||
|
||||
def _escape_cpp_string(s):
|
||||
"""Return *s* escaped for use inside a C++ double-quoted string literal.
|
||||
|
||||
JSON strings may contain control characters; emitting them verbatim into
|
||||
generated C++ source breaks tokenization. This helper escapes backslashes
|
||||
and double quotes, maps common control characters to their short escape
|
||||
sequences, and uses universal character names (\\u00XX) for any other
|
||||
character below 0x20.
|
||||
"""
|
||||
out = []
|
||||
for ch in s:
|
||||
cp = ord(ch)
|
||||
if cp == 0x09:
|
||||
out.append("\\t")
|
||||
elif cp == 0x0A:
|
||||
out.append("\\n")
|
||||
elif cp == 0x0B:
|
||||
out.append("\\v")
|
||||
elif cp == 0x0C:
|
||||
out.append("\\f")
|
||||
elif cp == 0x0D:
|
||||
out.append("\\r")
|
||||
elif cp == 0x08:
|
||||
out.append("\\b")
|
||||
elif cp == 0x07:
|
||||
out.append("\\a")
|
||||
elif cp < 0x20:
|
||||
out.append(f"\\u{cp:04X}")
|
||||
elif ch == "\\":
|
||||
out.append("\\\\")
|
||||
elif ch == '"':
|
||||
out.append('\\"')
|
||||
else:
|
||||
out.append(ch)
|
||||
return "".join(out)
|
||||
|
||||
|
||||
class GenError(Exception):
|
||||
pass
|
||||
|
||||
@@ -618,7 +655,8 @@ namespace {ns} {{"""
|
||||
and f.ty.kind in ("int", "dbl", "bool")
|
||||
):
|
||||
init = " = 0" if f.ty.kind != "bool" else " = false"
|
||||
out.append(f' {store} {f.cpp}{init}; // "{f.key}"')
|
||||
esc_key = _escape_cpp_string(f.key)
|
||||
out.append(f' {store} {f.cpp}{init}; // "{esc_key}"')
|
||||
out.append("};")
|
||||
out.append("")
|
||||
return "\n".join(out)
|
||||
@@ -746,7 +784,7 @@ namespace {ns} {{"""
|
||||
for name, obj in self.b.objects.items():
|
||||
lines.append(f" case Kind::{name}:")
|
||||
for i, fld in enumerate(obj.fields):
|
||||
esc = fld.key.replace("\\", "\\\\").replace('"', '\\"')
|
||||
esc = _escape_cpp_string(fld.key)
|
||||
lines.append(f' if (key == "{esc}") return {i};')
|
||||
lines.append(" return -1;")
|
||||
lines.append(" default: return -1;")
|
||||
@@ -829,10 +867,7 @@ namespace {ns} {{"""
|
||||
def _enum_name_arrays(self):
|
||||
out = []
|
||||
for e in self.b.enums.values():
|
||||
lits = ", ".join(
|
||||
'"' + v.replace("\\", "\\\\").replace('"', '\\"') + '"'
|
||||
for v in e.values
|
||||
)
|
||||
lits = ", ".join('"' + _escape_cpp_string(v) + '"' for v in e.values)
|
||||
out.append(
|
||||
f" static constexpr const char *{e.name}_names[] = {{ {lits} }};"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user