blob: c5c77832c6218657293e05a366f4699138fd1d77 [file]
// Protocol Buffers - Google's data interchange format
// Copyright 2026 Google LLC. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
use core::ffi::{c_int, CStr};
use core::marker::PhantomData;
// In Bazel `upb` and `reflection` are dependency crates. In Cargo this file is compiled as
// <crate_root>::upb::reflection, so bring the parent and sibling modules into scope under the same
// names.
#[cfg(not(bzl))]
use super::super::upb;
#[cfg(not(bzl))]
use super::sys::reflection;
use reflection::def_pool::{
upb_DefPool_FindMessageByNameWithSize, upb_DefPool_Free, upb_DefPool_Init_New,
upb_DefPool_LoadDefInit, upb_DefPool_New, upb_MiniTableFile_New, RawDefPool,
};
use reflection::message_def::RawMessageDef;
use reflection::upb_TextEncode;
use upb::{
Arena, MessagePtr, RawMiniTable, RawMiniTableEnum, RawMiniTableExtension, StringView,
TextEncodeOptions,
};
pub use reflection::def_pool::upb_DefPool_Init;
pub use reflection::def_pool::RawDefPoolInit as DefPoolInitPtr;
pub use reflection::def_pool::RawMiniTableFile as MiniTableFilePtr;
/// A wrapper over a `upb_DefPool`.
///
/// This is an owning type and will automatically free the pool when
/// dropped.
pub struct DefPool {
raw: RawDefPool,
}
impl Default for DefPool {
fn default() -> Self {
Self::new()
}
}
impl DefPool {
/// Allocates a new pool.
#[inline]
pub fn new() -> Self {
// SAFETY: `upb_DefPool_New` has no preconditions.
let raw = unsafe { upb_DefPool_New() }.expect("DefPool allocation failed");
Self { raw }
}
/// Loads a generated descriptor, and everything it imports, into the pool.
///
/// # Safety
/// - `init` must point to a valid `upb_DefPool_Init`, and so must every init reachable from it
/// through its dependencies according to the correct orderings and counts.
pub unsafe fn load_def_init(&mut self, init: *const upb_DefPool_Init) -> bool {
// SAFETY:
// - `self.raw` is a live pool; only `Drop` frees it.
// - `init` is valid per the safety requirements of this function.
unsafe { upb_DefPool_LoadDefInit(self.raw, init) }
}
/// Looks up a message by fully-qualified name in the pool.
pub fn find_message_by_name(&self, full_name: &str) -> Option<MessageDef<'_>> {
// SAFETY:
// - `self.raw` is a live pool; only `Drop` frees it.
// - `full_name.as_ptr()` is readable for `full_name.len()` bytes.
let raw = unsafe {
upb_DefPool_FindMessageByNameWithSize(
self.raw,
full_name.as_ptr().cast(),
full_name.len(),
)
}?;
// SAFETY: `raw` was allocated by `self`, and the returned `MessageDef` borrows `self`.
Some(unsafe { MessageDef::from_raw(raw) })
}
/// Returns the internal NonNull representation of the pool.
#[inline]
pub(crate) fn raw(&self) -> RawDefPool {
self.raw
}
}
impl Drop for DefPool {
fn drop(&mut self) {
// SAFETY: The pointer was generated by upb_DefPool_New and hasn't been freed.
unsafe { upb_DefPool_Free(self.raw) }
}
}
/// A borrowed message descriptor tied to the lifetime of a DefPool. No drop implementation is
/// provided because this is not an owning type and cannot be individually freed.
#[derive(Copy, Clone)]
pub struct MessageDef<'pool> {
raw: RawMessageDef,
_marker: PhantomData<&'pool DefPool>,
}
impl<'pool> MessageDef<'pool> {
/// # Safety
/// The caller must guarantee `raw` was allocated by the pool providing the `'pool` lifetime.
pub unsafe fn from_raw(raw: RawMessageDef) -> Self {
Self { raw, _marker: PhantomData }
}
/// Returns the internal NonNull representation of the descriptor.
#[inline]
pub fn raw(&self) -> RawMessageDef {
self.raw
}
}
/// Returns a string of field number to value entries of a message.
///
/// # Safety
/// - `msg` must be legally dereferenceable.
pub unsafe fn text_encode<'pool, T>(
msg: MessagePtr<T>,
def: MessageDef<'pool>,
ext_pool: Option<&DefPool>,
options: TextEncodeOptions,
) -> String {
let msg = msg.raw();
let def_raw = def.raw();
let ext_pool_raw = ext_pool.map(|p| p.raw());
let options = options.to_bits();
// Only find out the length first to then allocate a buffer of the minimum size
// needed.
// SAFETY:
// - `msg` is valid per safety requirements of this function.
// - `def` is valid per safety requirements of this function.
// - `ext_pool` is valid per safety requirements of this function.
// - `buf` is null and `size` is 0.
let min_len =
unsafe { upb_TextEncode(msg, def_raw, ext_pool_raw, options, core::ptr::null_mut(), 0) };
assert!(min_len < isize::MAX as usize);
// +1 for the trailing NULL
let mut buf = vec![0u8; min_len + 1];
// SAFETY:
// - `msg`, `def`, `ext_pool` are valid per safety requirements of this function.
// - `buf` is legally writable for `buf.len()` bytes.
let written_len =
unsafe { upb_TextEncode(msg, def_raw, ext_pool_raw, options, buf.as_mut_ptr(), buf.len()) };
assert_eq!(min_len, written_len);
// Drop the trailing NULL written by `upb_TextEncode`.
buf.truncate(written_len);
String::from_utf8_lossy(buf.as_slice()).into_owned()
}
fn copy_slice_or_null<T: Copy>(arena: &Arena, slice: &[T]) -> *const T {
if slice.is_empty() {
return core::ptr::null();
}
arena.copy_slice_in(slice).expect("arena allocation failed").as_ptr()
}
/// Builds a `upb_MiniTableFile` in `arena` from the MiniTables of one .proto file.
pub unsafe fn build_mini_table_file(
arena: &Arena,
msgs: &[RawMiniTable],
enums: &[RawMiniTableEnum],
exts: &[RawMiniTableExtension],
) -> MiniTableFilePtr {
// Copy the minitables that msgs, enums and exts point to into the arena.
let msgs_ptr = copy_slice_or_null(arena, msgs);
let enums_ptr = copy_slice_or_null(arena, enums);
let exts_ptr = copy_slice_or_null(arena, exts);
// SAFETY:
// - `arena` is live for the duration of the call.
// - each array is readable for the count passed alongside it.
unsafe {
upb_MiniTableFile_New(
arena.raw(),
msgs_ptr,
msgs.len() as c_int,
enums_ptr,
enums.len() as c_int,
exts_ptr,
exts.len() as c_int,
)
}
.expect("arena allocation failed")
}
/// Builds a upb_DefPool_Init
///
/// # Safety
/// - `layout` and every init in `deps` must outlive any `DefPool` this init is loaded into, as must
/// `arena`: upb keeps pointing at them rather than copying them.
pub unsafe fn build_def_pool_init(
arena: &Arena,
filename: &'static CStr,
descriptor: &'static [u8],
deps: &[DefPoolInitPtr],
layout: MiniTableFilePtr,
) -> DefPoolInitPtr {
// Add a `None` to the end of the deps to terminate the list.
let mut deps_terminated: Vec<Option<DefPoolInitPtr>> = Vec::with_capacity(deps.len() + 1);
for dep in deps {
deps_terminated.push(Some(*dep));
}
deps_terminated.push(None);
let deps_ptr = arena.copy_slice_in(&deps_terminated).expect("arena allocation failed").as_ptr();
// SAFETY:
// - `arena` is live for the duration of the call.
// - `filename` and `descriptor` are readable for 'static.
// - `deps_ptr` points at a null-terminated array. It is cast to a mutable pointer only to match
// the C signature; upb never writes through it.
unsafe {
upb_DefPool_Init_New(
arena.raw(),
filename.as_ptr(),
StringView::from(descriptor),
deps_ptr as *mut Option<DefPoolInitPtr>,
layout.as_ptr(),
)
}
.expect("arena allocation failed")
}