gbf_core/decompiler/ast/
member_access.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#![deny(missing_docs)]

use gbf_macros::AstNodeTransform;
use serde::{Deserialize, Serialize};

use super::{
    assignable::AssignableKind, expr::ExprKind, ssa::SsaVersion, visitors::AstVisitor, AstKind,
    AstNodeError,
};
use crate::decompiler::ast::AstVisitable;

/// Represents a member access node in the AST, such as `object.field`.
#[derive(Debug, Clone, Serialize, Deserialize, Eq, AstNodeTransform)]
#[convert_to(
    ExprKind::Assignable,
    AstKind::Expression,
    AssignableKind::MemberAccess
)]
pub struct MemberAccessNode {
    /// The left-hand side of the member access, such as `object`.
    pub lhs: Box<AssignableKind>,
    /// The right-hand side of the member access, such as `field`.
    pub rhs: Box<AssignableKind>,
    /// Represents the SSA version of a variable.
    pub ssa_version: Option<SsaVersion>,
}

impl MemberAccessNode {
    /// Creates a new `MemberAccessNode` after validating `lhs` and `rhs` types.
    ///
    /// # Arguments
    /// - `lhs` - The left-hand side of the member access.
    /// - `rhs` - The right-hand side of the member access.
    ///
    /// # Returns
    /// A new `MemberAccessNode`.
    ///
    /// # Errors
    /// Returns an `AstNodeError` if `lhs` or `rhs` is of an unsupported type.
    pub fn new(lhs: Box<AssignableKind>, rhs: Box<AssignableKind>) -> Result<Self, AstNodeError> {
        let mut new_lhs = lhs.clone();
        let mut new_rhs = rhs.clone();
        Self::validate_and_strip_operand(&mut new_lhs)?;
        Self::validate_and_strip_operand(&mut new_rhs)?;

        Ok(Self {
            lhs: new_lhs,
            rhs: new_rhs,
            ssa_version: None,
        })
    }

    // This is marked as unreachable because the only two types of operands are Identifier and MemberAccess.
    // In the future, if more types are added, this function will need to be updated, especially for array.
    // This also removes the SsaVersion from the operands.
    #[allow(unreachable_patterns)]
    fn validate_and_strip_operand(expr: &mut AssignableKind) -> Result<(), AstNodeError> {
        expr.remove_ssa_version();
        match expr {
            AssignableKind::Identifier(_) | AssignableKind::MemberAccess(_) => Ok(()),
            _ => Err(AstNodeError::InvalidOperand(
                "MemberAccessNode".to_string(),
                "Unsupported operand type".to_string(),
                vec!["IdentifierNode".to_string(), "MemberAccessNode".to_string()],
                format!("{:?}", expr),
            )),
        }
    }
}

impl AstVisitable for MemberAccessNode {
    fn accept(&self, visitor: &mut dyn AstVisitor) {
        visitor.visit_member_access(self);
    }
}

// == Other implementations for member access ==
impl PartialEq for MemberAccessNode {
    fn eq(&self, other: &Self) -> bool {
        self.lhs == other.lhs && self.rhs == other.rhs
    }
}

#[cfg(test)]
mod tests {
    use crate::decompiler::ast::{emit, member_access, new_id, AstNodeError};

    #[test]
    fn test_member_access_emit() -> Result<(), AstNodeError> {
        let member = member_access(new_id("object"), new_id("field"))?;
        assert_eq!(emit(member), "object.field");
        Ok(())
    }

    #[test]
    fn test_member_access_nested_emit() -> Result<(), AstNodeError> {
        let member = member_access(
            member_access(new_id("object"), new_id("field"))?,
            new_id("other"),
        )?;
        assert_eq!(emit(member), "object.field.other");
        Ok(())
    }

    #[test]
    fn test_member_access_equality() -> Result<(), AstNodeError> {
        let member1 = member_access(new_id("object"), new_id("field"))?;
        let member2 = member_access(new_id("object"), new_id("field"))?;
        assert_eq!(member1, member2);

        let member3 = member_access(new_id("object"), new_id("other"))?;
        assert_ne!(member1, member3);
        Ok(())
    }
}