Pre-Comparison Value Transforms on MagicRule#
MagicRule::value_transform implements the magic(5) arithmetic suffix that shapes a buffer-read value before the comparison operator runs and before printf-style %d/%x format substitution. The syntax appears between the type keyword and the operator: lelong+1 x volume %d reads a long-endian long, adds 1, and formats it; ulequad/1073741824 x size %lluGB reads an unsigned quad and divides by 2^30.
Data Structures#
The transform is stored on MagicRule::value_transform as Option<ValueTransform> with #[serde(default)], so existing serialized AST snapshots that predate the field deserialize to None (no transform). Default is None.
ValueTransform holds two fields:
op: ValueTransformOp— the arithmetic operationoperand: i64— the signed operand (bitwise ops reinterpret it asu64at evaluation time)
ValueTransformOp is #[non_exhaustive] with seven variants:
| Variant | magic(5) syntax | Notes |
|---|---|---|
Add | type+N | |
Sub | type-N | |
Mul | type*N | Overflow → InvalidValueTransform |
Div | type/N | Zero divisor → InvalidValueTransform |
Mod | type%N | Zero divisor → InvalidValueTransform |
BitAnd | type&N | See architectural note below |
Or | type|N | |
Xor | type^N |
The value_transform field was added to MagicRule in v0.6.0 . Because MagicRule is #[non_exhaustive], all external construction must go through MagicRule::new(...) builder methods — struct literal construction fails to compile if this field is absent .
Architectural Distinction: ValueTransform vs. Operator::BitwiseAndMask#
These two layers handle different concerns and must not be conflated.
ValueTransform is a value-shaping pre-pass. It runs after the type-specific buffer read and before the comparison operator, producing a transformed Value that becomes the left-hand side of the comparison. It also affects printf substitution: format specifiers in the rule message see the post-transform number.
Operator::BitwiseAndMask(u64) is a comparison-time mask baked into the equality check. long &0xff = 0x42 reads a long, ANDs with 0xff, and compares to 0x42 — the mask is part of the equality test, not a separate value-shaping step .
The distinction matters for rules that need the masked value for format substitution. ValueTransformOp::BitAnd exists precisely for this case: lelong&0xff x %d reads a long, masks to the lowest byte via the BitAnd transform, then prints the masked value with %d. That rule cannot be expressed with Operator::BitwiseAndMask because BitwiseAndMask implies equality comparison.
The parser encodes the legacy &MASK VALUE form (mask + implicit equal) via Operator::BitwiseAndMask for backward compatibility. When followed by another operator (x, >, !=, ...), the parser promotes &MASK to ValueTransformOp::BitAnd .
Do not add a BitwiseAnd variant to ValueTransformOp as a synonym for the operator-layer BitwiseAndMask. Doing so would create two encodings for the same semantic, silently breaking any code that dispatches on Operator variants to detect mask operations.
apply_value_transform and Error Handling#
apply_value_transform in src/evaluator/operators/mod.rs has the signature:
pub fn apply_value_transform(
value: &Value,
transform: ValueTransform,
) -> Result<Value, EvaluationError>
It dispatches on (value, transform.op) using checked arithmetic (checked_add, checked_sub, checked_mul, checked_div, checked_rem) for integer variants, returning EvaluationError::InvalidValueTransform { reason: String } on:
DivorModwith a zero operand- any arithmetic overflow or underflow
Non-numeric values (Float, String, Bytes) are returned unchanged rather than failing .
For bitwise ops, the i64 operand is reinterpreted bit-for-bit as u64 to match libmagic's apprentice.c::mconvert semantics .
Graceful-Skip Requirement#
EvaluationError::InvalidValueTransform is in the engine's graceful-skip allowlist in src/evaluator/engine/mod.rs alongside BufferOverrun, InvalidOffset, and TypeReadError(...). A rule that triggers overflow or division-by-zero is discarded and logged at warn!; evaluation continues with the next rule. Removing InvalidValueTransform from this allowlist would cause a single malformed transform (e.g., lequad*0) to abort the entire evaluation run.
Property Test Coverage Gap#
arb_magic_rule() in tests/property_tests.rs hard-codes value_transform: None (line 155). This means property tests (prop_evaluation_never_panics, prop_rule_serde_roundtrip, and others) never generate rules with arithmetic transforms; overflow paths, BitAnd masking, and format-substitution after transform are not exercised by proptest.
To close this gap, arb_magic_rule should generate transforms via a prop_oneof branch — for example:
prop_oneof![
Just(None),
(arb_value_transform_op(), any::<i64>())
.prop_map(|(op, operand)| Some(ValueTransform { op, operand }))
]
Note: ValueTransformOp::Div and ValueTransformOp::Mod with operand == 0 should be tested explicitly against EvaluationError::InvalidValueTransform rather than excluded from generation — the engine is required to handle them gracefully.
Known Issues and Related References#
PR #233 gaps (merged 2026-04-25):
- Gap 3 (doc/code drift): The
apply_value_transformhelper's docstring referenced an incorrect error variant name. The correct variant isEvaluationError::InvalidValueTransform { reason }. - Gap 4 (rustdoc failures): Eight doctest examples in the codebase constructed
MagicRulevia struct literals without thevalue_transformfield, causing doctest compilation failures after v0.6.0 added the field. All external construction must useMagicRule::new(...)or includevalue_transform: None.
GOTCHAS S2.3: Value exhaustive matches that interact with ValueTransform must include a wildcard arm because Value is #[non_exhaustive] — apply_value_transform already follows this with its catch-all _ => Ok(value.clone()) branch .
Related topics:
- Comparison Operators And Compare_Values Helper Pattern — covers the comparison helper that runs after the transform
- Type System And Operator Coverage — full
Operatorenum inventory - AGENTS.md "Current Limitations / Offset Specifications" — documents the
value_transformsurface as a current limitation