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') || truewarning: 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}")
PYwarning_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(¶ms);
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(¶ms.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 --nocapturerunning 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