nautechsystems/nautilus_trader · error

C string contains invalid JSON

Error message

C string contains invalid JSON

What it means

A panic in `bytes_to_string_vec` (crates/core/src/ffi/parsing.rs): the function receives a C string pointer from the PyO3/C boundary, decodes UTF-8, then parses it as JSON with `serde_json::from_str(...).expect("C string contains invalid JSON")`. If the C string is not valid JSON, the process panics. This is a boundary invariant: `string_vec_to_bytes` always emits JSON, so anything else indicates corrupted or mismatched FFI data.

Source

Thrown at crates/core/src/ffi/parsing.rs:61

/// # Safety
///
/// Assumes `ptr` is a valid C string pointer.
///
/// # Panics
///
/// Panics if `ptr` is null, contains invalid UTF-8/JSON, or the JSON value
/// is not an array of strings.
#[must_use]
pub unsafe fn bytes_to_string_vec(ptr: *const c_char) -> Vec<String> {
    assert!(!ptr.is_null(), "`ptr` was NULL");

    // SAFETY: Caller guarantees ptr is valid per function contract
    let c_str = unsafe { CStr::from_ptr(ptr) };
    let bytes = c_str.to_bytes();

    let json_string = std::str::from_utf8(bytes).expect("C string contains invalid UTF-8");
    let value: serde_json::Value =
        serde_json::from_str(json_string).expect("C string contains invalid JSON");

    let arr = value
        .as_array()
        .expect("C string JSON must be an array of strings");

    arr.iter()
        .map(|value| {
            value
                .as_str()
                .expect("C string JSON array must contain only strings")
                .to_owned()
        })
        .collect()
}

/// Convert a slice of `String` into a C string pointer (JSON encoded).
///
/// # Panics

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Ensure the buffer comes from `string_vec_to_bytes` (JSON-encoded array of strings) and is passed unmodified
  2. Print/decode the bytes with `std::str::from_utf8` and a JSON parser to inspect the actual payload before the call
  3. Check for buffer truncation or double-free style bugs at the FFI boundary
  4. In tests, use `string_vec_to_bytes` to build the input rather than crafting raw C strings

Example fix

// before
let ptr = CString::new("not json").unwrap().into_raw();
let v = bytes_to_string_vec(ptr); // panics
// after
let ptr = string_vec_to_bytes(&vec!["a".to_string(), "b".to_string()]);
let v = bytes_to_string_vec(ptr); // OK
Defensive patterns

Strategy: validation

Validate before calling

let text = unsafe { CStr::from_ptr(ptr) }.to_str()?;
serde_json::from_str::<serde_json::Value>(text)?; // validate before calling

Type guard

fn is_json_string_array(bytes: &[u8]) -> bool {
    std::str::from_utf8(bytes).ok()
        .and_then(|s| serde_json::from_str::<serde_json::Value>(s).ok())
        .map_or(false, |v| v.is_array())
}

Try / catch

// wrap unsafe boundary in catch_unwind and inspect payload on failure
let result = std::panic::catch_unwind(|| bytes_to_string_vec(ptr));
match result { Ok(v) => v, Err(_) => { log_payload(ptr); fallback_vec() } }

Prevention

When it happens

Trigger: Calling `bytes_to_string_vec` (directly or via `synthetic_instrument_new` / `synthetic_instrument_is_valid_formula`) with a pointer to bytes that are valid UTF-8 but not valid JSON — e.g. a bare string, a truncated buffer, or data not produced by `string_vec_to_bytes`.

Common situations: Hand-rolling C FFI calls that pass non-JSON payloads; memory truncation of the buffer before the terminating null; version mismatch where one side of the boundary encodes differently; tests feeding malformed input.

Understand the failure class

Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/8e2b0c1c58c19b3e. Report an issue: GitHub.