Skip to main content

sim_codec_classfile/constant/
model.rs

1/// One usable JVM constant-pool entry.
2#[derive(Clone, Debug, PartialEq)]
3pub enum Constant {
4    /// A modified-UTF-8 string, represented without losing UTF-16 code units.
5    Utf8(CodeUnitString),
6    /// A signed `int` bit pattern.
7    Integer(u32),
8    /// An IEEE 754 single-precision bit pattern.
9    Float(u32),
10    /// A signed `long` bit pattern.
11    Long(u64),
12    /// An IEEE 754 double-precision bit pattern.
13    Double(u64),
14    /// A class or interface name index.
15    Class {
16        /// Index of the `Utf8` internal name.
17        name_index: u16,
18    },
19    /// A string contents index.
20    String {
21        /// Index of the `Utf8` string contents.
22        string_index: u16,
23    },
24    /// A field reference.
25    Fieldref {
26        /// Index of the declaring `Class`.
27        class_index: u16,
28        /// Index of the member `NameAndType`.
29        name_and_type_index: u16,
30    },
31    /// A class method reference.
32    Methodref {
33        /// Index of the declaring `Class`.
34        class_index: u16,
35        /// Index of the member `NameAndType`.
36        name_and_type_index: u16,
37    },
38    /// An interface method reference.
39    InterfaceMethodref {
40        /// Index of the declaring interface `Class`.
41        class_index: u16,
42        /// Index of the member `NameAndType`.
43        name_and_type_index: u16,
44    },
45    /// A name and descriptor pair.
46    NameAndType {
47        /// Index of the member-name `Utf8`.
48        name_index: u16,
49        /// Index of the descriptor `Utf8`.
50        descriptor_index: u16,
51    },
52    /// A direct method-handle reference.
53    MethodHandle {
54        /// JVM reference-kind discriminator from 1 through 9.
55        reference_kind: u8,
56        /// Index of the category selected by `reference_kind`.
57        reference_index: u16,
58    },
59    /// A method descriptor.
60    MethodType {
61        /// Index of the method-descriptor `Utf8`.
62        descriptor_index: u16,
63    },
64    /// A dynamically computed constant.
65    Dynamic {
66        /// Index into the class's `BootstrapMethods` attribute.
67        bootstrap_method_attr_index: u16,
68        /// Index of the constant's `NameAndType`.
69        name_and_type_index: u16,
70    },
71    /// A dynamically selected call site.
72    InvokeDynamic {
73        /// Index into the class's `BootstrapMethods` attribute.
74        bootstrap_method_attr_index: u16,
75        /// Index of the call site's `NameAndType`.
76        name_and_type_index: u16,
77    },
78    /// A module name.
79    Module {
80        /// Index of the module-name `Utf8`.
81        name_index: u16,
82    },
83    /// A package name.
84    Package {
85        /// Index of the package-name `Utf8`.
86        name_index: u16,
87    },
88}
89/// One physical constant-pool index, including indices that cannot be referenced.
90#[derive(Clone, Debug, PartialEq)]
91pub enum ConstantSlot {
92    /// The mandated, non-encoded index zero.
93    Reserved,
94    /// A usable entry encoded at this index.
95    Entry(Constant),
96    /// The explicit second slot occupied by the preceding `Long` or `Double`.
97    Unusable,
98}
99
100/// A stable constant-pool failure category.
101#[derive(Clone, Copy, Debug, Eq, PartialEq)]
102pub enum ConstantPoolErrorKind {
103    /// The underlying bounded byte lane failed.
104    Bytes,
105    /// The pool uses an unassigned constant tag.
106    UnknownTag,
107    /// The entry is not admitted by the classfile major version.
108    Version,
109    /// A method-handle reference kind is outside 1 through 9.
110    ReferenceKind,
111    /// A referenced index is zero or outside the pool.
112    InvalidIndex,
113    /// A reference points at a reserved or unusable slot.
114    UnusableTarget,
115    /// A reference points at the wrong constant category.
116    WrongCategory,
117    /// An in-memory slot sequence cannot be encoded faithfully.
118    InvalidLayout,
119}
120
121/// A typed constant-pool failure located at the entry that caused it.
122#[derive(Clone, Debug, Eq, PartialEq)]
123pub struct ConstantPoolError {
124    /// Stable machine-matchable failure category.
125    pub kind: ConstantPoolErrorKind,
126    /// Constant-pool index containing the invalid value, or the next index while decoding.
127    pub index: u16,
128    /// Referenced constant-pool index when the failure concerns a target.
129    pub target_index: Option<u16>,
130    /// Human-readable context.
131    pub message: String,
132}
133
134impl ConstantPoolError {
135    fn new(
136        kind: ConstantPoolErrorKind,
137        index: u16,
138        target: Option<u16>,
139        message: impl Into<String>,
140    ) -> Self {
141        Self {
142            kind,
143            index,
144            target_index: target,
145            message: message.into(),
146        }
147    }
148
149    fn bytes(index: u16, error: ByteError) -> Self {
150        Self::new(ConstantPoolErrorKind::Bytes, index, None, error.to_string())
151    }
152}
153
154impl fmt::Display for ConstantPoolError {
155    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
156        write!(f, "{} at constant-pool index {}", self.message, self.index)
157    }
158}
159
160impl std::error::Error for ConstantPoolError {}
161
162/// An index-preserving constant pool whose `slots()[index]` is the physical JVM slot.
163#[derive(Clone, Debug, PartialEq)]
164pub struct ConstantPool {
165    slots: Vec<ConstantSlot>,
166}
167
168impl ConstantPool {
169    /// Decode the `constant_pool_count` and all following entries for `major_version`.
170    pub fn decode(
171        reader: &mut ByteReader<'_>,
172        major_version: u16,
173    ) -> Result<Self, ConstantPoolError> {
174        let count = reader
175            .read_u2()
176            .map_err(|e| ConstantPoolError::bytes(0, e))?;
177        if count == 0 {
178            return Err(ConstantPoolError::new(
179                ConstantPoolErrorKind::InvalidLayout,
180                0,
181                None,
182                "constant_pool_count must include the reserved zero index",
183            ));
184        }
185        reader
186            .preflight_allocation(usize::from(count))
187            .map_err(|e| ConstantPoolError::bytes(0, e))?;
188        let mut slots = Vec::with_capacity(usize::from(count));
189        slots.push(ConstantSlot::Reserved);
190        let mut index = 1u16;
191        while index < count {
192            let tag = reader
193                .read_u1()
194                .map_err(|e| ConstantPoolError::bytes(index, e))?;
195            let constant = decode_constant(reader, index, tag, major_version)?;
196            let two_slot = matches!(constant, Constant::Long(_) | Constant::Double(_));
197            slots.push(ConstantSlot::Entry(constant));
198            index += 1;
199            if two_slot {
200                if index >= count {
201                    return Err(ConstantPoolError::new(
202                        ConstantPoolErrorKind::InvalidLayout,
203                        index - 1,
204                        None,
205                        "two-slot constant has no trailing unusable index",
206                    ));
207                }
208                slots.push(ConstantSlot::Unusable);
209                index += 1;
210            }
211        }
212        let pool = Self { slots };
213        pool.validate(major_version)?;
214        Ok(pool)
215    }
216
217    /// Borrow every physical slot. Index zero and two-slot holes remain explicit.
218    pub fn slots(&self) -> &[ConstantSlot] {
219        &self.slots
220    }
221
222    /// Return a usable entry or a typed located target error.
223    pub fn entry(
224        &self,
225        source_index: u16,
226        target_index: u16,
227    ) -> Result<&Constant, ConstantPoolError> {
228        match self.slots.get(usize::from(target_index)) {
229            Some(ConstantSlot::Entry(value)) => Ok(value),
230            Some(ConstantSlot::Reserved | ConstantSlot::Unusable) => Err(ConstantPoolError::new(
231                ConstantPoolErrorKind::UnusableTarget,
232                source_index,
233                Some(target_index),
234                format!("index {source_index} points at unusable index {target_index}"),
235            )),
236            None => Err(ConstantPoolError::new(
237                ConstantPoolErrorKind::InvalidIndex,
238                source_index,
239                Some(target_index),
240                format!("index {source_index} points outside the pool at index {target_index}"),
241            )),
242        }
243    }
244
245    /// Validate layout, version bounds, reference indices, and target categories.
246    pub fn validate(&self, major_version: u16) -> Result<(), ConstantPoolError> {
247        if !matches!(self.slots.first(), Some(ConstantSlot::Reserved))
248            || self.slots.len() > usize::from(u16::MAX)
249        {
250            return Err(ConstantPoolError::new(
251                ConstantPoolErrorKind::InvalidLayout,
252                0,
253                None,
254                "pool must begin with exactly one reserved slot and fit u16",
255            ));
256        }
257        for (position, slot) in self.slots.iter().enumerate().skip(1) {
258            let index = position as u16;
259            match slot {
260                ConstantSlot::Reserved => {
261                    return Err(ConstantPoolError::new(
262                        ConstantPoolErrorKind::InvalidLayout,
263                        index,
264                        None,
265                        "reserved slot is only legal at index zero",
266                    ));
267                }
268                ConstantSlot::Unusable => {
269                    if !matches!(
270                        self.slots.get(position - 1),
271                        Some(ConstantSlot::Entry(Constant::Long(_) | Constant::Double(_)))
272                    ) {
273                        return Err(ConstantPoolError::new(
274                            ConstantPoolErrorKind::InvalidLayout,
275                            index,
276                            None,
277                            "unusable slot must follow a long or double",
278                        ));
279                    }
280                }
281                ConstantSlot::Entry(value) => {
282                    if matches!(
283                        self.slots.get(position.wrapping_add(1)),
284                        Some(ConstantSlot::Unusable)
285                    ) != matches!(value, Constant::Long(_) | Constant::Double(_))
286                    {
287                        return Err(ConstantPoolError::new(
288                            ConstantPoolErrorKind::InvalidLayout,
289                            index,
290                            None,
291                            "long and double entries must own exactly one following unusable slot",
292                        ));
293                    }
294                    let minimum_major = match value {
295                        Constant::MethodHandle { .. }
296                        | Constant::MethodType { .. }
297                        | Constant::InvokeDynamic { .. } => Some(51),
298                        Constant::Module { .. } | Constant::Package { .. } => Some(53),
299                        Constant::Dynamic { .. } => Some(55),
300                        _ => None,
301                    };
302                    if let Some(minimum_major) = minimum_major
303                        && major_version < minimum_major
304                    {
305                        return Err(ConstantPoolError::new(
306                            ConstantPoolErrorKind::Version,
307                            index,
308                            None,
309                            format!(
310                                "constant at index {index} requires classfile major version {minimum_major}"
311                            ),
312                        ));
313                    }
314                    validate_constant(self, index, value, major_version)?;
315                }
316            }
317        }
318        Ok(())
319    }
320
321    /// Encode the count and entries after revalidating the pool.
322    pub fn encode(
323        &self,
324        writer: &mut ByteWriter,
325        major_version: u16,
326    ) -> Result<(), ConstantPoolError> {
327        self.validate(major_version)?;
328        writer
329            .write_u2(self.slots.len() as u16)
330            .map_err(|e| ConstantPoolError::bytes(0, e))?;
331        for (position, slot) in self.slots.iter().enumerate().skip(1) {
332            if let ConstantSlot::Entry(value) = slot {
333                encode_constant(writer, position as u16, value)?;
334            }
335        }
336        Ok(())
337    }
338}