1#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3pub struct ShellBudget {
4 pub interfaces: usize,
6 pub fields: usize,
8 pub methods: usize,
10 pub attributes: usize,
12 pub attribute_bytes: usize,
14}
15#[derive(Clone, Debug, Eq, PartialEq)]
17pub struct AttributeShell {
18 pub name_index: u16,
20 pub declared_length: u32,
22 pub bytes: Vec<u8>,
24 pub origin: Origin,
26 pub location: AttributeLocation,
28}
29
30#[derive(Clone, Copy, Debug, Eq, PartialEq)]
32pub enum AttributeOwner {
33 Class,
35 Field(usize),
37 Method(usize),
39}
40
41#[derive(Clone, Copy, Debug, Eq, PartialEq)]
43pub struct AttributeLocation {
44 pub owner: AttributeOwner,
46 pub order: usize,
48}
49
50#[derive(Clone, Debug, Eq, PartialEq)]
52pub struct LayoutInvalidation {
53 pub path: String,
55 pub shifts_following_layout: bool,
57}
58
59#[derive(Clone, Debug, Eq, PartialEq)]
61pub struct EditReport {
62 pub invalidated: Vec<LayoutInvalidation>,
64}
65
66#[derive(Clone, Debug, Eq, PartialEq)]
68pub struct FieldShell {
69 pub access_flags: u16,
71 pub name_index: u16,
73 pub descriptor_index: u16,
75 pub attributes: Vec<AttributeShell>,
77 pub origin: Origin,
79}
80
81#[derive(Clone, Debug, Eq, PartialEq)]
83pub struct MethodShell {
84 pub access_flags: u16,
86 pub name_index: u16,
88 pub descriptor_index: u16,
90 pub attributes: Vec<AttributeShell>,
92 pub origin: Origin,
94}
95
96#[derive(Clone, Debug, PartialEq)]
98pub struct ClassShell {
99 pub minor_version: u16,
101 pub major_version: u16,
103 pub constant_pool: ConstantPool,
105 pub access_flags: u16,
107 pub this_class: u16,
109 pub super_class: u16,
111 pub interfaces: Vec<u16>,
113 pub fields: Vec<FieldShell>,
115 pub methods: Vec<MethodShell>,
117 pub attributes: Vec<AttributeShell>,
119 pub origin: Origin,
121}
122
123#[derive(Clone, Copy, Debug, Eq, PartialEq)]
125pub struct ClassIndex(pub u16);
126
127#[derive(Clone, Copy, Debug, Eq, PartialEq)]
129pub struct Utf8Index(pub u16);
130
131#[derive(Clone, Debug, Eq, PartialEq)]
133pub struct ValidatedFieldShell {
134 pub name: Utf8Index,
136 pub descriptor: Utf8Index,
138 pub attribute_names: Vec<Utf8Index>,
140}
141
142#[derive(Clone, Debug, Eq, PartialEq)]
144pub struct ValidatedMethodShell {
145 pub name: Utf8Index,
147 pub descriptor: Utf8Index,
149 pub attribute_names: Vec<Utf8Index>,
151}
152
153#[derive(Clone, Debug, Eq, PartialEq)]
155pub struct ValidatedClassShell {
156 pub this_class: ClassIndex,
158 pub super_class: Option<ClassIndex>,
160 pub interfaces: Vec<ClassIndex>,
162 pub fields: Vec<ValidatedFieldShell>,
164 pub methods: Vec<ValidatedMethodShell>,
166 pub attribute_names: Vec<Utf8Index>,
168}
169
170#[derive(Clone, Copy, Debug, Eq, PartialEq)]
172pub enum ShellErrorKind {
173 Magic,
175 Bytes,
177 ConstantPool,
179 Budget,
181 InvalidIndex,
183 TrailingBytes,
185 Edit,
187}
188
189#[derive(Clone, Debug, Eq, PartialEq)]
191pub struct ShellError {
192 pub kind: ShellErrorKind,
194 pub offset: usize,
196 pub index: Option<u16>,
198 pub path: String,
200 pub message: String,
202}
203
204impl fmt::Display for ShellError {
205 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206 write!(
207 f,
208 "{} at {} (byte {})",
209 self.message, self.path, self.offset
210 )
211 }
212}
213
214impl std::error::Error for ShellError {}
215
216impl ClassShell {
217 pub fn decode(
219 bytes: &[u8],
220 allocation_budget: usize,
221 budget: ShellBudget,
222 codec: CodecId,
223 source: SourceId,
224 ) -> Result<Self, ShellError> {
225 let mut reader = ByteReader::new(bytes, allocation_budget);
226 if reader.read_u4().map_err(|e| byte_error("magic", e))? != 0xcafe_babe {
227 return Err(error(
228 ShellErrorKind::Magic,
229 0,
230 None,
231 "magic",
232 "invalid classfile magic",
233 ));
234 }
235 let minor_version = reader
236 .read_u2()
237 .map_err(|e| byte_error("minor_version", e))?;
238 let major_version = reader
239 .read_u2()
240 .map_err(|e| byte_error("major_version", e))?;
241 let constant_pool = ConstantPool::decode(&mut reader, major_version).map_err(pool_error)?;
242 let access_flags = reader
243 .read_u2()
244 .map_err(|e| byte_error("access_flags", e))?;
245 let this_class = reader.read_u2().map_err(|e| byte_error("this_class", e))?;
246 let super_class = reader.read_u2().map_err(|e| byte_error("super_class", e))?;
247 let mut state = DecodeState {
248 budget,
249 attributes: 0,
250 attribute_bytes: 0,
251 codec,
252 source,
253 };
254 let interfaces = read_indices(&mut reader, budget.interfaces, "interfaces")?;
255 let fields = read_members(
256 &mut reader,
257 budget.fields,
258 "fields",
259 AttributeOwner::Field,
260 &mut state,
261 )?
262 .into_iter()
263 .map(Member::into_field)
264 .collect();
265 let methods = read_members(
266 &mut reader,
267 budget.methods,
268 "methods",
269 AttributeOwner::Method,
270 &mut state,
271 )?
272 .into_iter()
273 .map(Member::into_method)
274 .collect();
275 let attributes =
276 read_attributes(&mut reader, "attributes", AttributeOwner::Class, &mut state)?;
277 if reader.remaining() != 0 {
278 return Err(error(
279 ShellErrorKind::TrailingBytes,
280 reader.offset(),
281 None,
282 "class",
283 "trailing bytes after class shell",
284 ));
285 }
286 Ok(Self {
287 minor_version,
288 major_version,
289 constant_pool,
290 access_flags,
291 this_class,
292 super_class,
293 interfaces,
294 fields,
295 methods,
296 attributes,
297 origin: origin(codec, state.source, 0, reader.offset()),
298 })
299 }
300
301 pub fn validate(&self) -> Result<ValidatedClassShell, ShellError> {
303 let this_class = self.class_index(self.this_class, "this_class", &self.origin)?;
304 let super_class = if self.super_class == 0 {
305 None
306 } else {
307 Some(self.class_index(self.super_class, "super_class", &self.origin)?)
308 };
309 let interfaces = self
310 .interfaces
311 .iter()
312 .enumerate()
313 .map(|(position, &index)| {
314 self.class_index(index, &format!("interfaces[{position}]"), &self.origin)
315 })
316 .collect::<Result<_, _>>()?;
317 let fields = self
318 .fields
319 .iter()
320 .enumerate()
321 .map(|(position, member)| {
322 Ok(ValidatedFieldShell {
323 name: self.utf8_index(
324 member.name_index,
325 &format!("fields[{position}].name_index"),
326 &member.origin,
327 )?,
328 descriptor: self.utf8_index(
329 member.descriptor_index,
330 &format!("fields[{position}].descriptor_index"),
331 &member.origin,
332 )?,
333 attribute_names: self
334 .validate_attributes(&member.attributes, &format!("fields[{position}]"))?,
335 })
336 })
337 .collect::<Result<_, ShellError>>()?;
338 let methods = self
339 .methods
340 .iter()
341 .enumerate()
342 .map(|(position, member)| {
343 Ok(ValidatedMethodShell {
344 name: self.utf8_index(
345 member.name_index,
346 &format!("methods[{position}].name_index"),
347 &member.origin,
348 )?,
349 descriptor: self.utf8_index(
350 member.descriptor_index,
351 &format!("methods[{position}].descriptor_index"),
352 &member.origin,
353 )?,
354 attribute_names: self
355 .validate_attributes(&member.attributes, &format!("methods[{position}]"))?,
356 })
357 })
358 .collect::<Result<_, ShellError>>()?;
359 Ok(ValidatedClassShell {
360 this_class,
361 super_class,
362 interfaces,
363 fields,
364 methods,
365 attribute_names: self.validate_attributes(&self.attributes, "class")?,
366 })
367 }
368
369 pub fn encode(&self, allocation_budget: usize) -> Result<Vec<u8>, ShellError> {
371 self.validate()?;
372 let mut out = ByteWriter::new(allocation_budget);
373 out.write_u4(0xcafe_babe)
374 .map_err(|e| byte_error("magic", e))?;
375 out.write_u2(self.minor_version)
376 .map_err(|e| byte_error("minor_version", e))?;
377 out.write_u2(self.major_version)
378 .map_err(|e| byte_error("major_version", e))?;
379 self.constant_pool
380 .encode(&mut out, self.major_version)
381 .map_err(pool_error)?;
382 out.write_u2(self.access_flags)
383 .map_err(|e| byte_error("access_flags", e))?;
384 out.write_u2(self.this_class)
385 .map_err(|e| byte_error("this_class", e))?;
386 out.write_u2(self.super_class)
387 .map_err(|e| byte_error("super_class", e))?;
388 write_indices(&mut out, &self.interfaces, "interfaces")?;
389 write_members(&mut out, &self.fields, "fields")?;
390 write_members(&mut out, &self.methods, "methods")?;
391 write_attributes(&mut out, &self.attributes, "class")?;
392 Ok(out.into_bytes())
393 }
394
395 pub fn replace_method_code(
401 &mut self,
402 method_index: usize,
403 code: Vec<u8>,
404 allocation_budget: usize,
405 ) -> Result<EditReport, ShellError> {
406 let code_name = self.constant_pool.slots().iter().position(|slot| {
407 matches!(slot, crate::ConstantSlot::Entry(Constant::Utf8(value)) if value.as_code_units() == ['C' as u16, 'o' as u16, 'd' as u16, 'e' as u16])
408 }).ok_or_else(|| edit_error("constant_pool", "constant pool does not contain Code"))? as u16;
409 let method = self.methods.get_mut(method_index).ok_or_else(|| {
410 edit_error(
411 format!("methods[{method_index}]"),
412 "method index is out of range",
413 )
414 })?;
415 let (attribute_index, attribute) = method
416 .attributes
417 .iter_mut()
418 .enumerate()
419 .find(|(_, attribute)| attribute.name_index == code_name)
420 .ok_or_else(|| {
421 edit_error(
422 format!("methods[{method_index}]"),
423 "method has no Code attribute",
424 )
425 })?;
426 let old_len = attribute.bytes.len();
427 let mut structured =
428 CodeAttribute::decode(&mut ByteReader::new(&attribute.bytes, allocation_budget))
429 .map_err(|cause| {
430 edit_error(
431 format!("methods[{method_index}].attributes[{attribute_index}]"),
432 cause.to_string(),
433 )
434 })?;
435 structured.code = code;
436 let bytes = structured.encode(allocation_budget).map_err(|cause| {
437 edit_error(
438 format!("methods[{method_index}].attributes[{attribute_index}]"),
439 cause.to_string(),
440 )
441 })?;
442 attribute.declared_length = u32::try_from(bytes.len())
443 .map_err(|_| edit_error("Code", "encoded Code attribute exceeds u32"))?;
444 attribute.bytes = bytes;
445 Ok(EditReport {
446 invalidated: vec![LayoutInvalidation {
447 path: format!("methods[{method_index}].attributes[{attribute_index}].bytes"),
448 shifts_following_layout: old_len != attribute.bytes.len(),
449 }],
450 })
451 }
452
453 fn class_index(&self, index: u16, path: &str, at: &Origin) -> Result<ClassIndex, ShellError> {
454 self.expect(index, path, at, |entry| {
455 matches!(entry, Constant::Class { .. })
456 })?;
457 Ok(ClassIndex(index))
458 }
459
460 fn utf8_index(&self, index: u16, path: &str, at: &Origin) -> Result<Utf8Index, ShellError> {
461 self.expect(index, path, at, |entry| matches!(entry, Constant::Utf8(_)))?;
462 Ok(Utf8Index(index))
463 }
464
465 fn expect(
466 &self,
467 index: u16,
468 path: &str,
469 at: &Origin,
470 predicate: impl FnOnce(&Constant) -> bool,
471 ) -> Result<(), ShellError> {
472 let entry = self.constant_pool.entry(index, index).map_err(|cause| {
473 error(
474 ShellErrorKind::InvalidIndex,
475 at.span.start,
476 Some(index),
477 path,
478 format!("invalid constant-pool index {index}: {cause}"),
479 )
480 })?;
481 if !predicate(entry) {
482 return Err(error(
483 ShellErrorKind::InvalidIndex,
484 at.span.start,
485 Some(index),
486 path,
487 format!("constant-pool index {index} has the wrong category"),
488 ));
489 }
490 Ok(())
491 }
492
493 fn validate_attributes(
494 &self,
495 attributes: &[AttributeShell],
496 owner: &str,
497 ) -> Result<Vec<Utf8Index>, ShellError> {
498 attributes
499 .iter()
500 .enumerate()
501 .map(|(position, attribute)| {
502 self.utf8_index(
503 attribute.name_index,
504 &format!("{owner}.attributes[{position}].name_index"),
505 &attribute.origin,
506 )
507 })
508 .collect()
509 }
510}