use crate::{Limits, SexpKind, inspect as prefix};
#[derive(Debug, Clone, Copy)]
pub struct InspectionOptions {
pub(crate) limits: Limits,
pub(crate) max_formals: usize,
pub(crate) max_bytes_visited: usize,
}
impl Default for InspectionOptions {
fn default() -> Self {
Self {
limits: Limits::default(),
max_formals: 1_000_000,
max_bytes_visited: 256 * 1024 * 1024,
}
}
}
impl InspectionOptions {
#[must_use]
pub fn limits(mut self, value: Limits) -> Self {
self.limits = value;
self
}
#[must_use]
pub fn max_formals(mut self, value: usize) -> Self {
self.max_formals = value;
self
}
#[must_use]
pub fn max_bytes_visited(mut self, value: usize) -> Self {
self.max_bytes_visited = value;
self
}
}
pub fn inspect(bytes: &[u8]) -> Result<StoredObjectInspection, PrefixFailure> {
inspect_with_options(bytes, InspectionOptions::default())
}
pub fn inspect_with_options(
bytes: &[u8],
options: InspectionOptions,
) -> Result<StoredObjectInspection, PrefixFailure> {
prefix::inspect_stored_object(bytes, options)
.map(map_inspection)
.map_err(map_prefix_failure)
}
pub type StoredKind = SexpKind;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum InspectionExtent {
RootTagOnly,
ThroughFormals {
body_offset: usize,
record_len: usize,
body_kind: StoredKind,
body_validation: BodyValidation,
},
}
impl InspectionExtent {
#[must_use]
pub fn body_offset(&self) -> Option<usize> {
match self {
Self::ThroughFormals { body_offset, .. } => Some(*body_offset),
Self::RootTagOnly => None,
}
}
#[must_use]
pub fn body_kind(&self) -> Option<StoredKind> {
match self {
Self::ThroughFormals { body_kind, .. } => Some(*body_kind),
Self::RootTagOnly => None,
}
}
#[must_use]
pub fn body_validation(&self) -> Option<BodyValidation> {
match self {
Self::ThroughFormals {
body_validation, ..
} => Some(*body_validation),
Self::RootTagOnly => None,
}
}
#[must_use]
pub fn record_len(&self) -> Option<usize> {
match self {
Self::ThroughFormals { record_len, .. } => Some(*record_len),
Self::RootTagOnly => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum DefaultPresence {
Absent,
Present,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Formal {
name: String,
default: DefaultPresence,
}
impl Formal {
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[must_use]
pub fn default(&self) -> DefaultPresence {
self.default
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FunctionFormals {
values: Vec<Formal>,
}
impl FunctionFormals {
#[must_use]
pub fn as_slice(&self) -> &[Formal] {
&self.values
}
#[must_use]
pub fn len(&self) -> usize {
self.values.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
#[must_use]
pub fn get(&self, index: usize) -> Option<&Formal> {
self.values.get(index)
}
pub fn iter(&self) -> impl Iterator<Item = &Formal> {
self.values.iter()
}
}
impl AsRef<[Formal]> for FunctionFormals {
fn as_ref(&self) -> &[Formal] {
self.as_slice()
}
}
impl std::ops::Index<usize> for FunctionFormals {
type Output = Formal;
fn index(&self, index: usize) -> &Self::Output {
&self.values[index]
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum FormalsInspection {
Available(FunctionFormals),
NotApplicable(FormalsNotApplicable),
Unavailable(FormalsUnavailable),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum FormalsNotApplicable {
BuiltIn,
Special,
NonClosure,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum FormalsUnavailable {
PromiseNotEvaluated,
PersistentReferenceUnresolved,
Prefix(PrefixFailure),
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("{phase:?} failure at byte {offset}: {cause:?}")]
pub struct PrefixFailure {
phase: FailurePhase,
offset: usize,
cause: FailureCause,
}
impl PrefixFailure {
#[must_use]
pub fn phase(&self) -> FailurePhase {
self.phase
}
#[must_use]
pub fn offset(&self) -> usize {
self.offset
}
#[must_use]
pub fn cause(&self) -> &FailureCause {
&self.cause
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum FailurePhase {
Root,
Attributes,
Environment,
Formals,
Default(usize),
BodyTag,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum FailureCause {
Unsupported { type_code: u8, kind: StoredKind },
Malformed,
ResourceLimit,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum BodyValidation {
NotValidated,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StoredObjectInspection {
kind: StoredKind,
extent: InspectionExtent,
formals: FormalsInspection,
}
impl StoredObjectInspection {
#[must_use]
pub fn kind(&self) -> StoredKind {
self.kind
}
#[must_use]
pub fn extent(&self) -> &InspectionExtent {
&self.extent
}
#[must_use]
pub fn formals(&self) -> &FormalsInspection {
&self.formals
}
}
fn map_inspection(value: prefix::PrefixInspection) -> StoredObjectInspection {
let formals = match value.formals {
prefix::FormalsInspection::Available(values) => {
FormalsInspection::Available(FunctionFormals {
values: values
.into_iter()
.map(|formal| Formal {
name: formal.name,
default: match formal.default {
prefix::DefaultPresence::Absent => DefaultPresence::Absent,
prefix::DefaultPresence::Present => DefaultPresence::Present,
},
})
.collect(),
})
}
prefix::FormalsInspection::NotApplicable => match value.kind {
SexpKind::Promise => {
FormalsInspection::Unavailable(FormalsUnavailable::PromiseNotEvaluated)
}
SexpKind::Persist => {
FormalsInspection::Unavailable(FormalsUnavailable::PersistentReferenceUnresolved)
}
SexpKind::BuiltIn => FormalsInspection::NotApplicable(FormalsNotApplicable::BuiltIn),
SexpKind::Special => FormalsInspection::NotApplicable(FormalsNotApplicable::Special),
_ => FormalsInspection::NotApplicable(FormalsNotApplicable::NonClosure),
},
prefix::FormalsInspection::Unavailable(failure) => {
FormalsInspection::Unavailable(FormalsUnavailable::Prefix(map_prefix_failure(failure)))
}
};
StoredObjectInspection {
kind: value.kind,
extent: match value.extent {
prefix::InspectionExtent::RootTagOnly => InspectionExtent::RootTagOnly,
prefix::InspectionExtent::ThroughFormals {
body_offset,
record_len,
body_kind,
} => InspectionExtent::ThroughFormals {
body_offset,
record_len,
body_kind,
body_validation: BodyValidation::NotValidated,
},
},
formals,
}
}
fn map_prefix_failure(value: prefix::PrefixFailure) -> PrefixFailure {
PrefixFailure {
phase: match value.phase {
prefix::FailurePhase::Root => FailurePhase::Root,
prefix::FailurePhase::Attributes => FailurePhase::Attributes,
prefix::FailurePhase::Environment => FailurePhase::Environment,
prefix::FailurePhase::Formals => FailurePhase::Formals,
prefix::FailurePhase::Default(index) => FailurePhase::Default(index),
prefix::FailurePhase::BodyTag => FailurePhase::BodyTag,
},
offset: value.offset,
cause: match value.reason {
prefix::FailureReason::Unsupported { type_code, kind } => {
FailureCause::Unsupported { type_code, kind }
}
prefix::FailureReason::Malformed => FailureCause::Malformed,
prefix::FailureReason::ResourceLimit => FailureCause::ResourceLimit,
},
}
}
#[cfg(test)]
mod tests {
use super::*;
fn map_kind(kind: SexpKind) -> StoredObjectInspection {
map_inspection(prefix::PrefixInspection {
kind,
extent: prefix::InspectionExtent::RootTagOnly,
formals: prefix::FormalsInspection::NotApplicable,
})
}
#[test]
fn maps_non_closure_formal_reasons_without_inventing_failures() {
assert!(matches!(
map_kind(SexpKind::BuiltIn).formals(),
FormalsInspection::NotApplicable(FormalsNotApplicable::BuiltIn)
));
assert!(matches!(
map_kind(SexpKind::Special).formals(),
FormalsInspection::NotApplicable(FormalsNotApplicable::Special)
));
assert!(matches!(
map_kind(SexpKind::Integer).formals(),
FormalsInspection::NotApplicable(FormalsNotApplicable::NonClosure)
));
assert!(matches!(
map_kind(SexpKind::Promise).formals(),
FormalsInspection::Unavailable(FormalsUnavailable::PromiseNotEvaluated)
));
assert!(matches!(
map_kind(SexpKind::Persist).formals(),
FormalsInspection::Unavailable(FormalsUnavailable::PersistentReferenceUnresolved)
));
}
}