openlang

Notes on a buffer that expires at the next call

A C library hands back a pointer and a length. What's known so far. For a single-threaded call sequence the buffer is readable and valid until the next library call. No ownership transfer. What isn't known. Whether another handle, a callback or a background thread can invalidate it. The docs don't say.

A pointer to const bytes only rules out mutation through that one access path. It says nothing about lifetime, and the bytes can still change through another path. So keeping the library handle alive may be necessary and still not enough.

Option one. Copy into an owned Vec<u8> before any other call is allowed. The pointer and length have to describe a valid readable range for the whole copy, with no conflicting foreign write. Copying can't fix a pointer that was already bad.

Option two. A borrowed view, which has to block every call that could invalidate it for as long as the view lives. Tying the borrow to one handle only works if all invalidation goes through that handle. Shared global storage in the library means other handles matter too. Concurrent calls and reentrant callbacks belong on the same list.

I'm starting with the copy and writing down the foreign guarantees it still leans on. They stay assumptions until there's evidence from the C side.

后续讨论 5

顺着彼此的依据读下去
  1. 固定链接

    Name the object that owns the library handle while the copy runs. The bytes being copied belong to the library and are reached through that handle, so whatever owns the handle has to outlive the copy and must not release it on any path the copy can leave by. Put the handle in a field with its own Drop and have the copy borrow that field. Then the borrow checker holds the order for you. Whether the source buffer stayed valid during the copy is untouched by any of this.

  2. 固定链接

    Keeping the handle alive is the easy half, and in Swift it can happen by accident. A class owns the handle plus a stored callback that captures self strongly. The cycle keeps everything alive, and the next library call still invalidates the bytes. Capture self weakly and the class can go away, which leaves a callback held somewhere else with nothing to call into, so it needs a plan for finding nil.

  3. 固定链接

    Holding the view across an .await is where option two falls apart for me, and I doubt async Rust has a clean answer. The suspended future keeps the borrow alive the whole time it's parked. The borrow checker only stops calls through the handle you borrowed from. Another task on the same executor can call into the library through a different handle while yours is parked, and if invalidation goes through shared library state, nothing in the types notices. If the future is Send, it may also resume on another thread, which brings your concurrency item back. And if the view points into a buffer the future itself owns, it's self-referential and you're in Pin territory. So I'd copy before the first await.

  4. 固定链接

    Option one still has an unsafe block, and the assumption belongs on it, where grep finds it.

    fn take_name(h: &mut Handle) -> Vec<u8> {
        let mut len = 0usize;
        // SAFETY: valid until the next call on ANY handle.
        // `&mut` only rules out calls through this one.
        unsafe {
            let p = lib_name(h.raw, &mut len);
            if p.is_null() {
                return Vec::new();
            }
            std::slice::from_raw_parts(p, len).to_vec()
        }
    }

    The comment says ANY because that's the part you haven't proven. If the C side ever documents per-handle storage, change the comment first and the signature second. The null check is not optional either. from_raw_parts wants a non-null pointer even when the length is zero.

同一个问题的其他角度

  1. What the canary check looks like at -O2

    Three small C functions, gcc -O2 -fstack-protector-strong -S -masm=intel, x86-64 Linux. read_line and read_count get the same…

    回复 1最近活动 assemblyc
  2. Callbacks from C lose the arena I set up

    A C library calls back into my Odin code, and the callback can't see the context I set up before registering it. A proc "c" g…

    回复 8最近活动 odinc
  3. Clearing one status flag with |= cleared all of them

    |= on a write-1-to-clear status register clears more than the bit you named. The overrun flag clears by writing 1 to it. Writ…

    回复 5最近活动 c