Skip to content

Instantly share code, notes, and snippets.

@huitseeker
Last active March 27, 2026 14:32
Show Gist options
  • Select an option

  • Save huitseeker/14607b48f99807e2d56ece564c1ece21 to your computer and use it in GitHub Desktop.

Select an option

Save huitseeker/14607b48f99807e2d56ece564c1ece21 to your computer and use it in GitHub Desktop.

UX Walkthrough: Hunting mod_12289 with the Old and New MASM Analyses

This walkthrough studies one bug hunt: the missing u32assert on the advice remainder in miden::core::crypto::dsa::falcon512_poseidon2::mod_12289.

It compares three user experiences:

  • the baseline analysis
  • the current flat diagnostics
  • the current optional grouped mode

The case study is the advice remainder in mod_12289. The quotient is checked with u32assert2, but the remainder loaded at line 57 reaches u32overflowing_sub and u32overflowing_add without a u32assert.

nl -ba /Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm | sed -n '34,82p'
    34	pub proc mod_12289
    35	    emit.FALCON_DIV_EVENT
    36	    # the advice stack contains now [qhi, qlo, r, ...] where q = qhi * 2^32 + qlo is quotient
    37	    # and r is remainder
    38	
    39	    adv_push.2
    40	    u32assert2
    41	    # => [qlo, qhi, a_hi, a_lo, ...]
    42	
    43	    push.M
    44	    u32widening_mul swap
    45	    # => [overflow, M * qlo % 2^32, qhi, a_hi, a_lo, ...]
    46	
    47	    movup.2
    48	    push.M
    49	    # => [M, qhi, overflow, M * qlo % 2^32, a_hi, a_lo, ...]
    50	    u32widening_madd swap
    51	    # => [t1, t0, M * qlo % 32, a_hi, a_lo, ...] where t = t1 * 2^32 + t0 and t = M * qhi + overflow
    52	    # Note by the bound on x - r = q * M, we are guaranteed that t1 = 0
    53	    assertz.err="comparison failed: quotient overflow"
    54	    # => [M * q / 2^32, (M * q) % 2^32, a_hi, a_lo, ...]
    55	    # => [res_hi, res_lo, a_hi, a_lo, ...]
    56	
    57	    adv_push.1
    58	    dup
    59	    push.M
    60	    u32overflowing_sub
    61	    # => [borrow, diff, r, res_hi, res_lo, a_hi, a_lo, ...]
    62	    # borrow = 1 when r < M (underflow). Assert borrow != 0, then drop diff.
    63	    assert.err="comparison failed: modulus" drop
    64	    # => [r, res_hi, res_lo, a_hi, a_lo, ...]
    65	
    66	    dup
    67	    movup.3
    68	    u32overflowing_add
    69	    # => [flag, (res_lo + r) % 2^32, r, res_hi, a_hi, a_lo, ...] where u = uhi * 2^32 + ulo and u = (res_lo + r) / 2^32
    70	
    71	    movup.3
    72	    u32overflowing_add
    73	    # => [flag, final_res_hi, final_res_lo, r, a_hi, a_lo, ...] flag should be 0 by the bound on inputs
    74	    assertz.err="comparison failed: addition overflow"
    75	    # => [final_res_hi, final_res_lo, r, a_hi, a_lo, ...]
    76	
    77	    movup.3
    78	    assert_eq
    79	    movup.2
    80	    assert_eq
    81	    # => [r, ...]
    82	end

The baseline analysis shows one warning per sink. On this file, it points to the real bad source, but it still asks the reader to join six warnings by hand.

set -euo pipefail
tmpdir=$(mktemp -d)
cleanup() {
  git -C /Users/huitseeker/tmp/masm-lsp worktree remove "$tmpdir" --force >/dev/null 2>&1 || true
}
trap cleanup EXIT

git -C /Users/huitseeker/tmp/masm-lsp worktree add --detach "$tmpdir" origin/main >/dev/null
cd "$tmpdir"
(cargo run -q -p masm-lint -- --no-color \
  --library miden::core=/Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm \
  /Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm \
  2>&1 | sed -n '1,220p') || true
warning: unconstrained advice reaches a u32 intrinsic
  --> /Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm:60:5
       |
    60 |     u32overflowing_sub
       |     ^^^^^^^^^^^^^^^^^^
    ::: /Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm:57:5
       |
    57 |     adv_push.1
       |     ^^^^^^^^^^
    = help: unconstrained advice introduced here
    = note: in procedure `miden::core::crypto::dsa::falcon512_poseidon2::mod_12289`

warning: unconstrained advice reaches a u32 intrinsic
  --> /Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm:68:5
       |
    68 |     u32overflowing_add
       |     ^^^^^^^^^^^^^^^^^^
    ::: /Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm:57:5
       |
    57 |     adv_push.1
       |     ^^^^^^^^^^
    = help: unconstrained advice introduced here
    = note: in procedure `miden::core::crypto::dsa::falcon512_poseidon2::mod_12289`

warning: argument 0 to `miden::core::crypto::dsa::falcon512_poseidon2::norm_sq` expects U32 and may contain unconstrained advice
  --> /Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm:494:9
        |
    494 |         exec.norm_sq
        |         ^^^^^^^^^^^^
    ::: /Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm:57:5
       |
    57 |     adv_push.1
       |     ^^^^^^^^^^
    = help: unconstrained advice introduced here
    = note: in procedure `miden::core::crypto::dsa::falcon512_poseidon2::compute_s1_norm_sq`

warning: argument 0 to `miden::core::crypto::dsa::falcon512_poseidon2::norm_sq` expects U32 and may contain unconstrained advice
  --> /Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm:511:9
        |
    511 |         exec.norm_sq
        |         ^^^^^^^^^^^^
    ::: /Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm:57:5
       |
    57 |     adv_push.1
       |     ^^^^^^^^^^
    = help: unconstrained advice introduced here
    = note: in procedure `miden::core::crypto::dsa::falcon512_poseidon2::compute_s1_norm_sq`

warning: argument 0 to `miden::core::crypto::dsa::falcon512_poseidon2::norm_sq` expects U32 and may contain unconstrained advice
  --> /Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm:527:9
        |
    527 |         exec.norm_sq
        |         ^^^^^^^^^^^^
    ::: /Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm:57:5
       |
    57 |     adv_push.1
       |     ^^^^^^^^^^
    = help: unconstrained advice introduced here
    = note: in procedure `miden::core::crypto::dsa::falcon512_poseidon2::compute_s1_norm_sq`

warning: argument 0 to `miden::core::crypto::dsa::falcon512_poseidon2::norm_sq` expects U32 and may contain unconstrained advice
  --> /Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm:541:9
        |
    541 |         exec.norm_sq
        |         ^^^^^^^^^^^^
    ::: /Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm:57:5
       |
    57 |     adv_push.1
       |     ^^^^^^^^^^
    = help: unconstrained advice introduced here
    = note: in procedure `miden::core::crypto::dsa::falcon512_poseidon2::compute_s1_norm_sq`

warning: masm-lint generated 6 warning(s)

The bigger baseline stdlib run shows the noise more clearly: 18 total warnings, 13 advice warnings, and only 3 distinct advice origins.

set -euo pipefail
tmpdir=$(mktemp -d)
out=$(mktemp)
cleanup() {
  rm -f "$out"
  git -C /Users/huitseeker/tmp/masm-lsp worktree remove "$tmpdir" --force >/dev/null 2>&1 || true
}
trap cleanup EXIT

git -C /Users/huitseeker/tmp/masm-lsp worktree add --detach "$tmpdir" origin/main >/dev/null
cd "$tmpdir"
(cargo run -q -p masm-lint -- --no-color \
  --library miden::core=/Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm \
  /Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm >"$out" 2>&1) || true
python3 - "$out" <<'PY'
import re, sys
from collections import Counter
path = sys.argv[1]
lines = open(path).read().splitlines()
messages = Counter()
origins = Counter()
for i, line in enumerate(lines):
    if line.startswith('warning: '):
        msg = line[len('warning: '):]
        if msg.startswith('masm-lint generated '):
            continue
        messages[msg] += 1
    if line.startswith('    = help: unconstrained advice introduced here'):
        for j in range(max(0, i - 6), i):
            m = re.search(r':::\s+(.+):(\d+):(\d+)$', lines[j])
            if m:
                origins[f"{m.group(1)}:{m.group(2)}:{m.group(3)}"] += 1
                break
advice_count = sum(count for msg, count in messages.items() if msg.startswith('unconstrained advice') or ('expects U32' in msg))
print(f"warning_count {sum(messages.values())}")
print("by_message")
for msg, count in messages.most_common():
    if msg.startswith('unconstrained advice') or ('expects U32' in msg):
        print(f"{count}  {msg}")
print()
print(f"advice_warning_count {advice_count}")
print("unique_advice_origins")
for origin, count in origins.most_common():
    print(f"{count}  {origin}")
PY
warning_count 18
by_message
4  argument 0 to `miden::core::crypto::dsa::falcon512_poseidon2::norm_sq` expects U32 and may contain unconstrained advice
3  unconstrained advice reaches a u32 intrinsic
3  unconstrained advice reaches a u32 operation
3  unconstrained advice used as memory address

advice_warning_count 13
unique_advice_origins
6  /Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm:57:5
6  /Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/pcs/fri/frie2f4.masm:15:5
1  /Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/stark/random_coin.masm:217:5

Abstract interpretation gives the tool a small model for each value instead of the full value. Here the model is simple: a value is either Unknown or ProvenU32. The loop is simple too: start from the current abstract state, apply one transfer step, join the new facts back in, and repeat until nothing changes. That keeps the tool correct because unchecked advice still stays suspicious, and complete for this property because a value that was proved to be u32 can stay proved through locals, branches, loops, and calls.

nl -ba crates/masm-analysis/src/abstract_interp/mod.rs | sed -n '108,142p' && printf '\n---\n' && nl -ba crates/masm-analysis/src/unconstrained_advice/u32_domain.rs | sed -n '1,26p' && printf '\n---\n' && nl -ba crates/masm-analysis/src/unconstrained_advice/u32.rs | sed -n '313,418p'
   108	/// This is the didactic core of the abstract-interpretation loop used for loops and other cyclic
   109	/// control-flow regions:
   110	/// 1. Start from the current abstract state.
   111	/// 2. Apply a transfer function to produce a candidate next state.
   112	/// 3. Join the candidate into the current state.
   113	/// 4. Repeat until the join stops changing the state or a hard cutoff is reached.
   114	pub fn iterate_to_fixpoint<S, F>(
   115	    initial_state: S,
   116	    config: FixpointConfig,
   117	    mut step: F,
   118	) -> FixpointResult<S>
   119	where
   120	    S: JoinSemiLattice,
   121	    F: FnMut(&S) -> S,
   122	{
   123	    let mut state = initial_state;
   124	    let mut steps = 0;
   125	    let mut saw_change = false;
   126	
   127	    for _iteration in 0..config.max_iterations {
   128	        let candidate = step(&state);
   129	        steps += 1;
   130	        saw_change = state.join_assign(&candidate);
   131	        if !saw_change {
   132	            return FixpointResult::new(state, steps, FixpointOutcome::Converged);
   133	        }
   134	    }
   135	
   136	    let outcome = if saw_change {
   137	        FixpointOutcome::ReachedIterationLimitAfterChange
   138	    } else {
   139	        FixpointOutcome::ReachedIterationLimit
   140	    };
   141	
   142	    FixpointResult::new(state, steps, outcome)

---
     1	//! Sanitization-based abstract domain for intraprocedural `u32` validity.
     2	
     3	/// Whether a value is known to satisfy the MASM `u32` precondition.
     4	#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
     5	pub(crate) enum U32Validity {
     6	    /// No proof is available that the value is a valid `u32`.
     7	    #[default]
     8	    Unknown,
     9	    /// The value has been validated or produced by a trusted `u32` operation.
    10	    ProvenU32,
    11	}
    12	
    13	impl U32Validity {
    14	    /// Join two `u32` validity facts conservatively.
    15	    pub(crate) fn join(self, other: Self) -> Self {
    16	        match (self, other) {
    17	            (Self::ProvenU32, Self::ProvenU32) => Self::ProvenU32,
    18	            _ => Self::Unknown,
    19	        }
    20	    }
    21	
    22	    /// Return `true` when the value is proven to be a valid `u32`.
    23	    pub(crate) const fn is_proven(self) -> bool {
    24	        matches!(self, Self::ProvenU32)
    25	    }
    26	}

---
   313	    /// Emit diagnostics for call arguments whose callee expects `U32`.
   314	    fn call_diagnostics(
   315	        &self,
   316	        span: miden_debug_types::SourceSpan,
   317	        target: &str,
   318	        args: &[masm_decompiler::ir::Var],
   319	        env: &Env,
   320	    ) -> Vec<AdviceDiagnostic> {
   321	        let Some(summary) = self
   322	            .type_summaries
   323	            .get(&SymbolPath::new(target.to_string()))
   324	        else {
   325	            return Vec::new();
   326	        };
   327	        let mut diagnostics = Vec::new();
   328	        for (index, (arg, expected)) in args.iter().zip(summary.inputs.iter()).enumerate() {
   329	            let arg_fact = env.fact_for_var(arg);
   330	            if *expected != TypeRequirement::U32
   331	                || !arg_fact.has_concrete_sources()
   332	                || env.u32_validity_for_var(arg).is_proven()
   333	            {
   334	                continue;
   335	            }
   336	            let callee = SymbolPath::new(target.to_string());
   337	            let mut diagnostic = self.new_diagnostic(
   338	                span,
   339	                AdviceSinkKind::CallArgument,
   340	                format!(
   341	                    "argument {index} to `{callee}` expects U32 and may contain unconstrained advice"
   342	                ),
   343	                &arg_fact,
   344	            );
   345	            diagnostic.callee = Some(callee);
   346	            diagnostic.arg_index = Some(index);
   347	            diagnostic.call_requirement = Some(CallArgumentRequirement::U32);
   348	            diagnostics.push(diagnostic);
   349	        }
   350	        diagnostics
   351	    }
   352	}

The current default CLI stays flat for compatibility. On the Falcon file, the flat view still shows six sink warnings, each with the same origin at line 57.

cargo run -q -p masm-lint -- --no-color --library miden::core=/Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm /Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm 2>&1 | sed -n '1,220p'
warning: unconstrained advice reaches a u32 intrinsic
  --> /Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm:60:5
       |
    60 |     u32overflowing_sub
       |     ^^^^^^^^^^^^^^^^^^
    ::: /Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm:57:5
       |
    57 |     adv_push.1
       |     ^^^^^^^^^^
    = help: unconstrained advice introduced here
    = note: in procedure `miden::core::crypto::dsa::falcon512_poseidon2::mod_12289`

warning: unconstrained advice reaches a u32 intrinsic
  --> /Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm:68:5
       |
    68 |     u32overflowing_add
       |     ^^^^^^^^^^^^^^^^^^
    ::: /Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm:57:5
       |
    57 |     adv_push.1
       |     ^^^^^^^^^^
    = help: unconstrained advice introduced here
    = note: in procedure `miden::core::crypto::dsa::falcon512_poseidon2::mod_12289`

warning: argument 0 to `miden::core::crypto::dsa::falcon512_poseidon2::norm_sq` expects U32 and may contain unconstrained advice
  --> /Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm:494:9
        |
    494 |         exec.norm_sq
        |         ^^^^^^^^^^^^
    ::: /Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm:57:5
       |
    57 |     adv_push.1
       |     ^^^^^^^^^^
    = help: unconstrained advice introduced here
    = note: in procedure `miden::core::crypto::dsa::falcon512_poseidon2::compute_s1_norm_sq`

warning: argument 0 to `miden::core::crypto::dsa::falcon512_poseidon2::norm_sq` expects U32 and may contain unconstrained advice
  --> /Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm:511:9
        |
    511 |         exec.norm_sq
        |         ^^^^^^^^^^^^
    ::: /Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm:57:5
       |
    57 |     adv_push.1
       |     ^^^^^^^^^^
    = help: unconstrained advice introduced here
    = note: in procedure `miden::core::crypto::dsa::falcon512_poseidon2::compute_s1_norm_sq`

warning: argument 0 to `miden::core::crypto::dsa::falcon512_poseidon2::norm_sq` expects U32 and may contain unconstrained advice
  --> /Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm:527:9
        |
    527 |         exec.norm_sq
        |         ^^^^^^^^^^^^
    ::: /Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm:57:5
       |
    57 |     adv_push.1
       |     ^^^^^^^^^^
    = help: unconstrained advice introduced here
    = note: in procedure `miden::core::crypto::dsa::falcon512_poseidon2::compute_s1_norm_sq`

warning: argument 0 to `miden::core::crypto::dsa::falcon512_poseidon2::norm_sq` expects U32 and may contain unconstrained advice
  --> /Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm:541:9
        |
    541 |         exec.norm_sq
        |         ^^^^^^^^^^^^
    ::: /Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm:57:5
       |
    57 |     adv_push.1
       |     ^^^^^^^^^^
    = help: unconstrained advice introduced here
    = note: in procedure `miden::core::crypto::dsa::falcon512_poseidon2::compute_s1_norm_sq`

warning: masm-lint generated 6 warning(s)

The new grouped mode is optional. It keeps the same finding, but it centers the view on the bad advice source and lists the six downstream sinks underneath it.

cargo run -q -p masm-lint -- --no-color --group-by-origin --library miden::core=/Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm /Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm 2>&1 | sed -n '1,220p'
warning: unconstrained advice introduced here reaches 6 downstream sink(s)
  --> /Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm:57:5
       |
    57 |     adv_push.1
       |     ^^^^^^^^^^
    ::: /Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm:494:9
        |
    494 |         exec.norm_sq
        |         ^^^^^^^^^^^^
    = help: argument 0 to `miden::core::crypto::dsa::falcon512_poseidon2::norm_sq` expects U32 and may contain unconstrained advice (in procedure `miden::core::crypto::dsa::falcon512_poseidon2::compute_s1_norm_sq`)
    ::: /Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm:511:9
        |
    511 |         exec.norm_sq
        |         ^^^^^^^^^^^^
    = help: argument 0 to `miden::core::crypto::dsa::falcon512_poseidon2::norm_sq` expects U32 and may contain unconstrained advice (in procedure `miden::core::crypto::dsa::falcon512_poseidon2::compute_s1_norm_sq`)
    ::: /Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm:527:9
        |
    527 |         exec.norm_sq
        |         ^^^^^^^^^^^^
    = help: argument 0 to `miden::core::crypto::dsa::falcon512_poseidon2::norm_sq` expects U32 and may contain unconstrained advice (in procedure `miden::core::crypto::dsa::falcon512_poseidon2::compute_s1_norm_sq`)
    ::: /Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm:541:9
        |
    541 |         exec.norm_sq
        |         ^^^^^^^^^^^^
    = help: argument 0 to `miden::core::crypto::dsa::falcon512_poseidon2::norm_sq` expects U32 and may contain unconstrained advice (in procedure `miden::core::crypto::dsa::falcon512_poseidon2::compute_s1_norm_sq`)
    ::: /Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm:60:5
       |
    60 |     u32overflowing_sub
       |     ^^^^^^^^^^^^^^^^^^
    = help: unconstrained advice reaches a u32 intrinsic (in procedure `miden::core::crypto::dsa::falcon512_poseidon2::mod_12289`)
    ::: /Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm:68:5
       |
    68 |     u32overflowing_add
       |     ^^^^^^^^^^^^^^^^^^
    = help: unconstrained advice reaches a u32 intrinsic (in procedure `miden::core::crypto::dsa::falcon512_poseidon2::mod_12289`)
    = note: root cause fan-out reaches procedures `miden::core::crypto::dsa::falcon512_poseidon2::compute_s1_norm_sq` and `miden::core::crypto::dsa::falcon512_poseidon2::mod_12289`

warning: masm-lint generated 1 warning(s)

The grouped UI is also exposed through an LSP command. The editor can keep normal flat diagnostics, and only ask for grouped root causes when the user wants that view.

nl -ba src/server/lsp.rs | sed -n '35,125p' && printf '\n---\n' && nl -ba src/server/lsp.rs | sed -n '775,915p'
    35	};
    36	
    37	const CMD_SET_CORE_PATH: &str = "masm-lsp.setCorePath";
    38	const CMD_DECOMPILE_PROCEDURE_AT_CURSOR: &str = "masm-lsp.decompileProcedureAtCursor";
    39	const CMD_DECOMPILE_FILE: &str = "masm-lsp.decompileFile";
    40	const CMD_GROUP_ADVICE_DIAGNOSTICS_BY_ORIGIN: &str = "masm-lsp.groupAdviceDiagnosticsByOrigin";
    41	
    42	#[tower_lsp::async_trait]
    43	impl<C> LanguageServer for Backend<C>
    44	where
    45	    C: PublishDiagnostics,
    46	{
    47	    async fn initialize(&self, params: InitializeParams) -> Result<InitializeResult> {
    48	        let workspace_paths = workspace_parse_paths_from_initialize(&params);
    49	        self.set_workspace_parse_paths(workspace_paths).await;
    50	
    51	        let capabilities = ServerCapabilities {
    52	            text_document_sync: Some(TextDocumentSyncCapability::Kind(
    53	                TextDocumentSyncKind::INCREMENTAL,
    54	            )),
    55	            hover_provider: Some(tower_lsp::lsp_types::HoverProviderCapability::Simple(true)),
    56	            definition_provider: Some(tower_lsp::lsp_types::OneOf::Left(true)),
    57	            implementation_provider: Some(
    58	                tower_lsp::lsp_types::ImplementationProviderCapability::Simple(true),
    59	            ),
    60	            references_provider: Some(tower_lsp::lsp_types::OneOf::Left(true)),
    61	            workspace_symbol_provider: Some(tower_lsp::lsp_types::OneOf::Left(true)),
    62	            inlay_hint_provider: Some(tower_lsp::lsp_types::OneOf::Left(true)),
    63	            rename_provider: Some(tower_lsp::lsp_types::OneOf::Right(RenameOptions {
    64	                prepare_provider: Some(true),
    65	                work_done_progress_options: Default::default(),
    66	            })),
    67	            document_symbol_provider: Some(tower_lsp::lsp_types::OneOf::Left(true)),
    68	            code_lens_provider: Some(CodeLensOptions {
    69	                resolve_provider: Some(false),
    70	            }),
    71	            execute_command_provider: Some(ExecuteCommandOptions {
    72	                commands: vec![
    73	                    CMD_SET_CORE_PATH.to_string(),
    74	                    CMD_DECOMPILE_PROCEDURE_AT_CURSOR.to_string(),
    75	                    CMD_DECOMPILE_FILE.to_string(),
    76	                    CMD_GROUP_ADVICE_DIAGNOSTICS_BY_ORIGIN.to_string(),
    77	                ],
    78	                work_done_progress_options: Default::default(),
    79	            }),
    80	            ..Default::default()
    81	        };

---
   778	    async fn execute_group_advice_diagnostics_by_origin(
   779	        &self,
   780	        params: &ExecuteCommandParams,
   781	    ) -> Result<Option<serde_json::Value>> {
   782	        let Some(focus_uri) = parse_group_advice_argument(&params.arguments) else {
   783	            return Err(tower_lsp::jsonrpc::Error::invalid_params(
   784	                "masm-lsp.groupAdviceDiagnosticsByOrigin expects zero arguments or a single argument object: {\"uri\": \"file:///...\"}",
   785	            ));
   786	        };
   787	
   788	        let config = self.snapshot_config().await;
   789	        let effective_library_paths = self
   790	            .effective_library_paths_from(&config.library_paths)
   791	            .await;
   792	        let workspace = self
   793	            .build_analysis_workspace(focus_uri.as_ref(), &effective_library_paths)
   794	            .await;
   795	        let analysis = masm_analysis::AnalysisSnapshot::from_workspace(&workspace);
   796	        let focus_source_id = focus_uri
   797	            .as_ref()
   798	            .and_then(|uri| self.sources.find(&to_miden_uri(uri)));
   799	
   800	        let groups = masm_analysis::group_advice_diagnostics_by_origin(&analysis.advice_diagnostics)
   801	            .into_iter()
   802	            .filter(|group| {
   803	                focus_source_id.is_none_or(|source_id| {
   804	                    group.origin.source_id() == source_id
   805	                        || group
   806	                            .diagnostics
   807	                            .iter()
   808	                            .any(|diag| diag.span.source_id() == source_id)
   809	                })
   810	            })
   811	            .filter_map(|group| {
   812	                let origin = span_location_value(self.sources.as_ref(), group.origin)?;
   813	                let sinks = group
   814	                    .diagnostics
   815	                    .iter()
   816	                    .filter_map(|diag| {
   817	                        Some(serde_json::json!({
   818	                            "location": span_location_value(self.sources.as_ref(), diag.span)?,
   819	                            "procedure": diag.procedure.as_str(),
   820	                            "message": normalize_message(&diag.message),
   821	                        }))
   822	                    })
   823	                    .collect::<Vec<_>>();
   824	                let procedures = grouped_procedures(&group.diagnostics);
   825	                Some(serde_json::json!({
   826	                    "origin": origin,
   827	                    "message": normalize_message(&group.summary_message()),
   828	                    "sinkCount": sinks.len(),
   829	                    "procedures": procedures,
   830	                    "sinks": sinks,
   831	                }))
   832	            })
   833	            .collect::<Vec<_>>();

On the full stdlib scan, the new analysis keeps the known mod_12289 finding and gets quieter. The Phase 2 report shows 10 u32 diagnostics from the same 3 origins. The old memory-address fan-out is gone.

MASM_PHASE2_STDLIB_ROOT=/Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm cargo test -q -p masm-analysis phase2_stdlib_u32_noise_report -- --ignored --nocapture
running 1 test
one or more warnings were emitted
one or more warnings were emitted
one or more warnings were emitted
one or more warnings were emitted
one or more warnings were emitted
{
  "known_findings": {
    "mod_12289": {
      "diagnostics": [
        {
          "message": "unconstrained advice reaches a u32 intrinsic",
          "procedure": "miden::core::crypto::dsa::falcon512_poseidon2::mod_12289",
          "sink": "u32_intrinsic"
        },
        {
          "message": "unconstrained advice reaches a u32 intrinsic",
          "procedure": "miden::core::crypto::dsa::falcon512_poseidon2::mod_12289",
          "sink": "u32_intrinsic"
        }
      ],
      "reproduced": true
    }
  },
  "stdlib_root": "/Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm",
  "u32_diagnostics": {
    "distinct_origins": 3,
    "fanout_ratio": 3.3333333333333335,
    "max_origin_fanout": 6,
    "sink_counts": {
      "call_argument": 4,
      "u32_expression": 3,
      "u32_intrinsic": 3
    },
    "top_origins": [
      {
        "count": 6,
        "origin": {
          "column": 5,
          "file": "/Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm",
          "line": 57
        }
      },
      {
        "count": 3,
        "origin": {
          "column": 5,
          "file": "/Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/pcs/fri/frie2f4.masm",
          "line": 15
        }
      },
      {
        "count": 1,
        "origin": {
          "column": 5,
          "file": "/Users/huitseeker/tmp/miden-vm-next/crates/lib/core/asm/stark/random_coin.masm",
          "line": 217
        }
      }
    ],
    "top_procedures": [
      {
        "count": 4,
        "procedure": "miden::core::crypto::dsa::falcon512_poseidon2::compute_s1_norm_sq"
      },
      {
        "count": 3,
        "procedure": "miden::core::pcs::fri::frie2f4::preprocess"
      },
      {
        "count": 2,
        "procedure": "miden::core::crypto::dsa::falcon512_poseidon2::mod_12289"
      },
      {
        "count": 1,
        "procedure": "miden::core::stark::random_coin::init_seed"
      }
    ],
    "total": 10
  },
  "unresolved_modules": []
}
.
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 61 filtered out; finished in 4.07s
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment