Skip to main content

neo_decompiler/decompiler/analysis/
types.rs

1//! Lightweight type inference for lifted Neo N3 bytecode.
2//!
3//! The Neo VM is dynamically typed and most syscalls do not encode argument
4//! signatures in the bytecode. The goal of this module is therefore to provide
5//! a best-effort type recovery pass that is:
6//!
7//! - conservative (falls back to `unknown`/`any` rather than guessing)
8//! - useful for collection recovery and readability improvements
9//! - deterministic and panic-free on malformed input
10
11// Stack depth → i64 and type-tag byte reinterpretation casts are structurally
12// safe: stack depth fits in i64, and the i8→u8 cast is intentional.
13#![allow(
14    clippy::cast_possible_truncation,
15    clippy::cast_possible_wrap,
16    clippy::cast_sign_loss
17)]
18
19mod call_effects;
20mod return_depth;
21mod return_effects;
22mod simulation;
23mod slot_ops;
24mod stack_ops;
25mod support;
26
27use serde::Serialize;
28
29use crate::instruction::Instruction;
30use crate::manifest::ContractManifest;
31use crate::nef::MethodToken;
32
33use super::{MethodRef, MethodTable};
34use call_effects::build_direct_call_effects_by_offset;
35use simulation::infer_types_in_slice;
36use support::{scan_slot_counts, scan_static_slot_count, type_from_manifest};
37
38/// Primitive/value types inferred from the instruction stream.
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
40#[non_exhaustive]
41pub enum ValueType {
42    /// Unknown or not yet inferred.
43    #[serde(rename = "unknown")]
44    Unknown,
45    /// Dynamic `any` value.
46    #[serde(rename = "any")]
47    Any,
48    /// Null literal.
49    #[serde(rename = "null")]
50    Null,
51    /// Boolean.
52    #[serde(rename = "bool")]
53    Boolean,
54    /// Integer.
55    #[serde(rename = "integer")]
56    Integer,
57    /// ByteString.
58    #[serde(rename = "bytestring")]
59    ByteString,
60    /// Buffer.
61    #[serde(rename = "buffer")]
62    Buffer,
63    /// Array.
64    #[serde(rename = "array")]
65    Array,
66    /// Struct.
67    #[serde(rename = "struct")]
68    Struct,
69    /// Map.
70    #[serde(rename = "map")]
71    Map,
72    /// Interop interface.
73    #[serde(rename = "interopinterface")]
74    InteropInterface,
75    /// Pointer.
76    #[serde(rename = "pointer")]
77    Pointer,
78}
79
80impl ValueType {
81    fn join(self, other: Self) -> Self {
82        use ValueType::*;
83        if self == other {
84            return self;
85        }
86        match (self, other) {
87            (Unknown, x) | (x, Unknown) => x,
88            (Null, _) | (_, Null) => Any,
89            _ => Any,
90        }
91    }
92}
93
94/// Per-method inferred types.
95#[derive(Debug, Clone, Serialize)]
96pub struct MethodTypes {
97    /// Method whose slots were analyzed.
98    pub method: MethodRef,
99    /// Inferred argument types indexed by argument slot.
100    pub arguments: Vec<ValueType>,
101    /// Inferred local types indexed by local slot.
102    pub locals: Vec<ValueType>,
103}
104
105/// Aggregated type inference results.
106#[derive(Debug, Clone, Default, Serialize)]
107pub struct TypeInfo {
108    /// Per-method inferred locals/arguments.
109    pub methods: Vec<MethodTypes>,
110    /// Inferred static slot types indexed by static slot.
111    pub statics: Vec<ValueType>,
112}
113
114/// Infer primitive types and collection kinds from the instruction stream.
115#[must_use]
116pub fn infer_types(instructions: &[Instruction], manifest: Option<&ContractManifest>) -> TypeInfo {
117    infer_types_with_method_tokens(instructions, manifest, &[])
118}
119
120/// Infer types with NEF method-token metadata available for CALLT stack effects.
121#[must_use]
122pub fn infer_types_with_method_tokens(
123    instructions: &[Instruction],
124    manifest: Option<&ContractManifest>,
125    method_tokens: &[MethodToken],
126) -> TypeInfo {
127    let table = MethodTable::new(instructions, manifest);
128    let static_count = scan_static_slot_count(instructions).unwrap_or(0);
129    let mut statics = vec![ValueType::Unknown; static_count];
130    let direct_call_effects =
131        build_direct_call_effects_by_offset(&table, instructions, manifest, method_tokens);
132
133    let mut methods = Vec::new();
134    // `instructions` is sorted by offset and `table.spans()` is sorted by start
135    // with contiguous ranges, so sweep a single forward cursor instead of
136    // re-filtering the whole instruction stream per span (O(instructions *
137    // spans), quadratic on call-dense bytecode). See build_xrefs.
138    let mut cursor = 0usize;
139    for span in table.spans() {
140        while cursor < instructions.len() && instructions[cursor].offset < span.start {
141            cursor += 1;
142        }
143        let begin = cursor;
144        while cursor < instructions.len() && instructions[cursor].offset < span.end {
145            cursor += 1;
146        }
147        let slice: Vec<&Instruction> = instructions[begin..cursor].iter().collect();
148
149        let (locals_count, args_count) = scan_slot_counts(&slice).unwrap_or((0, 0));
150        let mut locals = vec![ValueType::Unknown; locals_count];
151        let mut arguments = vec![ValueType::Unknown; args_count];
152
153        if let Some(manifest) = manifest {
154            if let Some(index) = table.manifest_index_for_start(span.start) {
155                if let Some(method) = manifest.abi.methods.get(index) {
156                    if arguments.len() < method.parameters.len() {
157                        arguments.resize(method.parameters.len(), ValueType::Unknown);
158                    }
159                    for (idx, param) in method.parameters.iter().enumerate() {
160                        arguments[idx] = arguments[idx].join(type_from_manifest(&param.kind));
161                    }
162                }
163            }
164        }
165
166        infer_types_in_slice(
167            &slice,
168            &mut locals,
169            &mut arguments,
170            &mut statics,
171            method_tokens,
172            &direct_call_effects,
173        );
174
175        methods.push(MethodTypes {
176            method: span.method.clone(),
177            arguments,
178            locals,
179        });
180    }
181
182    TypeInfo { methods, statics }
183}