blob: 93cbe3b6459ce1ef2b8054311a55bb7895e9a711 [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 super::upb_reflection::{self, DefPool, DefPoolInitPtr, MessageDef};
use super::{MiniTableEnumPtr, MiniTableExtensionPtr, MiniTablePtr, THREAD_LOCAL_ARENA};
use std::ffi::CStr;
use std::sync::{Mutex, OnceLock};
#[derive(Clone, Copy)]
pub struct DefPoolInit(DefPoolInitPtr);
unsafe impl Send for DefPoolInit {}
unsafe impl Sync for DefPoolInit {}
/// Holds the MessageDef of one message once `message_def` has looked it up.
#[derive(Default)]
pub struct MessageDefCached(OnceLock<MessageDef<'static>>);
unsafe impl Sync for MessageDefCached {}
impl MessageDefCached {
pub const fn new() -> Self {
Self(OnceLock::new())
}
}
struct GlobalDefPool(DefPool);
unsafe impl Send for GlobalDefPool {}
// There is only one global, singleton DefPool, protected by a Mutex.
static POOL: OnceLock<Mutex<GlobalDefPool>> = OnceLock::new();
/// A message of which a MessageDef and DefPoolInit can be looked up. This trait must be
/// implemented in Rust gencode for every message that needs reflection support.
///
/// # Safety
/// - `FULL_NAME` must be the message's fully-qualified name.
/// - `def_init()` must return the init of the .proto file that declares the message.
/// - `message_def_cached()` must return a static MessageDef.
pub unsafe trait UpbWithReflection {
const FULL_NAME: &'static str;
fn def_init() -> DefPoolInit;
// For each message type, there is only one static, cached pointer to its MessageDef.
fn message_def_cached() -> &'static MessageDefCached;
}
/// Builds a upb_DefPool_Init
///
/// # Safety
/// - `filename` and `descriptor` must be the file's name and its serialized `FileDescriptorProto`.
/// - `deps` must be the inits of the files it imports.
/// - `msgs`, `enums` and `exts` must be the file's own MiniTables, in the order upb's generator
/// lists them in a `upb_MiniTableFile` (see `SortedMessages`, `SortedEnums` and
/// `SortedExtensions` in upb_generator/file_layout.h).
pub unsafe fn build_def_init(
filename: &'static CStr,
descriptor: &'static [u8],
deps: &[DefPoolInit],
msgs: &[MiniTablePtr],
enums: &[MiniTableEnumPtr],
exts: &[MiniTableExtensionPtr],
) -> DefPoolInit {
let mut raw_deps: Vec<DefPoolInitPtr> = Vec::with_capacity(deps.len());
for dep in deps {
raw_deps.push(dep.0);
}
THREAD_LOCAL_ARENA.with(|arena| unsafe {
let layout = upb_reflection::build_mini_table_file(arena, msgs, enums, exts);
DefPoolInit(upb_reflection::build_def_pool_init(
arena, filename, descriptor, &raw_deps, layout,
))
})
}
/// Returns the MessageDef of T.
pub fn message_def<T: UpbWithReflection>() -> MessageDef<'static> {
*(T::message_def_cached().0.get_or_init(|| {
// Eventually calls `build_def_init` emitted for `T`.
let init = T::def_init();
let mut pool = POOL
.get_or_init(|| Mutex::new(GlobalDefPool(DefPool::new())))
.lock()
.expect("global DefPool mutex should not be poisoned");
// Load the descriptor into the global DefPool. If it was already loaded, this would be a
// no-op.
//
// SAFETY: `init` was only built for the file that declares `T`.
let loaded = unsafe { pool.0.load_def_init(init.0.as_ptr()) };
assert!(loaded, "failed to load a generated descriptor into the global DefPool");
let def = pool
.0
.find_message_by_name(T::FULL_NAME)
.expect("a generated message is missing from its own file's descriptor");
// SAFETY: `def` was allocated by the global pool, which is never freed.
unsafe { MessageDef::from_raw(def.raw()) }
}))
}