Resolve N2: bound align annotations at 4096 (review #006)

align: 2^62 compiled and reported total_size = 2^63 — meaningless
layout output the consumer may act on, and the reachable path to the
MAX_ARRAY_BYTES cap used exactly this knob.

- MAX_ALIGN = 4096 (page granularity) in schema.rs, documented with the
  probe arithmetic
- parse_align returns Result and rejects over-cap values with a clean
  Schema error naming path/value/maximum — a silent clamp was rejected
  (it would change layout semantics without telling the consumer);
  both call sites thread the path, so standalone BastDoc::new (which
  never runs the meta-schema) is covered
- Meta-schema: "maximum": 4096 on StructDef.align and FieldDef.align —
  the published alk.dev/bast/v1/schema contract now matches the parser
- H1's byte-cap test retuned to align 4096 x count 2^16 = 2^28 > 2^26
  (the byte cap stays reachable under the new align cap)

Tests: 3 new (struct align above cap, field align above cap, align at
cap accepted). 511 tests green, clippy -D warnings clean, wasm32 build
green, cargo doc zero warnings.
This commit is contained in:
glm-5.3-flash committed 2026-09-02 19:46:51 +00:00
1 parent 8739d29550
commit 0bc5a541ac
5 files changed
+139 -21

No files matched your search

+29 -8
View File
@@ -36,7 +36,7 @@
use crate::error::AlkTypeError;
use crate::schema::{
AlkTypeKind, Endian, VariableEncoding, MAX_ARRAY_ELEMENTS,
AlkTypeKind, Endian, VariableEncoding, MAX_ALIGN, MAX_ARRAY_ELEMENTS,
};
use serde_json::Value;
@@ -308,7 +308,7 @@ impl BastStruct {
fn parse(node: &Value, path: &str, doc_root: &Value) -> Result<Self, AlkTypeError> {
let endian = parse_endian_opt(node).unwrap_or(Endian::Little);
let align = parse_align(node);
let align = parse_align(node, path)?;
let raw_fields = node
.get("fields")
.and_then(Value::as_array)
@@ -427,7 +427,7 @@ impl BastField {
})?;
let ty = BastType::parse(raw_kind, path, doc_root)?;
let endian = parse_endian_opt(node);
let align = parse_align(node);
let align = parse_align(node, path)?;
let encoding = parse_encoding(node);
let max_length = parse_max_length(node);
Ok(Self {
@@ -993,11 +993,32 @@ fn parse_endian_opt(node: &Value) -> Option<Endian> {
}
}
fn parse_align(node: &Value) -> Option<usize> {
node.as_object()
.and_then(|o| o.get("align"))
.and_then(Value::as_u64)
.and_then(|n| usize::try_from(n).ok())
/// Parse a struct- or field-level `align` annotation. Over-sized values
/// are a clean `Schema` error (review #006 N2: an unbounded align let a
/// one-field schema declare an exabyte-scale layout — `align_up` rounds
/// the running offset by the full annotation).
fn parse_align(node: &Value, path: &str) -> Result<Option<usize>, AlkTypeError> {
let raw = match node.as_object().and_then(|o| o.get("align")) {
None | Some(Value::Null) => return Ok(None),
Some(v) => v,
};
let n = raw.as_u64().ok_or_else(|| {
AlkTypeError::Schema(format!(
"bast: align at {path} is not a non-negative integer"
))
})?;
let n = usize::try_from(n).map_err(|_| {
AlkTypeError::Schema(format!(
"bast: align at {path} (= {n}) overflows usize"
))
})?;
if n > MAX_ALIGN {
return Err(AlkTypeError::Schema(format!(
"bast: align at {path} (= {n}) exceeds the maximum of {MAX_ALIGN} \
(review #006 N2: honest layouts never need more than page granularity)"
)));
}
Ok(Some(n))
}
fn parse_max_length(node: &Value) -> Option<usize> {