use super::*;
#[derive(Debug, Default, Clone)]
pub struct ParamAnnotation {
pub in_param: bool,
pub out_param: bool,
pub optional: bool,
pub retval: bool,
pub reserved: bool,
pub com_out_ptr: bool,
pub com_out_ptr_token: bool,
pub null_terminated: bool,
pub size: Option<SalSize>,
pub array: Option<ArrayInfo>,
}
impl ParamAnnotation {
pub fn is_annotated(&self) -> bool {
self.in_param
|| self.out_param
|| self.optional
|| self.retval
|| self.reserved
|| self.com_out_ptr
|| self.size.is_some()
|| self.array.is_some()
}
}
#[derive(Debug, Clone)]
pub enum SalSizeArg {
Const(i32),
Name(String),
}
#[derive(Debug, Clone)]
pub struct SalSize {
pub bytes: bool,
pub arg: SalSizeArg,
}
#[derive(Debug, Clone)]
pub enum ArrayInfo {
CountParamIndex(i16),
CountConst(i32),
BytesParamIndex(i16),
}
#[derive(Debug, Default)]
pub struct MethodAnnotation {
pub is_propget: bool,
pub is_propput: bool,
}
pub fn extract_method_annotation(
tokens: &[(CXTokenKind, String)],
method_name: &str,
) -> MethodAnnotation {
let mut annotation = MethodAnnotation::default();
for (kind, spelling) in tokens {
if *kind == CXToken_Identifier && spelling == method_name {
break;
}
if *kind == CXToken_Comment {
if spelling.contains("[propget]") {
annotation.is_propget = true;
}
if spelling.contains("[propput]") {
annotation.is_propput = true;
}
}
}
annotation
}
pub fn extract_param_annotation(cursor: &Cursor, tu: &TranslationUnit) -> ParamAnnotation {
let mut annotation = ParamAnnotation::default();
for child in cursor.children() {
match child.kind() {
CXCursor_AnnotateAttr => {
let spelling = child.name();
let (name, arg) = split_sal_annotation(&spelling);
apply_sal_string(name, &mut annotation);
if annotation.size.is_none() {
annotation.size = capture_sal_size(name, arg);
}
}
CXCursor_UnexposedAttr => {
let tokens = tu.tokenize(tu.to_expansion_range(child.extent()));
for (kind, spelling) in &tokens {
if *kind == CXToken_Identifier {
apply_sal_string(spelling, &mut annotation);
}
}
}
_ => {}
}
}
annotation
}
fn split_sal_annotation(s: &str) -> (&str, Option<&str>) {
match s.find('(') {
Some(open) if s.ends_with(')') => (&s[..open], Some(&s[open + 1..s.len() - 1])),
_ => (s, None),
}
}
fn capture_sal_size(name: &str, arg: Option<&str>) -> Option<SalSize> {
let bytes = sal_size_kind(name)?;
let first = arg?.split(',').next()?.trim();
Some(SalSize {
bytes,
arg: parse_size_arg(first)?,
})
}
fn sal_size_kind(name: &str) -> Option<bool> {
let is_size =
name.contains("_reads_") || name.contains("_writes_") || name.contains("_updates_");
is_size.then(|| name.contains("_bytes"))
}
fn parse_size_arg(s: &str) -> Option<SalSizeArg> {
let s = s.trim();
if let Some(n) = parse_int_literal(s) {
Some(SalSizeArg::Const(n))
} else {
let name = s.trim_start_matches('*').trim();
is_c_identifier(name).then(|| SalSizeArg::Name(name.to_string()))
}
}
fn parse_int_literal(s: &str) -> Option<i32> {
let t = s.trim_end_matches(['u', 'U', 'l', 'L']);
if let Some(hex) = t.strip_prefix("0x").or_else(|| t.strip_prefix("0X")) {
i32::from_str_radix(hex, 16).ok()
} else {
t.parse::<i32>().ok()
}
}
fn is_c_identifier(s: &str) -> bool {
let mut chars = s.chars();
match chars.next() {
Some(c) if c == '_' || c.is_ascii_alphabetic() => {}
_ => return false,
}
chars.all(|c| c == '_' || c.is_ascii_alphanumeric())
}
fn is_void_double_ptr(ty: &metadata::Type) -> bool {
let mut depth = 0usize;
let mut cur = ty;
loop {
match cur {
metadata::Type::PtrMut(inner, d) => {
depth += *d;
cur = inner;
}
metadata::Type::Void => return depth == 2,
_ => return false,
}
}
}
const IID_SELECTOR_PARAM_NAMES: [&str; 3] = ["riid", "iid", "riidltf"];
fn is_hresult(ty: &metadata::Type) -> bool {
matches!(ty, metadata::Type::ValueName(tn) if tn.name == "HRESULT")
}
fn is_const_guid_ptr(ty: &metadata::Type) -> bool {
matches!(ty, metadata::Type::PtrConst(inner, 1)
if matches!(inner.as_ref(), metadata::Type::ValueName(tn) if tn.name == "GUID"))
}
fn is_base_interface_out_ptr(ty: &metadata::Type) -> bool {
let metadata::Type::PtrMut(inner, 1) = ty else {
return false;
};
matches!(inner.as_ref(),
metadata::Type::ClassName(tn) | metadata::Type::ValueName(tn)
if tn.name == "IUnknown" || tn.name == "IInspectable")
}
pub(crate) fn infer_iid_is(params: &mut [Param], return_type: &metadata::Type) {
if !is_hresult(return_type) {
return;
}
let has_iid_selector = params
.iter()
.any(|p| IID_SELECTOR_PARAM_NAMES.contains(&p.name.as_str()) && is_const_guid_ptr(&p.ty));
if !has_iid_selector {
return;
}
for param in params.iter_mut() {
if !param.annotation.com_out_ptr
&& !param.annotation.in_param
&& param.annotation.array.is_none()
&& param.annotation.size.is_none()
&& (is_void_double_ptr(¶m.ty) || is_base_interface_out_ptr(¶m.ty))
{
param.annotation.com_out_ptr = true;
param.annotation.out_param = true;
param.ty = metadata::Type::PtrMut(
Box::new(metadata::Type::PtrMut(Box::new(metadata::Type::Void), 1)),
1,
);
}
}
}
pub(crate) fn parse_params(
cursor: &Cursor,
midl_annotations: &[ParamAnnotation],
parser: &mut Parser<'_>,
) -> Vec<Param> {
let mut params = vec![];
let mut param_idx = 0usize;
for child in cursor.children() {
if child.kind() != CXCursor_ParmDecl {
continue;
}
let mut name = child.name();
if name.is_empty() || is_midl_synthetic_param_name(&name) {
name = format!("param{param_idx}");
}
let sal_annotation = extract_param_annotation(&child, parser.tu);
let mut annotation = if sal_annotation.is_annotated() {
sal_annotation
} else {
midl_annotations.get(param_idx).cloned().unwrap_or_default()
};
let mut ty = param_metadata_type(&child.ty(), &annotation, parser);
if annotation.com_out_ptr_token && is_void_double_ptr(&ty) {
annotation.com_out_ptr = true;
}
if annotation.com_out_ptr {
ty = metadata::Type::PtrMut(
Box::new(metadata::Type::PtrMut(Box::new(metadata::Type::Void), 1)),
1,
);
}
if annotation.size.is_none()
&& (annotation.in_param
|| annotation.out_param
|| matches!(ty, metadata::Type::PtrConst(..)))
&& let Some(n) = inline_array_param_count(&child.ty())
{
annotation.array = Some(ArrayInfo::CountConst(n));
}
param_idx += 1;
params.push(Param {
name,
ty,
annotation,
});
}
resolve_param_array_info(&mut params);
params
}
pub fn resolve_param_array_info(params: &mut [Param]) {
let index_of: HashMap<&str, i16> = params
.iter()
.enumerate()
.map(|(i, p)| (p.name.as_str(), i as i16))
.collect();
let resolved: Vec<Option<ArrayInfo>> = params
.iter()
.map(|p| {
p.annotation.size.as_ref().and_then(|size| match &size.arg {
SalSizeArg::Const(n) if !size.bytes => Some(ArrayInfo::CountConst(*n)),
SalSizeArg::Const(_) => None,
SalSizeArg::Name(name) => index_of.get(name.as_str()).map(|&idx| {
if size.bytes {
ArrayInfo::BytesParamIndex(idx)
} else {
ArrayInfo::CountParamIndex(idx)
}
}),
})
})
.collect();
for (p, info) in params.iter_mut().zip(resolved) {
if info.is_some() {
p.annotation.array = info;
}
}
}
pub fn scan_method_param_annotations(
tokens: &[(CXTokenKind, String)],
method_name: &str,
macro_defs: &HashMap<String, Vec<String>>,
) -> Vec<ParamAnnotation> {
let mut result = Vec::new();
let mut current = ParamAnnotation::default();
let mut past_name = false;
let mut paren_depth: i32 = 0;
let mut in_params = false;
for (kind, spelling) in tokens {
if !past_name {
if *kind == CXToken_Identifier && spelling == method_name {
past_name = true;
}
continue;
}
match (*kind, spelling.as_str()) {
(CXToken_Punctuation, "(") => {
paren_depth += 1;
if paren_depth == 1 {
in_params = true;
current = ParamAnnotation::default();
}
}
(CXToken_Punctuation, ")") => {
if paren_depth > 0 {
paren_depth -= 1;
}
if paren_depth == 0 && in_params {
if !result.is_empty() || current.is_annotated() {
result.push(current.clone());
}
break;
}
}
(CXToken_Punctuation, ",") if in_params && paren_depth == 1 => {
result.push(current.clone());
current = ParamAnnotation::default();
}
(CXToken_Comment, s) if in_params && paren_depth == 1 => {
apply_midl_param_comment(s, &mut current);
}
(CXToken_Identifier, "IN")
if in_params
&& paren_depth == 1
&& macro_defs.get("IN").is_some_and(Vec::is_empty) =>
{
current.in_param = true;
}
(CXToken_Identifier, "OUT")
if in_params
&& paren_depth == 1
&& macro_defs.get("OUT").is_some_and(Vec::is_empty) =>
{
current.out_param = true;
}
(CXToken_Identifier, "OPTIONAL")
if in_params
&& paren_depth == 1
&& macro_defs.get("OPTIONAL").is_some_and(Vec::is_empty) =>
{
current.optional = true;
}
(CXToken_Identifier, s)
if in_params && paren_depth == 1 && s.starts_with("_COM_Outptr_") =>
{
current.out_param = true;
current.com_out_ptr_token = true;
if s.starts_with("_COM_Outptr_opt") {
current.optional = true;
}
}
(CXToken_Identifier, s)
if in_params
&& paren_depth == 1
&& matches!(
s,
"_In_z_" | "_In_opt_z_" | "_Out_z_" | "_Inout_z_" | "_Inout_opt_z_"
) =>
{
apply_sal_string(s, &mut current);
}
_ => {}
}
}
result
}
pub fn apply_midl_param_comment(comment: &str, annotation: &mut ParamAnnotation) {
if comment.contains("[in]") {
annotation.in_param = true;
}
if comment.contains("[out]") {
annotation.out_param = true;
}
if comment.contains("[retval]") {
annotation.retval = true;
}
if comment.contains("[optional]") {
annotation.optional = true;
}
if comment.contains("[iid_is]") && annotation.out_param {
annotation.com_out_ptr = true;
}
}
fn apply_sal_string(sal: &str, annotation: &mut ParamAnnotation) {
if sal.starts_with("_In_") || sal.starts_with("_Inout_") {
annotation.in_param = true;
}
if sal.starts_with("_Out_")
|| sal.starts_with("_Outptr_")
|| sal.starts_with("_COM_Outptr_")
|| sal.starts_with("_Inout_")
{
annotation.out_param = true;
}
if sal.contains("_opt_") || (sal.starts_with("_Outptr_") && sal.contains("_result_maybenull_"))
{
annotation.optional = true;
}
if sal == "_Reserved_" {
annotation.reserved = true;
}
if matches!(
sal,
"_In_z_" | "_In_opt_z_" | "_Out_z_" | "_Inout_z_" | "_Inout_opt_z_"
) {
annotation.null_terminated = true;
}
if sal.starts_with("_COM_Outptr_") {
annotation.com_out_ptr = true;
}
}
pub fn param_attrs_for_annotation(
annotation: &ParamAnnotation,
ty: &metadata::Type,
) -> Vec<TokenStream> {
if !annotation.is_annotated() {
return vec![];
}
let in_param = annotation.in_param;
let out_param = annotation.out_param;
let optional = annotation.optional;
let retval = annotation.retval;
let is_mutable = matches!(ty, metadata::Type::RefMut(_) | metadata::Type::PtrMut(..));
let mut attrs = vec![];
if let Some(array) = &annotation.array {
attrs.push(array_info_attr(array));
}
if annotation.reserved {
attrs.push(quote! { #[reserved] });
}
if annotation.com_out_ptr {
attrs.push(quote! { #[iid_is] });
}
if in_param && (out_param || is_mutable) {
attrs.push(quote! { #[r#in] });
}
if out_param && (in_param || !is_mutable) {
attrs.push(quote! { #[out] });
}
if optional {
attrs.push(quote! { #[opt] });
}
if retval {
attrs.push(quote! { #[retval] });
}
attrs
}
pub fn detect_does_not_return(cursor: &Cursor) -> bool {
if cursor.ty().spelling().contains("noreturn") {
return true;
}
cursor
.children()
.iter()
.any(|c| c.kind() == CXCursor_AnnotateAttr && c.name() == "_Analysis_noreturn_")
}
pub fn does_not_return_attr() -> TokenStream {
quote! { #[noreturn] }
}
fn array_info_attr(info: &ArrayInfo) -> TokenStream {
let (name, lit) = match info {
ArrayInfo::CountParamIndex(i) => ("len_param", Literal::i16_unsuffixed(*i)),
ArrayInfo::CountConst(n) => ("len_const", Literal::i32_unsuffixed(*n)),
ArrayInfo::BytesParamIndex(i) => ("size_param", Literal::i16_unsuffixed(*i)),
};
let name = write_ident(name);
quote! { #[#name(#lit)] }
}