<commit_before>use std::cmp;

use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_self_ty;
use clippy_utils::source::snippet;
use clippy_utils::ty::is_copy;
use if_chain::if_chain;
use rustc_ast::attr;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::intravisit::FnKind;
use rustc_hir::{BindingAnnotation, Body, FnDecl, HirId, Impl, ItemKind, MutTy, Mutability, Node, PatKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{sym, Span};
use rustc_target::abi::LayoutOf;
use rustc_target::spec::abi::Abi;
use rustc_target::spec::Target;

declare_clippy_lint! {
    /// **What it does:** Checks for functions taking arguments by reference, where
    /// the argument type is `Copy` and small enough to be more efficient to always
    /// pass by value.
    ///
    /// **Why is this bad?** In many calling conventions instances of structs will
    /// be passed through registers if they fit into two or less general purpose
    /// registers.
    ///
    /// **Known problems:** This lint is target register size dependent, it is
    /// limited to 32-bit to try and reduce portability problems between 32 and
    /// 64-bit, but if you are compiling for 8 or 16-bit targets then the limit
    /// will be different.
    ///
    /// The configuration option `trivial_copy_size_limit` can be set to override
    /// this limit for a project.
    ///
    /// This lint attempts to allow passing arguments by reference if a reference
    /// to that argument is returned. This is implemented by comparing the lifetime
    /// of the argument and return value for equality. However, this can cause
    /// false positives in cases involving multiple lifetimes that are bounded by
    /// each other.
    ///
    /// **Example:**
    ///
    /// ```rust
    /// // Bad
    /// fn foo(v: &u32) {}
    /// ```
    ///
    /// ```rust
    /// // Better
    /// fn foo(v: u32) {}
    /// ```
    pub TRIVIALLY_COPY_PASS_BY_REF,
    pedantic,
    "functions taking small copyable arguments by reference"
}

declare_clippy_lint! {
    /// **What it does:** Checks for functions taking arguments by value, where
    /// the argument type is `Copy` and large enough to be worth considering
    /// passing by reference. Does not trigger if the function is being exported,
    /// because that might induce API breakage, if the parameter is declared as mutable,
    /// or if the argument is a `self`.
    ///
    /// **Why is this bad?** Arguments passed by value might result in an unnecessary
    /// shallow copy, taking up more space in the stack and requiring a call to
    /// `memcpy`, which can be expensive.
    ///
    /// **Example:**
    ///
    /// ```rust
    /// #[derive(Clone, Copy)]
    /// struct TooLarge([u8; 2048]);
    ///
    /// // Bad
    /// fn foo(v: TooLarge) {}
    /// ```
    /// ```rust
    /// #[derive(Clone, Copy)]
    /// struct TooLarge([u8; 2048]);
    ///
    /// // Good
    /// fn foo(v: &TooLarge) {}
    /// ```
    pub LARGE_TYPES_PASSED_BY_VALUE,
    pedantic,
    "functions taking large arguments by value"
}

#[derive(Copy, Clone)]
pub struct PassByRefOrValue {
    ref_min_size: u64,
    value_max_size: u64,
}

impl<'tcx> PassByRefOrValue {
    pub fn new(ref_min_size: Option<u64>, value_max_size: u64, target: &Target) -> Self {
        let ref_min_size = ref_min_size.unwrap_or_else(|| {
            let bit_width = u64::from(target.pointer_width);
            // Cap the calculated bit width at 32-bits to reduce
            // portability problems between 32 and 64-bit targets
            let bit_width = cmp::min(bit_width, 32);
            #[allow(clippy::integer_division)]
            let byte_width = bit_width / 8;
            // Use a limit of 2 times the register byte width
            byte_width * 2
        });

        Self {
            ref_min_size,
            value_max_size,
        }
    }

    fn check_poly_fn(&mut self, cx: &LateContext<'tcx>, hir_id: HirId, decl: &FnDecl<'_>, span: Option<Span>) {
        let fn_def_id = cx.tcx.hir().local_def_id(hir_id);

        let fn_sig = cx.tcx.fn_sig(fn_def_id);
        let fn_sig = cx.tcx.erase_late_bound_regions(fn_sig);

        let fn_body = cx.enclosing_body.map(|id| cx.tcx.hir().body(id));

        for (index, (input, &ty)) in decl.inputs.iter().zip(fn_sig.inputs()).enumerate() {
            // All spans generated from a proc-macro invocation are the same...
            match span {
                Some(s) if s == input.span => return,
                _ => (),
            }

            match ty.kind() {
                ty::Ref(input_lt, ty, Mutability::Not) => {
                    // Use lifetimes to determine if we're returning a reference to the
                    // argument. In that case we can't switch to pass-by-value as the
                    // argument will not live long enough.
                    let output_lts = match *fn_sig.output().kind() {
                        ty::Ref(output_lt, _, _) => vec![output_lt],
                        ty::Adt(_, substs) => substs.regions().collect(),
                        _ => vec![],
                    };

                    if_chain! {
                        if !output_lts.contains(&input_lt);
                        if is_copy(cx, ty);
                        if let Some(size) = cx.layout_of(ty).ok().map(|l| l.size.bytes());
                        if size <= self.ref_min_size;
                        if let hir::TyKind::Rptr(_, MutTy { ty: ref decl_ty, .. }) = input.kind;
                        then {
                            let value_type = if is_self_ty(decl_ty) {
                                "self".into()
                            } else {
                                snippet(cx, decl_ty.span, "_").into()
                            };
                            span_lint_and_sugg(
                                cx,
                                TRIVIALLY_COPY_PASS_BY_REF,
                                input.span,
                                &format!("this argument ({} byte) is passed by reference, but would be more efficient if passed by value (limit: {} byte)", size, self.ref_min_size),
                                "consider passing by value instead",
                                value_type,
                                Applicability::Unspecified,
                            );
                        }
                    }
                },

                ty::Adt(_, _) | ty::Array(_, _) | ty::Tuple(_) => {
                    // if function has a body and parameter is annotated with mut, ignore
                    if let Some(param) = fn_body.and_then(|body| body.params.get(index)) {
                        match param.pat.kind {
                            PatKind::Binding(BindingAnnotation::Unannotated, _, _, _) => {},
                            _ => continue,
                        }
                    }

                    if_chain! {
                        if !cx.access_levels.is_exported(hir_id);
                        if is_copy(cx, ty);
                        if !is_self_ty(input);
                        if let Some(size) = cx.layout_of(ty).ok().map(|l| l.size.bytes());
                        if size > self.value_max_size;
                        then {
                            span_lint_and_sugg(
                                cx,
                                LARGE_TYPES_PASSED_BY_VALUE,
                                input.span,
                                &format!("this argument ({} byte) is passed by value, but might be more efficient if passed by reference (limit: {} byte)", size, self.value_max_size),
                                "consider passing by reference instead",
                                format!("&{}", snippet(cx, input.span, "_")),
                                Applicability::MaybeIncorrect,
                            );
                        }
                    }
                },

                _ => {},
            }
        }
    }
}

impl_lint_pass!(PassByRefOrValue => [TRIVIALLY_COPY_PASS_BY_REF, LARGE_TYPES_PASSED_BY_VALUE]);

impl<'tcx> LateLintPass<'tcx> for PassByRefOrValue {
    fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
        if item.span.from_expansion() {
            return;
        }

        if let hir::TraitItemKind::Fn(method_sig, _) = &item.kind {
            self.check_poly_fn(cx, item.hir_id(), &*method_sig.decl, None);
        }
    }

    fn check_fn(
        &mut self,
        cx: &LateContext<'tcx>,
        kind: FnKind<'tcx>,
        decl: &'tcx FnDecl<'_>,
        _body: &'tcx Body<'_>,
        span: Span,
        hir_id: HirId,
    ) {
        if span.from_expansion() {
            return;
        }

        match kind {
            FnKind::ItemFn(.., header, _) => {
                if header.abi != Abi::Rust {
                    return;
                }
                let attrs = cx.tcx.hir().attrs(hir_id);
                for a in attrs {
                    if let Some(meta_items) = a.meta_item_list() {
                        if a.has_name(sym::proc_macro_derive)
                            || (a.has_name(sym::inline) && attr::list_contains_name(&meta_items, sym::always))
                        {
                            return;
                        }
                    }
                }
            },
            FnKind::Method(..) => (),
            FnKind::Closure => return,
        }

        // Exclude non-inherent impls
        if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) {
            if matches!(
                item.kind,
                ItemKind::Impl(Impl { of_trait: Some(_), .. }) | ItemKind::Trait(..)
            ) {
                return;
            }
        }

        self.check_poly_fn(cx, hir_id, decl, Some(span));
    }
}
<commit_msg>Add a note on the issue #5953<commit_after>use std::cmp;

use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_self_ty;
use clippy_utils::source::snippet;
use clippy_utils::ty::is_copy;
use if_chain::if_chain;
use rustc_ast::attr;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::intravisit::FnKind;
use rustc_hir::{BindingAnnotation, Body, FnDecl, HirId, Impl, ItemKind, MutTy, Mutability, Node, PatKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{sym, Span};
use rustc_target::abi::LayoutOf;
use rustc_target::spec::abi::Abi;
use rustc_target::spec::Target;

declare_clippy_lint! {
    /// **What it does:** Checks for functions taking arguments by reference, where
    /// the argument type is `Copy` and small enough to be more efficient to always
    /// pass by value.
    ///
    /// **Why is this bad?** In many calling conventions instances of structs will
    /// be passed through registers if they fit into two or less general purpose
    /// registers.
    ///
    /// **Known problems:** This lint is target register size dependent, it is
    /// limited to 32-bit to try and reduce portability problems between 32 and
    /// 64-bit, but if you are compiling for 8 or 16-bit targets then the limit
    /// will be different.
    ///
    /// The configuration option `trivial_copy_size_limit` can be set to override
    /// this limit for a project.
    ///
    /// This lint attempts to allow passing arguments by reference if a reference
    /// to that argument is returned. This is implemented by comparing the lifetime
    /// of the argument and return value for equality. However, this can cause
    /// false positives in cases involving multiple lifetimes that are bounded by
    /// each other.
    ///
    /// Also, it does not take account of other similar cases where getting memory addresses
    /// matters; namely, returning the pointer to the argument in question,
    /// and passing the argument, as both references and pointers,
    /// to a function that needs the memory address. For further details, refer to
    /// [this issue](https://github.com/rust-lang/rust-clippy/issues/5953)
    /// that explains a real case in which this false positive
    /// led to an **undefined behaviour** introduced with unsafe code.
    ///
    /// **Example:**
    ///
    /// ```rust
    /// // Bad
    /// fn foo(v: &u32) {}
    /// ```
    ///
    /// ```rust
    /// // Better
    /// fn foo(v: u32) {}
    /// ```
    pub TRIVIALLY_COPY_PASS_BY_REF,
    pedantic,
    "functions taking small copyable arguments by reference"
}

declare_clippy_lint! {
    /// **What it does:** Checks for functions taking arguments by value, where
    /// the argument type is `Copy` and large enough to be worth considering
    /// passing by reference. Does not trigger if the function is being exported,
    /// because that might induce API breakage, if the parameter is declared as mutable,
    /// or if the argument is a `self`.
    ///
    /// **Why is this bad?** Arguments passed by value might result in an unnecessary
    /// shallow copy, taking up more space in the stack and requiring a call to
    /// `memcpy`, which can be expensive.
    ///
    /// **Example:**
    ///
    /// ```rust
    /// #[derive(Clone, Copy)]
    /// struct TooLarge([u8; 2048]);
    ///
    /// // Bad
    /// fn foo(v: TooLarge) {}
    /// ```
    /// ```rust
    /// #[derive(Clone, Copy)]
    /// struct TooLarge([u8; 2048]);
    ///
    /// // Good
    /// fn foo(v: &TooLarge) {}
    /// ```
    pub LARGE_TYPES_PASSED_BY_VALUE,
    pedantic,
    "functions taking large arguments by value"
}

#[derive(Copy, Clone)]
pub struct PassByRefOrValue {
    ref_min_size: u64,
    value_max_size: u64,
}

impl<'tcx> PassByRefOrValue {
    pub fn new(ref_min_size: Option<u64>, value_max_size: u64, target: &Target) -> Self {
        let ref_min_size = ref_min_size.unwrap_or_else(|| {
            let bit_width = u64::from(target.pointer_width);
            // Cap the calculated bit width at 32-bits to reduce
            // portability problems between 32 and 64-bit targets
            let bit_width = cmp::min(bit_width, 32);
            #[allow(clippy::integer_division)]
            let byte_width = bit_width / 8;
            // Use a limit of 2 times the register byte width
            byte_width * 2
        });

        Self {
            ref_min_size,
            value_max_size,
        }
    }

    fn check_poly_fn(&mut self, cx: &LateContext<'tcx>, hir_id: HirId, decl: &FnDecl<'_>, span: Option<Span>) {
        let fn_def_id = cx.tcx.hir().local_def_id(hir_id);

        let fn_sig = cx.tcx.fn_sig(fn_def_id);
        let fn_sig = cx.tcx.erase_late_bound_regions(fn_sig);

        let fn_body = cx.enclosing_body.map(|id| cx.tcx.hir().body(id));

        for (index, (input, &ty)) in decl.inputs.iter().zip(fn_sig.inputs()).enumerate() {
            // All spans generated from a proc-macro invocation are the same...
            match span {
                Some(s) if s == input.span => return,
                _ => (),
            }

            match ty.kind() {
                ty::Ref(input_lt, ty, Mutability::Not) => {
                    // Use lifetimes to determine if we're returning a reference to the
                    // argument. In that case we can't switch to pass-by-value as the
                    // argument will not live long enough.
                    let output_lts = match *fn_sig.output().kind() {
                        ty::Ref(output_lt, _, _) => vec![output_lt],
                        ty::Adt(_, substs) => substs.regions().collect(),
                        _ => vec![],
                    };

                    if_chain! {
                        if !output_lts.contains(&input_lt);
                        if is_copy(cx, ty);
                        if let Some(size) = cx.layout_of(ty).ok().map(|l| l.size.bytes());
                        if size <= self.ref_min_size;
                        if let hir::TyKind::Rptr(_, MutTy { ty: ref decl_ty, .. }) = input.kind;
                        then {
                            let value_type = if is_self_ty(decl_ty) {
                                "self".into()
                            } else {
                                snippet(cx, decl_ty.span, "_").into()
                            };
                            span_lint_and_sugg(
                                cx,
                                TRIVIALLY_COPY_PASS_BY_REF,
                                input.span,
                                &format!("this argument ({} byte) is passed by reference, but would be more efficient if passed by value (limit: {} byte)", size, self.ref_min_size),
                                "consider passing by value instead",
                                value_type,
                                Applicability::Unspecified,
                            );
                        }
                    }
                },

                ty::Adt(_, _) | ty::Array(_, _) | ty::Tuple(_) => {
                    // if function has a body and parameter is annotated with mut, ignore
                    if let Some(param) = fn_body.and_then(|body| body.params.get(index)) {
                        match param.pat.kind {
                            PatKind::Binding(BindingAnnotation::Unannotated, _, _, _) => {},
                            _ => continue,
                        }
                    }

                    if_chain! {
                        if !cx.access_levels.is_exported(hir_id);
                        if is_copy(cx, ty);
                        if !is_self_ty(input);
                        if let Some(size) = cx.layout_of(ty).ok().map(|l| l.size.bytes());
                        if size > self.value_max_size;
                        then {
                            span_lint_and_sugg(
                                cx,
                                LARGE_TYPES_PASSED_BY_VALUE,
                                input.span,
                                &format!("this argument ({} byte) is passed by value, but might be more efficient if passed by reference (limit: {} byte)", size, self.value_max_size),
                                "consider passing by reference instead",
                                format!("&{}", snippet(cx, input.span, "_")),
                                Applicability::MaybeIncorrect,
                            );
                        }
                    }
                },

                _ => {},
            }
        }
    }
}

impl_lint_pass!(PassByRefOrValue => [TRIVIALLY_COPY_PASS_BY_REF, LARGE_TYPES_PASSED_BY_VALUE]);

impl<'tcx> LateLintPass<'tcx> for PassByRefOrValue {
    fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
        if item.span.from_expansion() {
            return;
        }

        if let hir::TraitItemKind::Fn(method_sig, _) = &item.kind {
            self.check_poly_fn(cx, item.hir_id(), &*method_sig.decl, None);
        }
    }

    fn check_fn(
        &mut self,
        cx: &LateContext<'tcx>,
        kind: FnKind<'tcx>,
        decl: &'tcx FnDecl<'_>,
        _body: &'tcx Body<'_>,
        span: Span,
        hir_id: HirId,
    ) {
        if span.from_expansion() {
            return;
        }

        match kind {
            FnKind::ItemFn(.., header, _) => {
                if header.abi != Abi::Rust {
                    return;
                }
                let attrs = cx.tcx.hir().attrs(hir_id);
                for a in attrs {
                    if let Some(meta_items) = a.meta_item_list() {
                        if a.has_name(sym::proc_macro_derive)
                            || (a.has_name(sym::inline) && attr::list_contains_name(&meta_items, sym::always))
                        {
                            return;
                        }
                    }
                }
            },
            FnKind::Method(..) => (),
            FnKind::Closure => return,
        }

        // Exclude non-inherent impls
        if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) {
            if matches!(
                item.kind,
                ItemKind::Impl(Impl { of_trait: Some(_), .. }) | ItemKind::Trait(..)
            ) {
                return;
            }
        }

        self.check_poly_fn(cx, hir_id, decl, Some(span));
    }
}
<|endoftext|>


<commit_before>use crate::syntax::atom::Atom::{self, *};
use crate::syntax::{error, ident, Api, ExternFn, Ref, Ty1, Type, Types, Var};
use proc_macro2::Ident;
use syn::{Error, Result};

pub(crate) fn typecheck(apis: &[Api], types: &Types) -> Result<()> {
    let mut errors = Vec::new();

    for ty in types {
        match ty {
            Type::Ident(ident) => {
                if Atom::from(ident).is_none()
                    && !types.structs.contains_key(ident)
                    && !types.cxx.contains(ident)
                    && !types.rust.contains(ident)
                {
                    errors.push(unsupported_type(ident));
                }
            }
            Type::RustBox(ptr) => {
                if let Type::Ident(ident) = &ptr.inner {
                    if types.cxx.contains(ident) {
                        errors.push(unsupported_cxx_type_in_box(ptr));
                    }
                    if Atom::from(ident).is_none() {
                        continue;
                    }
                }
                errors.push(unsupported_box_target(ptr));
            }
            Type::UniquePtr(ptr) => {
                if let Type::Ident(ident) = &ptr.inner {
                    if types.rust.contains(ident) {
                        errors.push(unsupported_rust_type_in_unique_ptr(ptr));
                    }
                    match Atom::from(ident) {
                        None | Some(CxxString) => continue,
                        _ => {}
                    }
                }
                errors.push(unsupported_unique_ptr_target(ptr));
            }
            Type::Ref(ty) => {
                if let Type::Void(_) = ty.inner {
                    errors.push(unsupported_reference_type(ty));
                }
            }
            _ => {}
        }
    }

    for api in apis {
        match api {
            Api::Struct(strct) => {
                for field in &strct.fields {
                    if is_unsized(&field.ty, types) {
                        errors.push(field_by_value(field, types));
                    }
                }
            }
            Api::CxxFunction(efn) | Api::RustFunction(efn) => {
                for arg in &efn.args {
                    if is_unsized(&arg.ty, types) {
                        errors.push(argument_by_value(arg, types));
                    }
                }
                if let Some(ty) = &efn.ret {
                    if is_unsized(ty, types) {
                        errors.push(return_by_value(ty, types));
                    }
                }
            }
            _ => {}
        }
    }

    for api in apis {
        if let Api::CxxFunction(efn) = api {
            errors.extend(check_mut_return_restriction(efn).err());
        }
        if let Api::CxxFunction(efn) | Api::RustFunction(efn) = api {
            errors.extend(check_multiple_arg_lifetimes(efn).err());
        }
    }

    ident::check_all(apis, &mut errors);

    let mut iter = errors.into_iter();
    let mut all_errors = match iter.next() {
        Some(err) => err,
        None => return Ok(()),
    };
    for err in iter {
        all_errors.combine(err);
    }
    Err(all_errors)
}

fn is_unsized(ty: &Type, types: &Types) -> bool {
    let ident = match ty {
        Type::Ident(ident) => ident,
        Type::Void(_) => return true,
        _ => return false,
    };
    ident == CxxString || types.cxx.contains(ident) || types.rust.contains(ident)
}

fn check_mut_return_restriction(efn: &ExternFn) -> Result<()> {
    match &efn.ret {
        Some(Type::Ref(ty)) if ty.mutability.is_some() => {}
        _ => return Ok(()),
    }

    for arg in &efn.args {
        if let Type::Ref(ty) = &arg.ty {
            if ty.mutability.is_some() {
                return Ok(());
            }
        }
    }

    Err(Error::new_spanned(
        efn,
        "&mut return type is not allowed unless there is a &mut argument",
    ))
}

fn check_multiple_arg_lifetimes(efn: &ExternFn) -> Result<()> {
    match &efn.ret {
        Some(Type::Ref(_)) => {}
        _ => return Ok(()),
    }

    let mut reference_args = 0;
    for arg in &efn.args {
        if let Type::Ref(_) = &arg.ty {
            reference_args += 1;
        }
    }

    if reference_args == 1 {
        Ok(())
    } else {
        Err(Error::new_spanned(
            efn,
            "functions that return a reference must take exactly one input reference",
        ))
    }
}

fn describe(ty: &Type, types: &Types) -> String {
    match ty {
        Type::Ident(ident) => {
            if types.structs.contains_key(ident) {
                "struct".to_owned()
            } else if types.cxx.contains(ident) {
                "C++ type".to_owned()
            } else if types.rust.contains(ident) {
                "opaque Rust type".to_owned()
            } else if Atom::from(ident) == Some(CxxString) {
                "C++ string".to_owned()
            } else {
                ident.to_string()
            }
        }
        Type::RustBox(_) => "Box".to_owned(),
        Type::UniquePtr(_) => "unique_ptr".to_owned(),
        Type::Ref(_) => "reference".to_owned(),
        Type::Str(_) => "&str".to_owned(),
        Type::Void(_) => "()".to_owned(),
    }
}

fn unsupported_type(ident: &Ident) -> Error {
    Error::new(ident.span(), "unsupported type")
}

fn unsupported_reference_type(ty: &Ref) -> Error {
    Error::new_spanned(ty, "unsupported reference type")
}

fn unsupported_cxx_type_in_box(unique_ptr: &Ty1) -> Error {
    Error::new_spanned(unique_ptr, error::BOX_CXX_TYPE.msg)
}

fn unsupported_box_target(unique_ptr: &Ty1) -> Error {
    Error::new_spanned(unique_ptr, "unsupported target type of Box")
}

fn unsupported_rust_type_in_unique_ptr(unique_ptr: &Ty1) -> Error {
    Error::new_spanned(unique_ptr, "unique_ptr of a Rust type is not supported yet")
}

fn unsupported_unique_ptr_target(unique_ptr: &Ty1) -> Error {
    Error::new_spanned(unique_ptr, "unsupported unique_ptr target type")
}

fn field_by_value(field: &Var, types: &Types) -> Error {
    let desc = describe(&field.ty, types);
    let message = format!("using {} by value is not supported", desc);
    Error::new_spanned(field, message)
}

fn argument_by_value(arg: &Var, types: &Types) -> Error {
    let desc = describe(&arg.ty, types);
    let message = format!("passing {} by value is not supported", desc);
    Error::new_spanned(arg, message)
}

fn return_by_value(ty: &Type, types: &Types) -> Error {
    let desc = describe(ty, types);
    let message = format!("returning {} by value is not supported", desc);
    Error::new_spanned(ty, message)
}
<commit_msg>Add error message to say Result is not implemented yet<commit_after>use crate::syntax::atom::Atom::{self, *};
use crate::syntax::{error, ident, Api, ExternFn, Ref, Ty1, Type, Types, Var};
use proc_macro2::Ident;
use syn::{Error, Result};

pub(crate) fn typecheck(apis: &[Api], types: &Types) -> Result<()> {
    let mut errors = Vec::new();

    for ty in types {
        match ty {
            Type::Ident(ident) => {
                if Atom::from(ident).is_none()
                    && !types.structs.contains_key(ident)
                    && !types.cxx.contains(ident)
                    && !types.rust.contains(ident)
                {
                    errors.push(unsupported_type(ident));
                }
            }
            Type::RustBox(ptr) => {
                if let Type::Ident(ident) = &ptr.inner {
                    if types.cxx.contains(ident) {
                        errors.push(unsupported_cxx_type_in_box(ptr));
                    }
                    if Atom::from(ident).is_none() {
                        continue;
                    }
                }
                errors.push(unsupported_box_target(ptr));
            }
            Type::UniquePtr(ptr) => {
                if let Type::Ident(ident) = &ptr.inner {
                    if types.rust.contains(ident) {
                        errors.push(unsupported_rust_type_in_unique_ptr(ptr));
                    }
                    match Atom::from(ident) {
                        None | Some(CxxString) => continue,
                        _ => {}
                    }
                }
                errors.push(unsupported_unique_ptr_target(ptr));
            }
            Type::Ref(ty) => {
                if let Type::Void(_) = ty.inner {
                    errors.push(unsupported_reference_type(ty));
                }
            }
            _ => {}
        }
    }

    for api in apis {
        match api {
            Api::Struct(strct) => {
                for field in &strct.fields {
                    if is_unsized(&field.ty, types) {
                        errors.push(field_by_value(field, types));
                    }
                }
            }
            Api::CxxFunction(efn) | Api::RustFunction(efn) => {
                for arg in &efn.args {
                    if is_unsized(&arg.ty, types) {
                        errors.push(argument_by_value(arg, types));
                    }
                }
                if let Some(ty) = &efn.ret {
                    if is_unsized(ty, types) {
                        errors.push(return_by_value(ty, types));
                    }
                }
                if efn.throws {
                    errors.push(Error::new_spanned(
                        efn,
                        "fallible functions are not implemented yet",
                    ));
                }
            }
            _ => {}
        }
    }

    for api in apis {
        if let Api::CxxFunction(efn) = api {
            errors.extend(check_mut_return_restriction(efn).err());
        }
        if let Api::CxxFunction(efn) | Api::RustFunction(efn) = api {
            errors.extend(check_multiple_arg_lifetimes(efn).err());
        }
    }

    ident::check_all(apis, &mut errors);

    let mut iter = errors.into_iter();
    let mut all_errors = match iter.next() {
        Some(err) => err,
        None => return Ok(()),
    };
    for err in iter {
        all_errors.combine(err);
    }
    Err(all_errors)
}

fn is_unsized(ty: &Type, types: &Types) -> bool {
    let ident = match ty {
        Type::Ident(ident) => ident,
        Type::Void(_) => return true,
        _ => return false,
    };
    ident == CxxString || types.cxx.contains(ident) || types.rust.contains(ident)
}

fn check_mut_return_restriction(efn: &ExternFn) -> Result<()> {
    match &efn.ret {
        Some(Type::Ref(ty)) if ty.mutability.is_some() => {}
        _ => return Ok(()),
    }

    for arg in &efn.args {
        if let Type::Ref(ty) = &arg.ty {
            if ty.mutability.is_some() {
                return Ok(());
            }
        }
    }

    Err(Error::new_spanned(
        efn,
        "&mut return type is not allowed unless there is a &mut argument",
    ))
}

fn check_multiple_arg_lifetimes(efn: &ExternFn) -> Result<()> {
    match &efn.ret {
        Some(Type::Ref(_)) => {}
        _ => return Ok(()),
    }

    let mut reference_args = 0;
    for arg in &efn.args {
        if let Type::Ref(_) = &arg.ty {
            reference_args += 1;
        }
    }

    if reference_args == 1 {
        Ok(())
    } else {
        Err(Error::new_spanned(
            efn,
            "functions that return a reference must take exactly one input reference",
        ))
    }
}

fn describe(ty: &Type, types: &Types) -> String {
    match ty {
        Type::Ident(ident) => {
            if types.structs.contains_key(ident) {
                "struct".to_owned()
            } else if types.cxx.contains(ident) {
                "C++ type".to_owned()
            } else if types.rust.contains(ident) {
                "opaque Rust type".to_owned()
            } else if Atom::from(ident) == Some(CxxString) {
                "C++ string".to_owned()
            } else {
                ident.to_string()
            }
        }
        Type::RustBox(_) => "Box".to_owned(),
        Type::UniquePtr(_) => "unique_ptr".to_owned(),
        Type::Ref(_) => "reference".to_owned(),
        Type::Str(_) => "&str".to_owned(),
        Type::Void(_) => "()".to_owned(),
    }
}

fn unsupported_type(ident: &Ident) -> Error {
    Error::new(ident.span(), "unsupported type")
}

fn unsupported_reference_type(ty: &Ref) -> Error {
    Error::new_spanned(ty, "unsupported reference type")
}

fn unsupported_cxx_type_in_box(unique_ptr: &Ty1) -> Error {
    Error::new_spanned(unique_ptr, error::BOX_CXX_TYPE.msg)
}

fn unsupported_box_target(unique_ptr: &Ty1) -> Error {
    Error::new_spanned(unique_ptr, "unsupported target type of Box")
}

fn unsupported_rust_type_in_unique_ptr(unique_ptr: &Ty1) -> Error {
    Error::new_spanned(unique_ptr, "unique_ptr of a Rust type is not supported yet")
}

fn unsupported_unique_ptr_target(unique_ptr: &Ty1) -> Error {
    Error::new_spanned(unique_ptr, "unsupported unique_ptr target type")
}

fn field_by_value(field: &Var, types: &Types) -> Error {
    let desc = describe(&field.ty, types);
    let message = format!("using {} by value is not supported", desc);
    Error::new_spanned(field, message)
}

fn argument_by_value(arg: &Var, types: &Types) -> Error {
    let desc = describe(&arg.ty, types);
    let message = format!("passing {} by value is not supported", desc);
    Error::new_spanned(arg, message)
}

fn return_by_value(ty: &Type, types: &Types) -> Error {
    let desc = describe(ty, types);
    let message = format!("returning {} by value is not supported", desc);
    Error::new_spanned(ty, message)
}
<|endoftext|>


<commit_before>// src/controllers/money_map_users_controller.rs

/// Money Map Users Controller

// Import
// External
use ::nickel::{JsonBody, Request};
use ::bson::{Bson};
use ::bson::oid::ObjectId;
// Utilities
use ::common::api_result::ApiResult;
use ::common::config::Config;
use ::common::data_access::ServerData;
use ::common::mm_result::{MMResult, MMError, MMErrorKind};
use ::common::session as Session;
// DAO
use ::dao::dao_manager::DAOManager;
// Models
use ::models::user_model::{OutUserModel};
use ::models::money_map_model::{MoneyMapModel};
use ::models::money_map_user_model::{MoneyMapUserModel, InMoneyMapUserModel};

#[derive(Clone)]
pub struct MoneyMapUsersController{
    dao_manager: DAOManager,
    config: Config
}

impl MoneyMapUsersController{

    pub fn new(dao_manager: DAOManager, config: Config) -> MoneyMapUsersController{
        MoneyMapUsersController{
            dao_manager: dao_manager,
            config: config
        }
    }

    /// Retrieve list of users for a money map with following conditions
    ///     Money map information only available for money maps belonging to current user
    ///
    /// # Arguments
    /// &self
    /// req - nickel::Request
    ///
    /// # Returns
    /// `ApiResult<Vec<MoneyMapUserModel>>` - ApiResult including the list of money map users
    pub fn find(&self, req: &Request<ServerData>, mm_id: &str) -> ApiResult<Vec<MoneyMapUserModel>, ()>{

        let user_id = match Session::get_session_id(req){
            Ok(id) => id,
            Err(e) => {
                error!("{}",e.get_message().to_string());
                return ApiResult::Failure{msg:"Unable to retrieve session data."};
            }
        };

        match self.dao_manager.get_money_map_dao(){
            Ok(dao) => {

                match ObjectId::with_string(mm_id){
                    Ok(id) => {
                        //Get list of money maps for this user
                        let filter = doc!{
                            "_id" => id,
                            "users.user_id" => user_id,
                            "deleted" => {
                                "$ne" => true
                            }
                        };
                        match dao.find_one(Some(filter), None){
                            Some(mut money_map) => {

                                // Get list of user details for money map
                                match MoneyMapUsersController::get_users_for_mm(&self.dao_manager, &money_map){
                                    Ok(users_list) => {
                                        // Add the new list of user details to the money map
                                        money_map.set_users(Some(users_list));
                                    },
                                    Err(e) => {
                                        return ApiResult::Failure{msg:e.get_message()};
                                    }
                                }

                                // Return the list of money maps
                                match money_map.get_users(){
                                    Some(users) => ApiResult::Success{result:users},
                                    None => ApiResult::Failure{msg:"Unable to find user details for money map"}
                                }
                            },
                            None => {
                                ApiResult::Failure{msg:"Unable to find money map."}
                            }
                        }
                    },
                    Err(e) => {
                        error!("{}", e);
                        ApiResult::Failure{msg:"Failed to find money map. Invalid ID."}
                    }
                }
            },
            Err(e) => {
                error!("{}",e.get_message().to_string());
                ApiResult::Failure{msg:"Unable to interact with database"}
            }
        }
    }// end find

    /// Add User to Money Map with following conditions
    ///     Users can only be added to a money map by the money map owner
    ///
    /// # Arguments
    /// &self
    /// req - nickel::Request
    ///
    /// # Returns
    /// `ApiResult<String, InMoneyMapUserModel>`
    pub fn add(&self, req: &mut Request<ServerData>, mm_id: String) -> ApiResult<String, InMoneyMapUserModel>{

        let user_id = match Session::get_session_id(req){
            Ok(id) => id,
            Err(e) => {
                error!("{}",e.get_message().to_string());
                return ApiResult::Failure{msg:"Unable to retrieve session data."};
            }
        };

        match self.dao_manager.get_money_map_user_dao(){
            Ok(mm_user_dao) => {
                match self.dao_manager.get_money_map_dao(){
                    Ok(mm_dao) => {
                        match self.dao_manager.get_user_dao(){
                            Ok(user_dao) => {

                                match req.json_as::<InMoneyMapUserModel>(){
                                    Ok(mut money_map_user) => {

                                        match ObjectId::with_string(&mm_id){
                                            Ok(id) => {
                                                //Get list of money maps for this user
                                                let filter = doc!{
                                                    "_id" => id,
                                                    "users.user_id" => user_id,
                                                    "deleted" => {
                                                        "$ne" => true
                                                    }
                                                };
                                                match mm_dao.find_one(Some(filter), None){
                                                    Some(mut money_map) => {

                                                        // Get user for email
                                                        if let Some(ref email) = money_map_user.clone().email {
                                                            let filter = doc!{
                                                                "email" => email
                                                            };
                                                            let user = user_dao.find_one(Some(filter), None);

                                                            // Validate
                                                            let validation_result = money_map_user.validate(&user, &money_map);
                                                            if validation_result.is_valid(){
                                                                if let Ok(result) = mm_user_dao.add_user(money_map.get_id().unwrap(), user.unwrap().get_id().unwrap()){
                                                                    if result.acknowledged && result.modified_count > 0 {
                                                                        return ApiResult::Success{result:"Successfully added user to money map".to_string()};
                                                                    }
                                                                }
                                                                ApiResult::Failure{msg:"Error add user to money map"}
                                                            }else{
                                                                ApiResult::Invalid{validation:validation_result, request:money_map_user}
                                                            }
                                                        }else{
                                                            ApiResult::Failure{msg:"Error, email required."}
                                                        }
                                                    },
                                                    None => {
                                                        ApiResult::Failure{msg:"Unable to find money map."}
                                                    }
                                                }
                                            },
                                            Err(e) => {
                                                error!("{}", e);
                                                ApiResult::Failure{msg:"Failed to find money map. Invalid ID."}
                                            }
                                        }
                                    },
                                    Err(e) => {
                                        error!("{}",e);
                                        ApiResult::Failure{msg:"Invalid format. Unable to parse data."}
                                    }
                                }
                            },
                            Err(e) => {
                                // Unable to retrieve UserDAO
                                error!("{}",e.get_message().to_string());
                                ApiResult::Failure{msg:"Unable to interact with database"}
                            }
                        }
                    },
                    Err(e) => {
                        // Unable to retrieve MoneyMapDAO
                        error!("{}",e.get_message().to_string());
                        ApiResult::Failure{msg:"Unable to interact with database"}
                    }
                }
            },
            Err(e) => {
                // Unable to retrieve MoneyMapUserDAO
                error!("{}",e.get_message().to_string());
                ApiResult::Failure{msg:"Unable to interact with database"}
            }
        }
    }// end add

    /// Delete a Money Map User
    /// Remove a user from money map using the following rules:
    ///     Only an owner can remove users from a money map.
    ///     Owner should not be allowed to be deleted.
    ///
    /// # Arguments
    /// &self
    /// id - String
    ///
    /// # Returns
    /// `ApiResult<Vec<MoneyMapUserModel>>` - ApiResult including the updated list of money map users
    pub fn delete(&self, mm_id: &str, user_id: &str) -> ApiResult<Vec<MoneyMapUserModel>, ()>{
        ApiResult::Failure{msg:"Delete needs to be implemented"}
    }// end delete

    /// Get a list of user details for money map
    ///
    /// # Arguments
    /// &self
    /// money_map - &MoneyMapModel
    ///
    /// # Returns
    /// `ApiResult<Vec<MoneyMapUserModel>>` - MMResult including the list of money map users
    pub fn get_users_for_mm(dao_manager: &DAOManager, money_map: &MoneyMapModel) -> MMResult<Vec<MoneyMapUserModel>>{
        // Initialze a list of user details for this money map
        let mut users_list = Vec::new();
        if let Some(mm_users) = money_map.get_users(){

            // For each user associated with this money map
            for mm_user in mm_users{
                match dao_manager.get_user_dao(){
                    Ok(user_dao) => {

                        // Fetch the user's details
                        let user_id = Bson::ObjectId(mm_user.user.unwrap().id.unwrap());
                        let found_user = user_dao.find_one(Some(doc!{
                            "_id" => user_id
                        }), None);
                        if let Some(user) = found_user{
                            // Add the user details to the list
                            users_list.push(
                                MoneyMapUserModel::new(OutUserModel::new(user), mm_user.owner)
                            );
                        }
                    },
                    Err(e) => {
                        error!("{}",e.get_message().to_string());
                        return Err(MMError::new("Unable to interact with database", MMErrorKind::Controller));
                    }
                }
            }
        }
        Ok(users_list)
    }// end get_users_for_mm
}
<commit_msg>Annotate 'add money map user' DAO code blocks<commit_after>// src/controllers/money_map_users_controller.rs

/// Money Map Users Controller

// Import
// External
use ::nickel::{JsonBody, Request};
use ::bson::{Bson};
use ::bson::oid::ObjectId;
// Utilities
use ::common::api_result::ApiResult;
use ::common::config::Config;
use ::common::data_access::ServerData;
use ::common::mm_result::{MMResult, MMError, MMErrorKind};
use ::common::session as Session;
// DAO
use ::dao::dao_manager::DAOManager;
// Models
use ::models::user_model::{OutUserModel};
use ::models::money_map_model::{MoneyMapModel};
use ::models::money_map_user_model::{MoneyMapUserModel, InMoneyMapUserModel};

#[derive(Clone)]
pub struct MoneyMapUsersController{
    dao_manager: DAOManager,
    config: Config
}

impl MoneyMapUsersController{

    pub fn new(dao_manager: DAOManager, config: Config) -> MoneyMapUsersController{
        MoneyMapUsersController{
            dao_manager: dao_manager,
            config: config
        }
    }

    /// Retrieve list of users for a money map with following conditions
    ///     Money map information only available for money maps belonging to current user
    ///
    /// # Arguments
    /// &self
    /// req - nickel::Request
    ///
    /// # Returns
    /// `ApiResult<Vec<MoneyMapUserModel>>` - ApiResult including the list of money map users
    pub fn find(&self, req: &Request<ServerData>, mm_id: &str) -> ApiResult<Vec<MoneyMapUserModel>, ()>{

        let user_id = match Session::get_session_id(req){
            Ok(id) => id,
            Err(e) => {
                error!("{}",e.get_message().to_string());
                return ApiResult::Failure{msg:"Unable to retrieve session data."};
            }
        };

        match self.dao_manager.get_money_map_dao(){
            Ok(dao) => {

                match ObjectId::with_string(mm_id){
                    Ok(id) => {
                        //Get list of money maps for this user
                        let filter = doc!{
                            "_id" => id,
                            "users.user_id" => user_id,
                            "deleted" => {
                                "$ne" => true
                            }
                        };
                        match dao.find_one(Some(filter), None){
                            Some(mut money_map) => {

                                // Get list of user details for money map
                                match MoneyMapUsersController::get_users_for_mm(&self.dao_manager, &money_map){
                                    Ok(users_list) => {
                                        // Add the new list of user details to the money map
                                        money_map.set_users(Some(users_list));
                                    },
                                    Err(e) => {
                                        return ApiResult::Failure{msg:e.get_message()};
                                    }
                                }

                                // Return the list of money maps
                                match money_map.get_users(){
                                    Some(users) => ApiResult::Success{result:users},
                                    None => ApiResult::Failure{msg:"Unable to find user details for money map"}
                                }
                            },
                            None => {
                                ApiResult::Failure{msg:"Unable to find money map."}
                            }
                        }
                    },
                    Err(e) => {
                        error!("{}", e);
                        ApiResult::Failure{msg:"Failed to find money map. Invalid ID."}
                    }
                }
            },
            Err(e) => {
                error!("{}",e.get_message().to_string());
                ApiResult::Failure{msg:"Unable to interact with database"}
            }
        }
    }// end find

    /// Add User to Money Map with following conditions
    ///     Users can only be added to a money map by the money map owner
    ///
    /// # Arguments
    /// &self
    /// req - nickel::Request
    ///
    /// # Returns
    /// `ApiResult<String, InMoneyMapUserModel>`
    pub fn add(&self, req: &mut Request<ServerData>, mm_id: String) -> ApiResult<String, InMoneyMapUserModel>{

        let user_id = match Session::get_session_id(req){
            Ok(id) => id,
            Err(e) => {
                error!("{}",e.get_message().to_string());
                return ApiResult::Failure{msg:"Unable to retrieve session data."};
            }
        };

        // START Retrieve DAO ---------------------------------------------------------------------
        match self.dao_manager.get_money_map_user_dao(){
            Ok(mm_user_dao) => {
                match self.dao_manager.get_money_map_dao(){
                    Ok(mm_dao) => {
                        match self.dao_manager.get_user_dao(){
                            Ok(user_dao) => {
                                // END Retrieve DAO -----------------------------------------------

                                match req.json_as::<InMoneyMapUserModel>(){
                                    Ok(mut money_map_user) => {

                                        match ObjectId::with_string(&mm_id){
                                            Ok(id) => {
                                                //Find Money Map
                                                let filter = doc!{
                                                    "_id" => id,
                                                    "users.user_id" => user_id,
                                                    "deleted" => {
                                                        "$ne" => true
                                                    }
                                                };
                                                match mm_dao.find_one(Some(filter), None){
                                                    Some(mut money_map) => {

                                                        // Get user for email
                                                        if let Some(ref email) = money_map_user.clone().email {
                                                            let filter = doc!{
                                                                "email" => email
                                                            };
                                                            let user = user_dao.find_one(Some(filter), None);

                                                            // Validate
                                                            let validation_result = money_map_user.validate(&user, &money_map);
                                                            if validation_result.is_valid(){
                                                                if let Ok(result) = mm_user_dao.add_user(money_map.get_id().unwrap(), user.unwrap().get_id().unwrap()){
                                                                    if result.acknowledged && result.modified_count > 0 {
                                                                        return ApiResult::Success{result:"Successfully added user to money map".to_string()};
                                                                    }
                                                                }
                                                                ApiResult::Failure{msg:"Error add user to money map"}
                                                            }else{
                                                                ApiResult::Invalid{validation:validation_result, request:money_map_user}
                                                            }
                                                        }else{
                                                            ApiResult::Failure{msg:"Error, email required."}
                                                        }
                                                    },
                                                    None => {
                                                        ApiResult::Failure{msg:"Unable to find money map."}
                                                    }
                                                }
                                            },
                                            Err(e) => {
                                                error!("{}", e);
                                                ApiResult::Failure{msg:"Failed to find money map. Invalid ID."}
                                            }
                                        }
                                    },
                                    Err(e) => {
                                        error!("{}",e);
                                        ApiResult::Failure{msg:"Invalid format. Unable to parse data."}
                                    }
                                }

                                // START Retrieve DAO Error Handling ------------------------------
                            },
                            Err(e) => {
                                // Unable to retrieve UserDAO
                                error!("{}",e.get_message().to_string());
                                ApiResult::Failure{msg:"Unable to interact with database"}
                            }
                        }
                    },
                    Err(e) => {
                        // Unable to retrieve MoneyMapDAO
                        error!("{}",e.get_message().to_string());
                        ApiResult::Failure{msg:"Unable to interact with database"}
                    }
                }
            },
            Err(e) => {
                // Unable to retrieve MoneyMapUserDAO
                error!("{}",e.get_message().to_string());
                ApiResult::Failure{msg:"Unable to interact with database"}
            }
        }
        // END Retrieve DAO Error Handling --------------------------------------------------------

    }// end add

    /// Delete a Money Map User
    /// Remove a user from money map using the following rules:
    ///     Only an owner can remove users from a money map.
    ///     Owner should not be allowed to be deleted.
    ///
    /// # Arguments
    /// &self
    /// id - String
    ///
    /// # Returns
    /// `ApiResult<Vec<MoneyMapUserModel>>` - ApiResult including the updated list of money map users
    pub fn delete(&self, mm_id: &str, user_id: &str) -> ApiResult<Vec<MoneyMapUserModel>, ()>{
        ApiResult::Failure{msg:"Delete needs to be implemented"}
    }// end delete

    /// Get a list of user details for money map
    ///
    /// # Arguments
    /// &self
    /// money_map - &MoneyMapModel
    ///
    /// # Returns
    /// `ApiResult<Vec<MoneyMapUserModel>>` - MMResult including the list of money map users
    pub fn get_users_for_mm(dao_manager: &DAOManager, money_map: &MoneyMapModel) -> MMResult<Vec<MoneyMapUserModel>>{
        // Initialze a list of user details for this money map
        let mut users_list = Vec::new();
        if let Some(mm_users) = money_map.get_users(){

            // For each user associated with this money map
            for mm_user in mm_users{
                match dao_manager.get_user_dao(){
                    Ok(user_dao) => {

                        // Fetch the user's details
                        let user_id = Bson::ObjectId(mm_user.user.unwrap().id.unwrap());
                        let found_user = user_dao.find_one(Some(doc!{
                            "_id" => user_id
                        }), None);
                        if let Some(user) = found_user{
                            // Add the user details to the list
                            users_list.push(
                                MoneyMapUserModel::new(OutUserModel::new(user), mm_user.owner)
                            );
                        }
                    },
                    Err(e) => {
                        error!("{}",e.get_message().to_string());
                        return Err(MMError::new("Unable to interact with database", MMErrorKind::Controller));
                    }
                }
            }
        }
        Ok(users_list)
    }// end get_users_for_mm
}
<|endoftext|>


<commit_before>//! Handling of everything related to debuginfo.

mod emit;
mod line_info;
mod unwind;

use crate::prelude::*;

use rustc_index::vec::IndexVec;

use cranelift_codegen::entity::EntityRef;
use cranelift_codegen::ir::{LabelValueLoc, StackSlots, ValueLabel, ValueLoc};
use cranelift_codegen::isa::TargetIsa;
use cranelift_codegen::ValueLocRange;

use gimli::write::{
    Address, AttributeValue, DwarfUnit, Expression, LineProgram, LineString, Location,
    LocationList, Range, RangeList, UnitEntryId,
};
use gimli::{Encoding, Format, LineEncoding, RunTimeEndian, X86_64};

pub(crate) use emit::{DebugReloc, DebugRelocName};
pub(crate) use unwind::UnwindContext;

fn target_endian(tcx: TyCtxt<'_>) -> RunTimeEndian {
    use rustc_target::abi::Endian;

    match tcx.data_layout.endian {
        Endian::Big => RunTimeEndian::Big,
        Endian::Little => RunTimeEndian::Little,
    }
}

pub(crate) struct DebugContext<'tcx> {
    tcx: TyCtxt<'tcx>,

    endian: RunTimeEndian,

    dwarf: DwarfUnit,
    unit_range_list: RangeList,

    types: FxHashMap<Ty<'tcx>, UnitEntryId>,
}

impl<'tcx> DebugContext<'tcx> {
    pub(crate) fn new(tcx: TyCtxt<'tcx>, isa: &dyn TargetIsa) -> Self {
        let encoding = Encoding {
            format: Format::Dwarf32,
            // FIXME this should be configurable
            // macOS doesn't seem to support DWARF > 3
            // 5 version is required for md5 file hash
            version: if tcx.sess.target.is_like_osx {
                3
            } else {
                // FIXME change to version 5 once the gdb and lldb shipping with the latest debian
                // support it.
                4
            },
            address_size: isa.frontend_config().pointer_bytes(),
        };

        let mut dwarf = DwarfUnit::new(encoding);

        let producer = format!(
            "cg_clif (rustc {}, cranelift {})",
            rustc_interface::util::version_str().unwrap_or("unknown version"),
            cranelift_codegen::VERSION,
        );
        let comp_dir = tcx.sess.working_dir.to_string_lossy(false).into_owned();
        let (name, file_info) = match tcx.sess.local_crate_source_file.clone() {
            Some(path) => {
                let name = path.to_string_lossy().into_owned();
                (name, None)
            }
            None => (tcx.crate_name(LOCAL_CRATE).to_string(), None),
        };

        let mut line_program = LineProgram::new(
            encoding,
            LineEncoding::default(),
            LineString::new(comp_dir.as_bytes(), encoding, &mut dwarf.line_strings),
            LineString::new(name.as_bytes(), encoding, &mut dwarf.line_strings),
            file_info,
        );
        line_program.file_has_md5 = file_info.is_some();

        dwarf.unit.line_program = line_program;

        {
            let name = dwarf.strings.add(name);
            let comp_dir = dwarf.strings.add(comp_dir);

            let root = dwarf.unit.root();
            let root = dwarf.unit.get_mut(root);
            root.set(gimli::DW_AT_producer, AttributeValue::StringRef(dwarf.strings.add(producer)));
            root.set(gimli::DW_AT_language, AttributeValue::Language(gimli::DW_LANG_Rust));
            root.set(gimli::DW_AT_name, AttributeValue::StringRef(name));
            root.set(gimli::DW_AT_comp_dir, AttributeValue::StringRef(comp_dir));
            root.set(gimli::DW_AT_low_pc, AttributeValue::Address(Address::Constant(0)));
        }

        DebugContext {
            tcx,

            endian: target_endian(tcx),

            dwarf,
            unit_range_list: RangeList(Vec::new()),

            types: FxHashMap::default(),
        }
    }

    fn dwarf_ty(&mut self, ty: Ty<'tcx>) -> UnitEntryId {
        if let Some(type_id) = self.types.get(ty) {
            return *type_id;
        }

        let new_entry = |dwarf: &mut DwarfUnit, tag| dwarf.unit.add(dwarf.unit.root(), tag);

        let primitive = |dwarf: &mut DwarfUnit, ate| {
            let type_id = new_entry(dwarf, gimli::DW_TAG_base_type);
            let type_entry = dwarf.unit.get_mut(type_id);
            type_entry.set(gimli::DW_AT_encoding, AttributeValue::Encoding(ate));
            type_id
        };

        let name = format!("{}", ty);
        let layout = self.tcx.layout_of(ParamEnv::reveal_all().and(ty)).unwrap();

        let type_id = match ty.kind() {
            ty::Bool => primitive(&mut self.dwarf, gimli::DW_ATE_boolean),
            ty::Char => primitive(&mut self.dwarf, gimli::DW_ATE_UTF),
            ty::Uint(_) => primitive(&mut self.dwarf, gimli::DW_ATE_unsigned),
            ty::Int(_) => primitive(&mut self.dwarf, gimli::DW_ATE_signed),
            ty::Float(_) => primitive(&mut self.dwarf, gimli::DW_ATE_float),
            ty::Ref(_, pointee_ty, _mutbl)
            | ty::RawPtr(ty::TypeAndMut { ty: pointee_ty, mutbl: _mutbl }) => {
                let type_id = new_entry(&mut self.dwarf, gimli::DW_TAG_pointer_type);

                // Ensure that type is inserted before recursing to avoid duplicates
                self.types.insert(ty, type_id);

                let pointee = self.dwarf_ty(pointee_ty);

                let type_entry = self.dwarf.unit.get_mut(type_id);

                //type_entry.set(gimli::DW_AT_mutable, AttributeValue::Flag(mutbl == rustc_hir::Mutability::Mut));
                type_entry.set(gimli::DW_AT_type, AttributeValue::UnitRef(pointee));

                type_id
            }
            ty::Adt(adt_def, _substs) if adt_def.is_struct() && !layout.is_unsized() => {
                let type_id = new_entry(&mut self.dwarf, gimli::DW_TAG_structure_type);

                // Ensure that type is inserted before recursing to avoid duplicates
                self.types.insert(ty, type_id);

                let variant = adt_def.non_enum_variant();

                for (field_idx, field_def) in variant.fields.iter().enumerate() {
                    let field_offset = layout.fields.offset(field_idx);
                    let field_layout = layout
                        .field(
                            &layout::LayoutCx { tcx: self.tcx, param_env: ParamEnv::reveal_all() },
                            field_idx,
                        )
                        .unwrap();

                    let field_type = self.dwarf_ty(field_layout.ty);

                    let field_id = self.dwarf.unit.add(type_id, gimli::DW_TAG_member);
                    let field_entry = self.dwarf.unit.get_mut(field_id);

                    field_entry.set(
                        gimli::DW_AT_name,
                        AttributeValue::String(field_def.ident.as_str().to_string().into_bytes()),
                    );
                    field_entry.set(
                        gimli::DW_AT_data_member_location,
                        AttributeValue::Udata(field_offset.bytes()),
                    );
                    field_entry.set(gimli::DW_AT_type, AttributeValue::UnitRef(field_type));
                }

                type_id
            }
            _ => new_entry(&mut self.dwarf, gimli::DW_TAG_structure_type),
        };

        let type_entry = self.dwarf.unit.get_mut(type_id);

        type_entry.set(gimli::DW_AT_name, AttributeValue::String(name.into_bytes()));
        type_entry.set(gimli::DW_AT_byte_size, AttributeValue::Udata(layout.size.bytes()));

        self.types.insert(ty, type_id);

        type_id
    }

    fn define_local(&mut self, scope: UnitEntryId, name: String, ty: Ty<'tcx>) -> UnitEntryId {
        let dw_ty = self.dwarf_ty(ty);

        let var_id = self.dwarf.unit.add(scope, gimli::DW_TAG_variable);
        let var_entry = self.dwarf.unit.get_mut(var_id);

        var_entry.set(gimli::DW_AT_name, AttributeValue::String(name.into_bytes()));
        var_entry.set(gimli::DW_AT_type, AttributeValue::UnitRef(dw_ty));

        var_id
    }

    pub(crate) fn define_function(
        &mut self,
        instance: Instance<'tcx>,
        func_id: FuncId,
        name: &str,
        isa: &dyn TargetIsa,
        context: &Context,
        source_info_set: &indexmap::IndexSet<SourceInfo>,
        local_map: IndexVec<mir::Local, CPlace<'tcx>>,
    ) {
        let symbol = func_id.as_u32() as usize;
        let mir = self.tcx.instance_mir(instance.def);

        // FIXME: add to appropriate scope instead of root
        let scope = self.dwarf.unit.root();

        let entry_id = self.dwarf.unit.add(scope, gimli::DW_TAG_subprogram);
        let entry = self.dwarf.unit.get_mut(entry_id);
        let name_id = self.dwarf.strings.add(name);
        // Gdb requires DW_AT_name. Otherwise the DW_TAG_subprogram is skipped.
        entry.set(gimli::DW_AT_name, AttributeValue::StringRef(name_id));
        entry.set(gimli::DW_AT_linkage_name, AttributeValue::StringRef(name_id));

        let end = self.create_debug_lines(symbol, entry_id, context, mir.span, source_info_set);

        self.unit_range_list.0.push(Range::StartLength {
            begin: Address::Symbol { symbol, addend: 0 },
            length: u64::from(end),
        });

        let func_entry = self.dwarf.unit.get_mut(entry_id);
        // Gdb requires both DW_AT_low_pc and DW_AT_high_pc. Otherwise the DW_TAG_subprogram is skipped.
        func_entry.set(
            gimli::DW_AT_low_pc,
            AttributeValue::Address(Address::Symbol { symbol, addend: 0 }),
        );
        // Using Udata for DW_AT_high_pc requires at least DWARF4
        func_entry.set(gimli::DW_AT_high_pc, AttributeValue::Udata(u64::from(end)));

        // FIXME make it more reliable and implement scopes before re-enabling this.
        if false {
            let value_labels_ranges = context.build_value_labels_ranges(isa).unwrap();

            for (local, _local_decl) in mir.local_decls.iter_enumerated() {
                let ty = self.tcx.subst_and_normalize_erasing_regions(
                    instance.substs,
                    ty::ParamEnv::reveal_all(),
                    mir.local_decls[local].ty,
                );
                let var_id = self.define_local(entry_id, format!("{:?}", local), ty);

                let location = place_location(
                    self,
                    isa,
                    symbol,
                    context,
                    &local_map,
                    &value_labels_ranges,
                    Place { local, projection: ty::List::empty() },
                );

                let var_entry = self.dwarf.unit.get_mut(var_id);
                var_entry.set(gimli::DW_AT_location, location);
            }
        }

        // FIXME create locals for all entries in mir.var_debug_info
    }
}

fn place_location<'tcx>(
    debug_context: &mut DebugContext<'tcx>,
    isa: &dyn TargetIsa,
    symbol: usize,
    context: &Context,
    local_map: &IndexVec<mir::Local, CPlace<'tcx>>,
    #[allow(rustc::default_hash_types)] value_labels_ranges: &std::collections::HashMap<
        ValueLabel,
        Vec<ValueLocRange>,
    >,
    place: Place<'tcx>,
) -> AttributeValue {
    assert!(place.projection.is_empty()); // FIXME implement them

    match local_map[place.local].inner() {
        CPlaceInner::Var(_local, var) => {
            let value_label = cranelift_codegen::ir::ValueLabel::new(var.index());
            if let Some(value_loc_ranges) = value_labels_ranges.get(&value_label) {
                let loc_list = LocationList(
                    value_loc_ranges
                        .iter()
                        .map(|value_loc_range| Location::StartEnd {
                            begin: Address::Symbol {
                                symbol,
                                addend: i64::from(value_loc_range.start),
                            },
                            end: Address::Symbol { symbol, addend: i64::from(value_loc_range.end) },
                            data: translate_loc(
                                isa,
                                value_loc_range.loc,
                                &context.func.stack_slots,
                            )
                            .unwrap(),
                        })
                        .collect(),
                );
                let loc_list_id = debug_context.dwarf.unit.locations.add(loc_list);

                AttributeValue::LocationListRef(loc_list_id)
            } else {
                // FIXME set value labels for unused locals

                AttributeValue::Exprloc(Expression::new())
            }
        }
        CPlaceInner::VarPair(_, _, _) => {
            // FIXME implement this

            AttributeValue::Exprloc(Expression::new())
        }
        CPlaceInner::VarLane(_, _, _) => {
            // FIXME implement this

            AttributeValue::Exprloc(Expression::new())
        }
        CPlaceInner::Addr(_, _) => {
            // FIXME implement this (used by arguments and returns)

            AttributeValue::Exprloc(Expression::new())

            // For PointerBase::Stack:
            //AttributeValue::Exprloc(translate_loc(ValueLoc::Stack(*stack_slot), &context.func.stack_slots).unwrap())
        }
    }
}

// Adapted from https://github.com/CraneStation/wasmtime/blob/5a1845b4caf7a5dba8eda1fef05213a532ed4259/crates/debug/src/transform/expression.rs#L59-L137
fn translate_loc(
    isa: &dyn TargetIsa,
    loc: LabelValueLoc,
    stack_slots: &StackSlots,
) -> Option<Expression> {
    match loc {
        LabelValueLoc::ValueLoc(ValueLoc::Reg(reg)) => {
            let machine_reg = isa.map_dwarf_register(reg).unwrap();
            let mut expr = Expression::new();
            expr.op_reg(gimli::Register(machine_reg));
            Some(expr)
        }
        LabelValueLoc::ValueLoc(ValueLoc::Stack(ss)) => {
            if let Some(ss_offset) = stack_slots[ss].offset {
                let mut expr = Expression::new();
                expr.op_breg(X86_64::RBP, i64::from(ss_offset) + 16);
                Some(expr)
            } else {
                None
            }
        }
        LabelValueLoc::ValueLoc(ValueLoc::Unassigned) => unreachable!(),
        LabelValueLoc::Reg(reg) => {
            let machine_reg = isa.map_regalloc_reg_to_dwarf(reg).unwrap();
            let mut expr = Expression::new();
            expr.op_reg(gimli::Register(machine_reg));
            Some(expr)
        }
        LabelValueLoc::SPOffset(offset) => {
            let mut expr = Expression::new();
            expr.op_breg(X86_64::RSP, offset);
            Some(expr)
        }
    }
}
<commit_msg>Update rustc_codegen_cratelift for working_dir change<commit_after>//! Handling of everything related to debuginfo.

mod emit;
mod line_info;
mod unwind;

use crate::prelude::*;

use rustc_index::vec::IndexVec;

use cranelift_codegen::entity::EntityRef;
use cranelift_codegen::ir::{LabelValueLoc, StackSlots, ValueLabel, ValueLoc};
use cranelift_codegen::isa::TargetIsa;
use cranelift_codegen::ValueLocRange;

use gimli::write::{
    Address, AttributeValue, DwarfUnit, Expression, LineProgram, LineString, Location,
    LocationList, Range, RangeList, UnitEntryId,
};
use gimli::{Encoding, Format, LineEncoding, RunTimeEndian, X86_64};

pub(crate) use emit::{DebugReloc, DebugRelocName};
pub(crate) use unwind::UnwindContext;

fn target_endian(tcx: TyCtxt<'_>) -> RunTimeEndian {
    use rustc_target::abi::Endian;

    match tcx.data_layout.endian {
        Endian::Big => RunTimeEndian::Big,
        Endian::Little => RunTimeEndian::Little,
    }
}

pub(crate) struct DebugContext<'tcx> {
    tcx: TyCtxt<'tcx>,

    endian: RunTimeEndian,

    dwarf: DwarfUnit,
    unit_range_list: RangeList,

    types: FxHashMap<Ty<'tcx>, UnitEntryId>,
}

impl<'tcx> DebugContext<'tcx> {
    pub(crate) fn new(tcx: TyCtxt<'tcx>, isa: &dyn TargetIsa) -> Self {
        let encoding = Encoding {
            format: Format::Dwarf32,
            // FIXME this should be configurable
            // macOS doesn't seem to support DWARF > 3
            // 5 version is required for md5 file hash
            version: if tcx.sess.target.is_like_osx {
                3
            } else {
                // FIXME change to version 5 once the gdb and lldb shipping with the latest debian
                // support it.
                4
            },
            address_size: isa.frontend_config().pointer_bytes(),
        };

        let mut dwarf = DwarfUnit::new(encoding);

        let producer = format!(
            "cg_clif (rustc {}, cranelift {})",
            rustc_interface::util::version_str().unwrap_or("unknown version"),
            cranelift_codegen::VERSION,
        );
        let comp_dir = tcx.sess.opts.working_dir.to_string_lossy(false).into_owned();
        let (name, file_info) = match tcx.sess.local_crate_source_file.clone() {
            Some(path) => {
                let name = path.to_string_lossy().into_owned();
                (name, None)
            }
            None => (tcx.crate_name(LOCAL_CRATE).to_string(), None),
        };

        let mut line_program = LineProgram::new(
            encoding,
            LineEncoding::default(),
            LineString::new(comp_dir.as_bytes(), encoding, &mut dwarf.line_strings),
            LineString::new(name.as_bytes(), encoding, &mut dwarf.line_strings),
            file_info,
        );
        line_program.file_has_md5 = file_info.is_some();

        dwarf.unit.line_program = line_program;

        {
            let name = dwarf.strings.add(name);
            let comp_dir = dwarf.strings.add(comp_dir);

            let root = dwarf.unit.root();
            let root = dwarf.unit.get_mut(root);
            root.set(gimli::DW_AT_producer, AttributeValue::StringRef(dwarf.strings.add(producer)));
            root.set(gimli::DW_AT_language, AttributeValue::Language(gimli::DW_LANG_Rust));
            root.set(gimli::DW_AT_name, AttributeValue::StringRef(name));
            root.set(gimli::DW_AT_comp_dir, AttributeValue::StringRef(comp_dir));
            root.set(gimli::DW_AT_low_pc, AttributeValue::Address(Address::Constant(0)));
        }

        DebugContext {
            tcx,

            endian: target_endian(tcx),

            dwarf,
            unit_range_list: RangeList(Vec::new()),

            types: FxHashMap::default(),
        }
    }

    fn dwarf_ty(&mut self, ty: Ty<'tcx>) -> UnitEntryId {
        if let Some(type_id) = self.types.get(ty) {
            return *type_id;
        }

        let new_entry = |dwarf: &mut DwarfUnit, tag| dwarf.unit.add(dwarf.unit.root(), tag);

        let primitive = |dwarf: &mut DwarfUnit, ate| {
            let type_id = new_entry(dwarf, gimli::DW_TAG_base_type);
            let type_entry = dwarf.unit.get_mut(type_id);
            type_entry.set(gimli::DW_AT_encoding, AttributeValue::Encoding(ate));
            type_id
        };

        let name = format!("{}", ty);
        let layout = self.tcx.layout_of(ParamEnv::reveal_all().and(ty)).unwrap();

        let type_id = match ty.kind() {
            ty::Bool => primitive(&mut self.dwarf, gimli::DW_ATE_boolean),
            ty::Char => primitive(&mut self.dwarf, gimli::DW_ATE_UTF),
            ty::Uint(_) => primitive(&mut self.dwarf, gimli::DW_ATE_unsigned),
            ty::Int(_) => primitive(&mut self.dwarf, gimli::DW_ATE_signed),
            ty::Float(_) => primitive(&mut self.dwarf, gimli::DW_ATE_float),
            ty::Ref(_, pointee_ty, _mutbl)
            | ty::RawPtr(ty::TypeAndMut { ty: pointee_ty, mutbl: _mutbl }) => {
                let type_id = new_entry(&mut self.dwarf, gimli::DW_TAG_pointer_type);

                // Ensure that type is inserted before recursing to avoid duplicates
                self.types.insert(ty, type_id);

                let pointee = self.dwarf_ty(pointee_ty);

                let type_entry = self.dwarf.unit.get_mut(type_id);

                //type_entry.set(gimli::DW_AT_mutable, AttributeValue::Flag(mutbl == rustc_hir::Mutability::Mut));
                type_entry.set(gimli::DW_AT_type, AttributeValue::UnitRef(pointee));

                type_id
            }
            ty::Adt(adt_def, _substs) if adt_def.is_struct() && !layout.is_unsized() => {
                let type_id = new_entry(&mut self.dwarf, gimli::DW_TAG_structure_type);

                // Ensure that type is inserted before recursing to avoid duplicates
                self.types.insert(ty, type_id);

                let variant = adt_def.non_enum_variant();

                for (field_idx, field_def) in variant.fields.iter().enumerate() {
                    let field_offset = layout.fields.offset(field_idx);
                    let field_layout = layout
                        .field(
                            &layout::LayoutCx { tcx: self.tcx, param_env: ParamEnv::reveal_all() },
                            field_idx,
                        )
                        .unwrap();

                    let field_type = self.dwarf_ty(field_layout.ty);

                    let field_id = self.dwarf.unit.add(type_id, gimli::DW_TAG_member);
                    let field_entry = self.dwarf.unit.get_mut(field_id);

                    field_entry.set(
                        gimli::DW_AT_name,
                        AttributeValue::String(field_def.ident.as_str().to_string().into_bytes()),
                    );
                    field_entry.set(
                        gimli::DW_AT_data_member_location,
                        AttributeValue::Udata(field_offset.bytes()),
                    );
                    field_entry.set(gimli::DW_AT_type, AttributeValue::UnitRef(field_type));
                }

                type_id
            }
            _ => new_entry(&mut self.dwarf, gimli::DW_TAG_structure_type),
        };

        let type_entry = self.dwarf.unit.get_mut(type_id);

        type_entry.set(gimli::DW_AT_name, AttributeValue::String(name.into_bytes()));
        type_entry.set(gimli::DW_AT_byte_size, AttributeValue::Udata(layout.size.bytes()));

        self.types.insert(ty, type_id);

        type_id
    }

    fn define_local(&mut self, scope: UnitEntryId, name: String, ty: Ty<'tcx>) -> UnitEntryId {
        let dw_ty = self.dwarf_ty(ty);

        let var_id = self.dwarf.unit.add(scope, gimli::DW_TAG_variable);
        let var_entry = self.dwarf.unit.get_mut(var_id);

        var_entry.set(gimli::DW_AT_name, AttributeValue::String(name.into_bytes()));
        var_entry.set(gimli::DW_AT_type, AttributeValue::UnitRef(dw_ty));

        var_id
    }

    pub(crate) fn define_function(
        &mut self,
        instance: Instance<'tcx>,
        func_id: FuncId,
        name: &str,
        isa: &dyn TargetIsa,
        context: &Context,
        source_info_set: &indexmap::IndexSet<SourceInfo>,
        local_map: IndexVec<mir::Local, CPlace<'tcx>>,
    ) {
        let symbol = func_id.as_u32() as usize;
        let mir = self.tcx.instance_mir(instance.def);

        // FIXME: add to appropriate scope instead of root
        let scope = self.dwarf.unit.root();

        let entry_id = self.dwarf.unit.add(scope, gimli::DW_TAG_subprogram);
        let entry = self.dwarf.unit.get_mut(entry_id);
        let name_id = self.dwarf.strings.add(name);
        // Gdb requires DW_AT_name. Otherwise the DW_TAG_subprogram is skipped.
        entry.set(gimli::DW_AT_name, AttributeValue::StringRef(name_id));
        entry.set(gimli::DW_AT_linkage_name, AttributeValue::StringRef(name_id));

        let end = self.create_debug_lines(symbol, entry_id, context, mir.span, source_info_set);

        self.unit_range_list.0.push(Range::StartLength {
            begin: Address::Symbol { symbol, addend: 0 },
            length: u64::from(end),
        });

        let func_entry = self.dwarf.unit.get_mut(entry_id);
        // Gdb requires both DW_AT_low_pc and DW_AT_high_pc. Otherwise the DW_TAG_subprogram is skipped.
        func_entry.set(
            gimli::DW_AT_low_pc,
            AttributeValue::Address(Address::Symbol { symbol, addend: 0 }),
        );
        // Using Udata for DW_AT_high_pc requires at least DWARF4
        func_entry.set(gimli::DW_AT_high_pc, AttributeValue::Udata(u64::from(end)));

        // FIXME make it more reliable and implement scopes before re-enabling this.
        if false {
            let value_labels_ranges = context.build_value_labels_ranges(isa).unwrap();

            for (local, _local_decl) in mir.local_decls.iter_enumerated() {
                let ty = self.tcx.subst_and_normalize_erasing_regions(
                    instance.substs,
                    ty::ParamEnv::reveal_all(),
                    mir.local_decls[local].ty,
                );
                let var_id = self.define_local(entry_id, format!("{:?}", local), ty);

                let location = place_location(
                    self,
                    isa,
                    symbol,
                    context,
                    &local_map,
                    &value_labels_ranges,
                    Place { local, projection: ty::List::empty() },
                );

                let var_entry = self.dwarf.unit.get_mut(var_id);
                var_entry.set(gimli::DW_AT_location, location);
            }
        }

        // FIXME create locals for all entries in mir.var_debug_info
    }
}

fn place_location<'tcx>(
    debug_context: &mut DebugContext<'tcx>,
    isa: &dyn TargetIsa,
    symbol: usize,
    context: &Context,
    local_map: &IndexVec<mir::Local, CPlace<'tcx>>,
    #[allow(rustc::default_hash_types)] value_labels_ranges: &std::collections::HashMap<
        ValueLabel,
        Vec<ValueLocRange>,
    >,
    place: Place<'tcx>,
) -> AttributeValue {
    assert!(place.projection.is_empty()); // FIXME implement them

    match local_map[place.local].inner() {
        CPlaceInner::Var(_local, var) => {
            let value_label = cranelift_codegen::ir::ValueLabel::new(var.index());
            if let Some(value_loc_ranges) = value_labels_ranges.get(&value_label) {
                let loc_list = LocationList(
                    value_loc_ranges
                        .iter()
                        .map(|value_loc_range| Location::StartEnd {
                            begin: Address::Symbol {
                                symbol,
                                addend: i64::from(value_loc_range.start),
                            },
                            end: Address::Symbol { symbol, addend: i64::from(value_loc_range.end) },
                            data: translate_loc(
                                isa,
                                value_loc_range.loc,
                                &context.func.stack_slots,
                            )
                            .unwrap(),
                        })
                        .collect(),
                );
                let loc_list_id = debug_context.dwarf.unit.locations.add(loc_list);

                AttributeValue::LocationListRef(loc_list_id)
            } else {
                // FIXME set value labels for unused locals

                AttributeValue::Exprloc(Expression::new())
            }
        }
        CPlaceInner::VarPair(_, _, _) => {
            // FIXME implement this

            AttributeValue::Exprloc(Expression::new())
        }
        CPlaceInner::VarLane(_, _, _) => {
            // FIXME implement this

            AttributeValue::Exprloc(Expression::new())
        }
        CPlaceInner::Addr(_, _) => {
            // FIXME implement this (used by arguments and returns)

            AttributeValue::Exprloc(Expression::new())

            // For PointerBase::Stack:
            //AttributeValue::Exprloc(translate_loc(ValueLoc::Stack(*stack_slot), &context.func.stack_slots).unwrap())
        }
    }
}

// Adapted from https://github.com/CraneStation/wasmtime/blob/5a1845b4caf7a5dba8eda1fef05213a532ed4259/crates/debug/src/transform/expression.rs#L59-L137
fn translate_loc(
    isa: &dyn TargetIsa,
    loc: LabelValueLoc,
    stack_slots: &StackSlots,
) -> Option<Expression> {
    match loc {
        LabelValueLoc::ValueLoc(ValueLoc::Reg(reg)) => {
            let machine_reg = isa.map_dwarf_register(reg).unwrap();
            let mut expr = Expression::new();
            expr.op_reg(gimli::Register(machine_reg));
            Some(expr)
        }
        LabelValueLoc::ValueLoc(ValueLoc::Stack(ss)) => {
            if let Some(ss_offset) = stack_slots[ss].offset {
                let mut expr = Expression::new();
                expr.op_breg(X86_64::RBP, i64::from(ss_offset) + 16);
                Some(expr)
            } else {
                None
            }
        }
        LabelValueLoc::ValueLoc(ValueLoc::Unassigned) => unreachable!(),
        LabelValueLoc::Reg(reg) => {
            let machine_reg = isa.map_regalloc_reg_to_dwarf(reg).unwrap();
            let mut expr = Expression::new();
            expr.op_reg(gimli::Register(machine_reg));
            Some(expr)
        }
        LabelValueLoc::SPOffset(offset) => {
            let mut expr = Expression::new();
            expr.op_breg(X86_64::RSP, offset);
            Some(expr)
        }
    }
}
<|endoftext|>