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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
use super::State;
use crate::abstract_domain::*;
use crate::intermediate_representation::*;

impl State {
    /// Load the value at the given address.
    ///
    /// Only values on the stack and in registers are tracked directly.
    /// For all other values abstract location strings are generated
    /// that track how the pointer to the value is computed.
    ///
    /// This function does not set any access flags for input IDs in the address value.
    pub fn load_value(
        &mut self,
        address: DataDomain<BitvectorDomain>,
        size: ByteSize,
        global_memory: Option<&RuntimeMemoryImage>,
    ) -> DataDomain<BitvectorDomain> {
        let mut loaded_value = DataDomain::new_empty(size);
        for (id, offset) in address.get_relative_values() {
            loaded_value = loaded_value.merge(&self.load_value_via_id_and_offset(id, offset, size));
        }
        if let Some(global_address) = address.get_absolute_value() {
            loaded_value =
                loaded_value.merge(&self.load_global_address(global_address, size, global_memory));
        }
        if address.contains_top() {
            loaded_value.set_contains_top_flag();
        }
        loaded_value
    }

    /// Load the value whose position is given by derefencing the given ID and then adding an offset.
    ///
    /// If the ID is the stack then this function actually loads the value at the given stack position.
    /// Otherwise it only generates the abstract location of the value and returns it as a relative value.
    fn load_value_via_id_and_offset(
        &mut self,
        id: &AbstractIdentifier,
        offset: &BitvectorDomain,
        size: ByteSize,
    ) -> DataDomain<BitvectorDomain> {
        if *id == self.stack_id {
            // Try to load a value from the stack (which may generate a new stack parameter)
            match offset.try_to_bitvec() {
                Ok(stack_offset) => self.load_value_from_stack(stack_offset, size),
                Err(_) => DataDomain::new_top(size),
            }
        } else if let (true, Ok(constant_offset)) = (
            id.get_location().recursion_depth() < self.pointer_recursion_depth_limit,
            offset.try_to_offset(),
        ) {
            // Extend the abstract location string
            let new_id = AbstractIdentifier::new(
                id.get_tid().clone(),
                id.get_location()
                    .clone()
                    .dereferenced(size, self.stack_id.bytesize())
                    .with_offset_addendum(constant_offset),
            );
            DataDomain::from_target(new_id, Bitvector::zero(size.into()).into())
        } else {
            // The abstract location string cannot be extended
            DataDomain::new_top(size)
        }
    }

    /// Load a value from the global address space.
    /// If the address is located in writeable global memory then generate a new abstract ID for the value
    /// and return a value relative to the new ID.
    fn load_global_address(
        &mut self,
        global_address: &BitvectorDomain,
        size: ByteSize,
        global_memory: Option<&RuntimeMemoryImage>,
    ) -> DataDomain<BitvectorDomain> {
        if let (Ok(offset), Some(global_mem)) = (global_address.try_to_bitvec(), global_memory) {
            match global_mem.read(&offset, size) {
                Ok(Some(value)) => value.into(),
                Ok(None) => {
                    let address = global_address.try_to_offset().unwrap() as u64;
                    let global_mem_location = AbstractLocation::GlobalAddress { address, size };
                    let global_mem_id = AbstractIdentifier::new(
                        self.get_current_function_tid().clone(),
                        global_mem_location,
                    );
                    DataDomain::from_target(global_mem_id, Bitvector::zero(size.into()).into())
                }
                Err(_) => DataDomain::new_top(size),
            }
        } else {
            DataDomain::new_top(size)
        }
    }

    /// Load the value at the given stack offset.
    /// If the offset is non-negative a corresponding stack parameter is generated if necessary.
    pub fn load_value_from_stack(
        &mut self,
        stack_offset: Bitvector,
        size: ByteSize,
    ) -> DataDomain<BitvectorDomain> {
        if !stack_offset.sign_bit().to_bool() {
            // Stack offset is nonnegative, i.e. this is a stack parameter access.
            self.get_stack_param(stack_offset, size)
        } else {
            self.stack.get(stack_offset, size)
        }
    }

    /// Load a value of unknown bytesize at the given stack offset.
    /// If the offset is non-negative, a corresponding stack parameter is generated if necessary.
    ///
    /// One must be careful to not rely on the correctness of the bytesize of the returned value!
    /// If the size of the value cannot be guessed from the contents of the stack,
    /// then a size of 1 byte is assumed, which will be wrong in general!
    pub fn load_unsized_value_from_stack(
        &mut self,
        offset: Bitvector,
    ) -> DataDomain<BitvectorDomain> {
        if !offset.sign_bit().to_bool() {
            // This is a pointer to a stack parameter of the current function
            self.stack
                .get_unsized(offset.clone())
                .unwrap_or_else(|| self.get_stack_param(offset, ByteSize::new(1)))
        } else {
            self.stack
                .get_unsized(offset)
                .unwrap_or_else(|| DataDomain::new_top(ByteSize::new(1)))
        }
    }

    /// If `address` is a stack offset, then write `value` onto the stack.
    ///
    /// If address points to a stack parameter, whose ID does not yet exists,
    /// then the ID is generated and added to the tracked IDs.
    ///
    /// This function does not set any access flags for input IDs of the given address or value.
    pub fn write_value(
        &mut self,
        address: DataDomain<BitvectorDomain>,
        value: DataDomain<BitvectorDomain>,
    ) {
        if let Some(stack_offset) = self.get_offset_if_exact_stack_pointer(&address) {
            if !stack_offset.sign_bit().to_bool() {
                // We generate a new stack parameter object, but do not set any access flags,
                // since the stack parameter is not accessed but overwritten.
                let _ = self
                    .generate_stack_param_id_if_nonexistent(stack_offset.clone(), value.bytesize());
            }
            self.stack.add(value, stack_offset);
        } else if let Some(stack_offset_domain) = address.get_relative_values().get(&self.stack_id)
        {
            if let Ok(stack_offset) = stack_offset_domain.try_to_bitvec() {
                if !stack_offset.sign_bit().to_bool() {
                    // We generate a new stack parameter object, but do not set any access flags,
                    // since the stack parameter is not accessed but overwritten.
                    let _ = self.generate_stack_param_id_if_nonexistent(
                        stack_offset.clone(),
                        value.bytesize(),
                    );
                }
                let previous_value = self.stack.get(stack_offset.clone(), value.bytesize());
                self.stack.add(previous_value.merge(&value), stack_offset);
            } else {
                self.stack.mark_all_values_as_top();
            }
        }
    }

    /// Get the value located at a positive stack offset.
    /// This function panics if the address is a negative offset.
    ///
    /// If no corresponding stack parameter ID exists for the value,
    /// generate it and then return it as an unmodified stack parameter.
    /// Otherwise just read the value at the given stack address.
    fn get_stack_param(
        &mut self,
        address: Bitvector,
        size: ByteSize,
    ) -> DataDomain<BitvectorDomain> {
        assert!(!address.sign_bit().to_bool());
        if let Some(param_id) = self.generate_stack_param_id_if_nonexistent(address.clone(), size) {
            let stack_param =
                DataDomain::from_target(param_id, Bitvector::zero(size.into()).into());
            self.stack.add(stack_param.clone(), address);
            stack_param
        } else {
            self.stack.get(address, size)
        }
    }

    /// If the address is an exactly known pointer to the stack with a constant offset, then return the offset.
    pub fn get_offset_if_exact_stack_pointer(
        &self,
        address: &DataDomain<BitvectorDomain>,
    ) -> Option<Bitvector> {
        if let Some((target, offset)) = address.get_if_unique_target() {
            if *target == self.stack_id {
                return offset.try_to_bitvec().ok();
            }
        }
        None
    }
}

#[cfg(test)]
pub mod tests {
    use super::*;
    use crate::{bitvec, variable};

    /// Mock an abstract ID representing the stack.
    fn mock_stack_id() -> AbstractIdentifier {
        AbstractIdentifier::from_var(Tid::new("mock_fn"), &variable!("sp:4"))
    }

    /// Mock an abstract ID of a stack parameter
    fn mock_stack_param_id(offset: i64, size: u64) -> AbstractIdentifier {
        AbstractIdentifier::new(
            Tid::new("mock_fn"),
            AbstractLocation::from_stack_position(
                mock_stack_id().unwrap_register(),
                offset,
                ByteSize::new(size),
            ),
        )
    }

    #[test]
    fn test_get_offset_if_exact_stack_pointer() {
        let state = State::mock_arm32();
        let stack_pointer =
            DataDomain::from_target(mock_stack_id(), Bitvector::from_i32(-10).into());
        assert_eq!(
            state.get_offset_if_exact_stack_pointer(&stack_pointer),
            Some(Bitvector::from_i32(-10))
        );
    }

    #[test]
    fn test_get_stack_param() {
        // Reading a previously non-existing stack parameter
        let mut state = State::mock_arm32();
        let stack_param = state.get_stack_param(bitvec!("0xc:4"), ByteSize::new(8));
        let expected_stack_id = AbstractIdentifier::mock_nested("mock_fn", "sp:4", &[12], 8);
        let expected_value =
            DataDomain::from_target(expected_stack_id.clone(), bitvec!("0x0:8").into());
        assert_eq!(&stack_param, &expected_value);
        assert!(state.tracked_ids.contains_key(&expected_stack_id));
        // Reading the stack parameter again. The position should still contain the stack parameter.
        let stack_param = state.get_stack_param(bitvec!("0xc:4"), ByteSize::new(8));
        assert_eq!(&stack_param, &expected_value);
        // Reading the stack parameter after it has been overwritten with a value.
        state
            .stack
            .insert_at_byte_index(bitvec!("0x2a:8").into(), 12);
        let value = state.get_stack_param(bitvec!("0xc:4"), ByteSize::new(8));
        assert_eq!(value, bitvec!("0x2a:8").into());
    }

    #[test]
    fn test_store_and_load_from_stack() {
        let mut state = State::mock_arm32();
        let address = DataDomain::from_target(mock_stack_id(), bitvec!("-4:4").into());
        let value: DataDomain<BitvectorDomain> = bitvec!("0x0:4").into();
        // write and load a value to the current stack frame
        state.write_value(address.clone(), value.clone());
        assert_eq!(state.stack.iter().len(), 1);
        assert_eq!(
            state.stack.get(bitvec!("-4:4"), ByteSize::new(4)),
            value.clone()
        );
        assert_eq!(state.load_value(address, ByteSize::new(4), None), value);
        // Load a parameter register and check that the parameter gets generated
        let address = DataDomain::from_target(mock_stack_id(), bitvec!("0x4:4").into());
        let stack_param_id = mock_stack_param_id(4, 4);
        let stack_param = DataDomain::from_target(stack_param_id.clone(), bitvec!("0x0:4").into());
        assert_eq!(state.tracked_ids.iter().len(), 6);
        assert_eq!(
            state.load_value(address.clone(), ByteSize::new(4), None),
            stack_param
        );
        assert_eq!(state.tracked_ids.iter().len(), 7);
        assert_eq!(
            state
                .tracked_ids
                .get(&stack_param_id)
                .unwrap()
                .is_accessed(),
            false
        ); // The load method does not set access flags.
    }

    #[test]
    fn test_load_unsized_from_stack() {
        let mut state = State::mock_arm32();
        // Load an existing stack param (generated by a sized load at the same address)
        let address = DataDomain::from_target(mock_stack_id(), bitvec!("0x0:4").into());
        let stack_param_id = mock_stack_param_id(0, 4);
        let stack_param = DataDomain::from_target(stack_param_id.clone(), bitvec!("0x0:4").into());
        state.load_value(address, ByteSize::new(4), None);
        let unsized_load = state.load_unsized_value_from_stack(bitvec!("0x0:4").into());
        assert_eq!(unsized_load, stack_param);
        assert!(state.tracked_ids.get(&stack_param_id).is_some());
        // Load a non-existing stack param
        let stack_param_id = mock_stack_param_id(4, 1);
        let stack_param = DataDomain::from_target(stack_param_id.clone(), bitvec!("0x0:1").into());
        let unsized_load = state.load_unsized_value_from_stack(bitvec!("0x4:4"));
        assert_eq!(unsized_load, stack_param);
        assert!(state.tracked_ids.get(&stack_param_id).is_some());
        // Unsized load from the current stack frame
        let unsized_load = state.load_unsized_value_from_stack(bitvec!("-4:4"));
        assert_eq!(unsized_load, DataDomain::new_top(ByteSize::new(1)));
    }

    #[test]
    fn test_load_nested_pointers() {
        let mut state = State::mock_arm32();
        let global_memory = RuntimeMemoryImage::mock();
        let parent_id = AbstractIdentifier::mock_nested("mock_fn", "r0:4", &[4], 4);
        let pointer = DataDomain::from_target(parent_id.clone(), bitvec!("0x8:4").into());
        let loaded_value = state.load_value(pointer, ByteSize::new(4), Some(&global_memory));
        let expected_id = AbstractIdentifier::mock_nested("mock_fn", "r0:4", &[4, 8], 4);
        let expected_value = DataDomain::from_target(expected_id.clone(), bitvec!("0x0:4").into());
        assert_eq!(loaded_value, expected_value);
    }
}