#![forbid(unsafe_code)]
#![deny(missing_docs)]
use std::path::Path;
use serde_json::Value;
pub use xuanji::{
Baseline, BoundDecl, BoundId, BoundaryKind, Defence, Demonstrates, Extent, FactGranularity,
Finding, Observer, Outcome, Owner, Polarity, Reached, Report, RuleKey, ScanDepth, Severity,
StructuredFactIdentity, Subject, Violation, ViolationId, apply_baseline,
};
mod bounds;
pub use bounds::observation_bounds;
mod observer;
pub use observer::SemanticObserver;
mod dsl;
pub use dsl::*;
mod rules;
pub use rules::*;
mod collect;
mod containment;
mod crate_scope;
mod driver;
mod emit;
mod errors;
mod file_scope;
mod finding;
mod module_resolve;
mod resolve;
mod scan;
mod shape_scan;
mod syn_util;
mod async_exposure;
mod dyn_trait;
mod exposure;
mod forbidden_marker;
mod impl_trait;
mod trait_impl;
mod unsafe_confinement;
mod visibility;
pub use async_exposure::check_async_exposure;
pub use dyn_trait::check_dyn_trait;
pub use exposure::check;
pub use forbidden_marker::check_forbidden_marker;
pub use impl_trait::check_impl_trait;
pub use trait_impl::check_trait_impl_locality;
pub use unsafe_confinement::check_unsafe_confinement;
pub use visibility::check_visibility;
#[cfg(test)]
pub(crate) use async_exposure::{async_exposure_module_findings, async_exposure_subtree_findings};
#[cfg(test)]
pub(crate) use dyn_trait::{dyn_module_findings, dyn_operand_module_findings};
#[cfg(test)]
pub(crate) use forbidden_marker::forbidden_marker_findings;
#[cfg(test)]
pub(crate) use impl_trait::{
impl_trait_module_findings, impl_trait_operand_module_findings,
impl_trait_operand_subtree_findings, impl_trait_subtree_findings,
};
#[cfg(test)]
pub(crate) use trait_impl::trait_impl_findings;
#[cfg(test)]
pub(crate) use unsafe_confinement::unsafe_findings;
#[cfg(test)]
pub(crate) use visibility::visibility_findings;
use crate::async_exposure::check_async_exposure_boundary;
use crate::driver::{eval_into, outcome_from, read_metadata};
use crate::dyn_trait::check_dyn_trait_boundary;
use crate::exposure::check_boundary;
use crate::forbidden_marker::check_forbidden_marker_boundary;
use crate::impl_trait::check_impl_trait_boundary;
use crate::trait_impl::check_trait_impl_boundary;
use crate::unsafe_confinement::check_unsafe_boundary;
use crate::visibility::check_visibility_boundary;
#[derive(Debug, Clone, Default)]
pub struct SemanticBoundaries {
pub signature: Vec<SignatureBoundary>,
pub trait_impl: Vec<TraitImplBoundary>,
pub visibility: Vec<VisibilityBoundary>,
pub forbidden_marker: Vec<ForbiddenMarkerBoundary>,
pub dyn_trait: Vec<DynTraitBoundary>,
pub impl_trait: Vec<ImplTraitBoundary>,
pub async_exposure: Vec<AsyncExposureBoundary>,
pub unsafe_confinement: Vec<UnsafeBoundary>,
}
trait CapabilitySet<'a> {
fn is_empty(&self) -> bool;
fn len(&self) -> usize;
fn crate_packages(&self) -> Vec<&'a str>;
fn eval(&self, metadata: &Value, violations: &mut Vec<Violation>) -> Result<(), String>;
}
struct Capability<'a, B> {
boundaries: &'a [B],
crate_package: fn(&B) -> &str,
check: fn(&Value, &B, &mut Vec<Violation>) -> Result<(), String>,
}
impl<'a, B> CapabilitySet<'a> for Capability<'a, B> {
fn is_empty(&self) -> bool {
self.boundaries.is_empty()
}
fn crate_packages(&self) -> Vec<&'a str> {
self.boundaries
.iter()
.map(|boundary| (self.crate_package)(boundary))
.collect()
}
fn len(&self) -> usize {
self.boundaries.len()
}
fn eval(&self, metadata: &Value, violations: &mut Vec<Violation>) -> Result<(), String> {
eval_into(metadata, self.boundaries, self.check, violations)
}
}
impl SemanticBoundaries {
fn capability_sets(&self) -> Vec<Box<dyn CapabilitySet<'_> + '_>> {
vec![
Box::new(Capability {
boundaries: &self.signature,
crate_package: SignatureBoundary::crate_package,
check: check_boundary,
}),
Box::new(Capability {
boundaries: &self.trait_impl,
crate_package: TraitImplBoundary::crate_package,
check: check_trait_impl_boundary,
}),
Box::new(Capability {
boundaries: &self.visibility,
crate_package: VisibilityBoundary::crate_package,
check: check_visibility_boundary,
}),
Box::new(Capability {
boundaries: &self.forbidden_marker,
crate_package: ForbiddenMarkerBoundary::crate_package,
check: check_forbidden_marker_boundary,
}),
Box::new(Capability {
boundaries: &self.dyn_trait,
crate_package: DynTraitBoundary::crate_package,
check: check_dyn_trait_boundary,
}),
Box::new(Capability {
boundaries: &self.impl_trait,
crate_package: ImplTraitBoundary::crate_package,
check: check_impl_trait_boundary,
}),
Box::new(Capability {
boundaries: &self.async_exposure,
crate_package: AsyncExposureBoundary::crate_package,
check: check_async_exposure_boundary,
}),
Box::new(Capability {
boundaries: &self.unsafe_confinement,
crate_package: UnsafeBoundary::crate_package,
check: check_unsafe_boundary,
}),
]
}
pub fn is_empty(&self) -> bool {
self.capability_sets().iter().all(|set| set.is_empty())
}
pub fn declared(&self) -> usize {
self.capability_sets().iter().map(|set| set.len()).sum()
}
pub fn crate_packages(&self) -> impl Iterator<Item = &str> {
self.capability_sets()
.into_iter()
.flat_map(|set| set.crate_packages())
.collect::<Vec<_>>()
.into_iter()
}
}
fn eval_all(
metadata: &Value,
boundaries: &SemanticBoundaries,
violations: &mut Vec<Violation>,
) -> Result<(), String> {
for set in boundaries.capability_sets() {
set.eval(metadata, violations)?;
}
Ok(())
}
pub fn check_all(boundaries: &SemanticBoundaries, manifest_path: &Path) -> Outcome {
if boundaries.is_empty() {
return Outcome::Clean(Subject::nothing_declared());
}
let metadata = match read_metadata(manifest_path) {
Ok(metadata) => metadata,
Err(outcome) => return outcome,
};
let mut violations = Vec::new();
match eval_all(&metadata, boundaries, &mut violations) {
Ok(()) => outcome_from(
violations,
boundaries.declared(),
xingbiao::member_root_files(&metadata).len(),
),
Err(error) => Outcome::ConstitutionError(error),
}
}
#[cfg(test)]
mod tests;