Documents
Pre-Comparison Value Transforms On MagicRule
Pre-Comparison Value Transforms On MagicRule
Type
Topic
Status
Published
Created
Apr 25, 2026
Updated
Apr 25, 2026
Created by
Dosu Bot
Updated by
Dosu Bot

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 operation
  • operand: i64 — the signed operand (bitwise ops reinterpret it as u64 at evaluation time)

ValueTransformOp is #[non_exhaustive] with seven variants:

Variantmagic(5) syntaxNotes
Addtype+N
Subtype-N
Multype*NOverflow → InvalidValueTransform
Divtype/NZero divisor → InvalidValueTransform
Modtype%NZero divisor → InvalidValueTransform
BitAndtype&NSee architectural note below
Ortype|N
Xortype^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:

  • Div or Mod with 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.

PR #233 gaps (merged 2026-04-25):

  • Gap 3 (doc/code drift): The apply_value_transform helper's docstring referenced an incorrect error variant name. The correct variant is EvaluationError::InvalidValueTransform { reason } .
  • Gap 4 (rustdoc failures): Eight doctest examples in the codebase constructed MagicRule via struct literals without the value_transform field, causing doctest compilation failures after v0.6.0 added the field. All external construction must use MagicRule::new(...) or include value_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:

Pre-Comparison Value Transforms On MagicRule | Dosu