488 lines
16 KiB
Rust
488 lines
16 KiB
Rust
//! Intermediate representation of variables.
|
|
|
|
use super::super::codegen::MacroTypeVariation;
|
|
use super::context::{BindgenContext, TypeId};
|
|
use super::dot::DotAttributes;
|
|
use super::function::cursor_mangling;
|
|
use super::int::IntKind;
|
|
use super::item::Item;
|
|
use super::ty::{FloatKind, TypeKind};
|
|
use crate::callbacks::{ItemInfo, ItemKind, MacroParsingBehavior};
|
|
use crate::clang;
|
|
use crate::clang::ClangToken;
|
|
use crate::parse::{ClangSubItemParser, ParseError, ParseResult};
|
|
|
|
use std::io;
|
|
use std::num::Wrapping;
|
|
|
|
/// The type for a constant variable.
|
|
#[derive(Debug)]
|
|
pub(crate) enum VarType {
|
|
/// A boolean.
|
|
Bool(bool),
|
|
/// An integer.
|
|
Int(i64),
|
|
/// A floating point number.
|
|
Float(f64),
|
|
/// A character.
|
|
Char(u8),
|
|
/// A string, not necessarily well-formed utf-8.
|
|
String(Vec<u8>),
|
|
}
|
|
|
|
/// A `Var` is our intermediate representation of a variable.
|
|
#[derive(Debug)]
|
|
pub(crate) struct Var {
|
|
/// The name of the variable.
|
|
name: String,
|
|
/// The mangled name of the variable.
|
|
mangled_name: Option<String>,
|
|
/// The link name of the variable.
|
|
link_name: Option<String>,
|
|
/// The type of the variable.
|
|
ty: TypeId,
|
|
/// The value of the variable, that needs to be suitable for `ty`.
|
|
val: Option<VarType>,
|
|
/// Whether this variable is const.
|
|
is_const: bool,
|
|
}
|
|
|
|
impl Var {
|
|
/// Construct a new `Var`.
|
|
pub(crate) fn new(
|
|
name: String,
|
|
mangled_name: Option<String>,
|
|
link_name: Option<String>,
|
|
ty: TypeId,
|
|
val: Option<VarType>,
|
|
is_const: bool,
|
|
) -> Var {
|
|
assert!(!name.is_empty());
|
|
Var {
|
|
name,
|
|
mangled_name,
|
|
link_name,
|
|
ty,
|
|
val,
|
|
is_const,
|
|
}
|
|
}
|
|
|
|
/// Is this variable `const` qualified?
|
|
pub(crate) fn is_const(&self) -> bool {
|
|
self.is_const
|
|
}
|
|
|
|
/// The value of this constant variable, if any.
|
|
pub(crate) fn val(&self) -> Option<&VarType> {
|
|
self.val.as_ref()
|
|
}
|
|
|
|
/// Get this variable's type.
|
|
pub(crate) fn ty(&self) -> TypeId {
|
|
self.ty
|
|
}
|
|
|
|
/// Get this variable's name.
|
|
pub(crate) fn name(&self) -> &str {
|
|
&self.name
|
|
}
|
|
|
|
/// Get this variable's mangled name.
|
|
pub(crate) fn mangled_name(&self) -> Option<&str> {
|
|
self.mangled_name.as_deref()
|
|
}
|
|
|
|
/// Get this variable's link name.
|
|
pub fn link_name(&self) -> Option<&str> {
|
|
self.link_name.as_deref()
|
|
}
|
|
}
|
|
|
|
impl DotAttributes for Var {
|
|
fn dot_attributes<W>(
|
|
&self,
|
|
_ctx: &BindgenContext,
|
|
out: &mut W,
|
|
) -> io::Result<()>
|
|
where
|
|
W: io::Write,
|
|
{
|
|
if self.is_const {
|
|
writeln!(out, "<tr><td>const</td><td>true</td></tr>")?;
|
|
}
|
|
|
|
if let Some(ref mangled) = self.mangled_name {
|
|
writeln!(
|
|
out,
|
|
"<tr><td>mangled name</td><td>{}</td></tr>",
|
|
mangled
|
|
)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn default_macro_constant_type(ctx: &BindgenContext, value: i64) -> IntKind {
|
|
if value < 0 ||
|
|
ctx.options().default_macro_constant_type ==
|
|
MacroTypeVariation::Signed
|
|
{
|
|
if value < i32::min_value() as i64 || value > i32::max_value() as i64 {
|
|
IntKind::I64
|
|
} else if !ctx.options().fit_macro_constants ||
|
|
value < i16::min_value() as i64 ||
|
|
value > i16::max_value() as i64
|
|
{
|
|
IntKind::I32
|
|
} else if value < i8::min_value() as i64 ||
|
|
value > i8::max_value() as i64
|
|
{
|
|
IntKind::I16
|
|
} else {
|
|
IntKind::I8
|
|
}
|
|
} else if value > u32::max_value() as i64 {
|
|
IntKind::U64
|
|
} else if !ctx.options().fit_macro_constants ||
|
|
value > u16::max_value() as i64
|
|
{
|
|
IntKind::U32
|
|
} else if value > u8::max_value() as i64 {
|
|
IntKind::U16
|
|
} else {
|
|
IntKind::U8
|
|
}
|
|
}
|
|
|
|
/// Parses tokens from a CXCursor_MacroDefinition pointing into a function-like
|
|
/// macro, and calls the func_macro callback.
|
|
fn handle_function_macro(
|
|
cursor: &clang::Cursor,
|
|
callbacks: &dyn crate::callbacks::ParseCallbacks,
|
|
) {
|
|
let is_closing_paren = |t: &ClangToken| {
|
|
// Test cheap token kind before comparing exact spellings.
|
|
t.kind == clang_sys::CXToken_Punctuation && t.spelling() == b")"
|
|
};
|
|
let tokens: Vec<_> = cursor.tokens().iter().collect();
|
|
if let Some(boundary) = tokens.iter().position(is_closing_paren) {
|
|
let mut spelled = tokens.iter().map(ClangToken::spelling);
|
|
// Add 1, to convert index to length.
|
|
let left = spelled.by_ref().take(boundary + 1);
|
|
let left = left.collect::<Vec<_>>().concat();
|
|
if let Ok(left) = String::from_utf8(left) {
|
|
let right: Vec<_> = spelled.collect();
|
|
callbacks.func_macro(&left, &right);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ClangSubItemParser for Var {
|
|
fn parse(
|
|
cursor: clang::Cursor,
|
|
ctx: &mut BindgenContext,
|
|
) -> Result<ParseResult<Self>, ParseError> {
|
|
use cexpr::expr::EvalResult;
|
|
use cexpr::literal::CChar;
|
|
use clang_sys::*;
|
|
match cursor.kind() {
|
|
CXCursor_MacroDefinition => {
|
|
for callbacks in &ctx.options().parse_callbacks {
|
|
match callbacks.will_parse_macro(&cursor.spelling()) {
|
|
MacroParsingBehavior::Ignore => {
|
|
return Err(ParseError::Continue);
|
|
}
|
|
MacroParsingBehavior::Default => {}
|
|
}
|
|
|
|
if cursor.is_macro_function_like() {
|
|
handle_function_macro(&cursor, callbacks.as_ref());
|
|
// We handled the macro, skip macro processing below.
|
|
return Err(ParseError::Continue);
|
|
}
|
|
}
|
|
|
|
let value = parse_macro(ctx, &cursor);
|
|
|
|
let (id, value) = match value {
|
|
Some(v) => v,
|
|
None => return Err(ParseError::Continue),
|
|
};
|
|
|
|
assert!(!id.is_empty(), "Empty macro name?");
|
|
|
|
let previously_defined = ctx.parsed_macro(&id);
|
|
|
|
// NB: It's important to "note" the macro even if the result is
|
|
// not an integer, otherwise we might loose other kind of
|
|
// derived macros.
|
|
ctx.note_parsed_macro(id.clone(), value.clone());
|
|
|
|
if previously_defined {
|
|
let name = String::from_utf8(id).unwrap();
|
|
duplicated_macro_diagnostic(&name, cursor.location(), ctx);
|
|
return Err(ParseError::Continue);
|
|
}
|
|
|
|
// NOTE: Unwrapping, here and above, is safe, because the
|
|
// identifier of a token comes straight from clang, and we
|
|
// enforce utf8 there, so we should have already panicked at
|
|
// this point.
|
|
let name = String::from_utf8(id).unwrap();
|
|
let (type_kind, val) = match value {
|
|
EvalResult::Invalid => return Err(ParseError::Continue),
|
|
EvalResult::Float(f) => {
|
|
(TypeKind::Float(FloatKind::Double), VarType::Float(f))
|
|
}
|
|
EvalResult::Char(c) => {
|
|
let c = match c {
|
|
CChar::Char(c) => {
|
|
assert_eq!(c.len_utf8(), 1);
|
|
c as u8
|
|
}
|
|
CChar::Raw(c) => {
|
|
assert!(c <= ::std::u8::MAX as u64);
|
|
c as u8
|
|
}
|
|
};
|
|
|
|
(TypeKind::Int(IntKind::U8), VarType::Char(c))
|
|
}
|
|
EvalResult::Str(val) => {
|
|
let char_ty = Item::builtin_type(
|
|
TypeKind::Int(IntKind::U8),
|
|
true,
|
|
ctx,
|
|
);
|
|
for callbacks in &ctx.options().parse_callbacks {
|
|
callbacks.str_macro(&name, &val);
|
|
}
|
|
(TypeKind::Pointer(char_ty), VarType::String(val))
|
|
}
|
|
EvalResult::Int(Wrapping(value)) => {
|
|
let kind = ctx
|
|
.options()
|
|
.last_callback(|c| c.int_macro(&name, value))
|
|
.unwrap_or_else(|| {
|
|
default_macro_constant_type(ctx, value)
|
|
});
|
|
|
|
(TypeKind::Int(kind), VarType::Int(value))
|
|
}
|
|
};
|
|
|
|
let ty = Item::builtin_type(type_kind, true, ctx);
|
|
|
|
Ok(ParseResult::New(
|
|
Var::new(name, None, None, ty, Some(val), true),
|
|
Some(cursor),
|
|
))
|
|
}
|
|
CXCursor_VarDecl => {
|
|
let mut name = cursor.spelling();
|
|
if cursor.linkage() == CXLinkage_External {
|
|
if let Some(nm) = ctx.options().last_callback(|callbacks| {
|
|
callbacks.generated_name_override(ItemInfo {
|
|
name: name.as_str(),
|
|
kind: ItemKind::Var,
|
|
})
|
|
}) {
|
|
name = nm;
|
|
}
|
|
}
|
|
// No more changes to name
|
|
let name = name;
|
|
|
|
if name.is_empty() {
|
|
warn!("Empty constant name?");
|
|
return Err(ParseError::Continue);
|
|
}
|
|
|
|
let link_name = ctx.options().last_callback(|callbacks| {
|
|
callbacks.generated_link_name_override(ItemInfo {
|
|
name: name.as_str(),
|
|
kind: ItemKind::Var,
|
|
})
|
|
});
|
|
|
|
let ty = cursor.cur_type();
|
|
|
|
// TODO(emilio): do we have to special-case constant arrays in
|
|
// some other places?
|
|
let is_const = ty.is_const() ||
|
|
([CXType_ConstantArray, CXType_IncompleteArray]
|
|
.contains(&ty.kind()) &&
|
|
ty.elem_type()
|
|
.map_or(false, |element| element.is_const()));
|
|
|
|
let ty = match Item::from_ty(&ty, cursor, None, ctx) {
|
|
Ok(ty) => ty,
|
|
Err(e) => {
|
|
assert!(
|
|
matches!(ty.kind(), CXType_Auto | CXType_Unexposed),
|
|
"Couldn't resolve constant type, and it \
|
|
wasn't an nondeductible auto type or unexposed \
|
|
type!"
|
|
);
|
|
return Err(e);
|
|
}
|
|
};
|
|
|
|
// Note: Ty might not be totally resolved yet, see
|
|
// tests/headers/inner_const.hpp
|
|
//
|
|
// That's fine because in that case we know it's not a literal.
|
|
let canonical_ty = ctx
|
|
.safe_resolve_type(ty)
|
|
.and_then(|t| t.safe_canonical_type(ctx));
|
|
|
|
let is_integer = canonical_ty.map_or(false, |t| t.is_integer());
|
|
let is_float = canonical_ty.map_or(false, |t| t.is_float());
|
|
|
|
// TODO: We could handle `char` more gracefully.
|
|
// TODO: Strings, though the lookup is a bit more hard (we need
|
|
// to look at the canonical type of the pointee too, and check
|
|
// is char, u8, or i8 I guess).
|
|
let value = if is_integer {
|
|
let kind = match *canonical_ty.unwrap().kind() {
|
|
TypeKind::Int(kind) => kind,
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
let mut val = cursor.evaluate().and_then(|v| v.as_int());
|
|
if val.is_none() || !kind.signedness_matches(val.unwrap()) {
|
|
val = get_integer_literal_from_cursor(&cursor);
|
|
}
|
|
|
|
val.map(|val| {
|
|
if kind == IntKind::Bool {
|
|
VarType::Bool(val != 0)
|
|
} else {
|
|
VarType::Int(val)
|
|
}
|
|
})
|
|
} else if is_float {
|
|
cursor
|
|
.evaluate()
|
|
.and_then(|v| v.as_double())
|
|
.map(VarType::Float)
|
|
} else {
|
|
cursor
|
|
.evaluate()
|
|
.and_then(|v| v.as_literal_string())
|
|
.map(VarType::String)
|
|
};
|
|
|
|
let mangling = cursor_mangling(ctx, &cursor);
|
|
let var =
|
|
Var::new(name, mangling, link_name, ty, value, is_const);
|
|
|
|
Ok(ParseResult::New(var, Some(cursor)))
|
|
}
|
|
_ => {
|
|
/* TODO */
|
|
Err(ParseError::Continue)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Try and parse a macro using all the macros parsed until now.
|
|
fn parse_macro(
|
|
ctx: &BindgenContext,
|
|
cursor: &clang::Cursor,
|
|
) -> Option<(Vec<u8>, cexpr::expr::EvalResult)> {
|
|
use cexpr::expr;
|
|
|
|
let cexpr_tokens = cursor.cexpr_tokens();
|
|
|
|
let parser = expr::IdentifierParser::new(ctx.parsed_macros());
|
|
|
|
match parser.macro_definition(&cexpr_tokens) {
|
|
Ok((_, (id, val))) => Some((id.into(), val)),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn parse_int_literal_tokens(cursor: &clang::Cursor) -> Option<i64> {
|
|
use cexpr::expr;
|
|
use cexpr::expr::EvalResult;
|
|
|
|
let cexpr_tokens = cursor.cexpr_tokens();
|
|
|
|
// TODO(emilio): We can try to parse other kinds of literals.
|
|
match expr::expr(&cexpr_tokens) {
|
|
Ok((_, EvalResult::Int(Wrapping(val)))) => Some(val),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn get_integer_literal_from_cursor(cursor: &clang::Cursor) -> Option<i64> {
|
|
use clang_sys::*;
|
|
let mut value = None;
|
|
cursor.visit(|c| {
|
|
match c.kind() {
|
|
CXCursor_IntegerLiteral | CXCursor_UnaryOperator => {
|
|
value = parse_int_literal_tokens(&c);
|
|
}
|
|
CXCursor_UnexposedExpr => {
|
|
value = get_integer_literal_from_cursor(&c);
|
|
}
|
|
_ => (),
|
|
}
|
|
if value.is_some() {
|
|
CXChildVisit_Break
|
|
} else {
|
|
CXChildVisit_Continue
|
|
}
|
|
});
|
|
value
|
|
}
|
|
|
|
fn duplicated_macro_diagnostic(
|
|
macro_name: &str,
|
|
_location: crate::clang::SourceLocation,
|
|
_ctx: &BindgenContext,
|
|
) {
|
|
warn!("Duplicated macro definition: {}", macro_name);
|
|
|
|
#[cfg(feature = "experimental")]
|
|
// FIXME (pvdrz & amanjeev): This diagnostic message shows way too often to be actually
|
|
// useful. We have to change the logic where this function is called to be able to emit this
|
|
// message only when the duplication is an actuall issue.
|
|
//
|
|
// If I understood correctly, `bindgen` ignores all `#undef` directives. Meaning that this:
|
|
// ```c
|
|
// #define FOO 1
|
|
// #undef FOO
|
|
// #define FOO 2
|
|
// ```
|
|
//
|
|
// Will trigger this message even though there's nothing wrong with it.
|
|
#[allow(clippy::overly_complex_bool_expr)]
|
|
if false && _ctx.options().emit_diagnostics {
|
|
use crate::diagnostics::{get_line, Diagnostic, Level, Slice};
|
|
use std::borrow::Cow;
|
|
|
|
let mut slice = Slice::default();
|
|
let mut source = Cow::from(macro_name);
|
|
|
|
let (file, line, col, _) = _location.location();
|
|
if let Some(filename) = file.name() {
|
|
if let Ok(Some(code)) = get_line(&filename, line) {
|
|
source = code.into();
|
|
}
|
|
slice.with_location(filename, line, col);
|
|
}
|
|
|
|
slice.with_source(source);
|
|
|
|
Diagnostic::default()
|
|
.with_title("Duplicated macro definition.", Level::Warn)
|
|
.add_slice(slice)
|
|
.add_annotation("This macro had a duplicate.", Level::Note)
|
|
.display();
|
|
}
|
|
}
|