Skip to content

Commit 6368cf5

Browse files
authored
Improved show printing for leaves (#946)
### Short Version This PR: - Threads `Span`s through to `#setUpCalleeData` instead of being ignored from the `Terminator` - Modifies the python utility code that prints leaves for `kmir show --leaves` to print function, call site, and error messages when possible for `panic` and `assert_failed` - Adds the ability to truncate a path for testing so CI is deterministic ### Long Version In the event that a `panic` or `assert_failed` is reached in a proof, a stuck leaf node will be present in the KCFG that calls `** UNKNOWN FUNCTION **`. It is not clear exactly which function is being called, where this is being called from, or what the error message is (if there is one) as all the information is represented as interned data (`DefId`, `Span`, `AllocId`). The print out of the failing nodes is improved for `kmir show --leaves` from: ``` Node 3: #setUpCalleeData ( monoItemFn ( ... name: symbol ( "** UNKNOWN FUNCTION **" ) , id: defId ( 38 ) , body: noBody ) , operandConstant ( constOperand ( ... span: span ( 32 ) , userTy: noUserTypeAnnotationIndex , const: mirConst ( ... kind: constantKindAllocated ( allocation ( ... bytes: b"\x00\x00\x00\x00\x00\x00\x00\x00\x17\x00\x00\x00\x00\x00\x00\x00" , provenance: provenanceMap ( ... ptrs: provenanceMapEntry ( ... offset: 0 , allocId: allocId ( 1 ) ) .ProvenanceMapEntries ) , align: align ( 8 ) , mutability: mutabilityMut ) ) , ty: ty ( 39 ) , id: mirConstId ( 25 ) ) ) ) .Operands) ~> .K ``` to: ``` Node 3: #setUpCalleeData ( monoItemFn ( ... name: symbol ( "** UNKNOWN FUNCTION **" ) , id: defId ( 38 ) , body: noBody ) , operandConstant ( constOperand ( ... span: span ( 32 ) , userTy: noUserTypeAnnotationIndex , const: mirConst ( ... kind: constantKindAllocated ( allocation ( ... bytes: b"\x00\x00\x00\x00\x00\x00\x00\x00\x17\x00\x00\x00\x00\x00\x00\x00" , provenance: provenanceMap ( ... ptrs: provenanceMapEntry ( ... offset: 0 , allocId: allocId ( 1 ) ) .ProvenanceMapEntries ) , align: align ( 8 ) , mutability: mutabilityMut ) ) , ty: ty ( 39 ) , id: mirConstId ( 25 ) ) ) ) .Operands , span ( 117 ) ) ~> .K >> function: core::panicking::panic::h941160ead03e2d54 >> call span: /home/daniel/Applications/mir-semantics/kmir/src/tests/integration/data/prove-rs/symbolic-args-fail.rs:53:5 >> message: 'assertion failed: false' ``` This is achieved by taking advantage of the deterministic location of the interned values, retrieving those values, and then consulting the `.smir.json` to get the un-interned data associated with those interned values.
1 parent 59ba85c commit 6368cf5

7 files changed

Lines changed: 173 additions & 31 deletions

File tree

kmir/src/kmir/__main__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ def _kmir_show(opts: ShowOpts) -> None:
180180
if opts.leaves:
181181
if lines and lines[-1] != '':
182182
lines.append('')
183-
lines.extend(render_leaf_k_cells(proof, node_printer.cterm_show))
183+
lines.extend(render_leaf_k_cells(proof, node_printer.cterm_show, smir_info=node_printer.smir_info))
184184

185185
# Handle --to-module output
186186
if opts.to_module:

kmir/src/kmir/kdist/mir-semantics/intrinsics.md

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ its argument to the destination without modification.
2323

2424
```k
2525
// Black box intrinsic implementation - identity function
26-
rule <k> #execIntrinsic(IntrinsicFunction(symbol("black_box")), ARG:Operand .Operands, DEST)
26+
rule <k> #execIntrinsic(IntrinsicFunction(symbol("black_box")), ARG:Operand .Operands, DEST, _SPAN)
2727
=> #setLocalValue(DEST, ARG)
2828
... </k>
2929
```
@@ -36,7 +36,7 @@ a NO OP for program semantics. `std::intrinsics::likely` and `std::intrinsics::u
3636
"normal" `MonoItemFn`s that call the `cold_path` intrinsic.
3737

3838
```k
39-
rule <k> #execIntrinsic(IntrinsicFunction(symbol("cold_path")), .Operands, _DEST) => .K ... </k>
39+
rule <k> #execIntrinsic(IntrinsicFunction(symbol("cold_path")), .Operands, _DEST, _SPAN) => .K ... </k>
4040
```
4141

4242
#### Prefetch (`std::intrinsics::prefetch_*`)
@@ -46,11 +46,11 @@ intrinsics in Rust are performance hints that request the CPU to load or prepare
4646
before it's used. They have no effect on program semantics, and are implemented as a NO OP in this semantics.
4747

4848
```k
49-
rule <k> #execIntrinsic(IntrinsicFunction(symbol("prefetch_read_data")), _ARG1:Operand _ARG2:Operand .Operands, _DEST) => .K ... </k>
50-
rule <k> #execIntrinsic(IntrinsicFunction(symbol("prefetch_write_data")), _ARG1:Operand _ARG2:Operand .Operands, _DEST) => .K ... </k>
49+
rule <k> #execIntrinsic(IntrinsicFunction(symbol("prefetch_read_data")), _ARG1:Operand _ARG2:Operand .Operands, _DEST, _SPAN) => .K ... </k>
50+
rule <k> #execIntrinsic(IntrinsicFunction(symbol("prefetch_write_data")), _ARG1:Operand _ARG2:Operand .Operands, _DEST, _SPAN) => .K ... </k>
5151
52-
rule <k> #execIntrinsic(IntrinsicFunction(symbol("prefetch_read_instruction")), _ARG1:Operand _ARG2:Operand .Operands, _DEST) => .K ... </k>
53-
rule <k> #execIntrinsic(IntrinsicFunction(symbol("prefetch_write_instruction")), _ARG1:Operand _ARG2:Operand .Operands, _DEST) => .K ... </k>
52+
rule <k> #execIntrinsic(IntrinsicFunction(symbol("prefetch_read_instruction")), _ARG1:Operand _ARG2:Operand .Operands, _DEST, _SPAN) => .K ... </k>
53+
rule <k> #execIntrinsic(IntrinsicFunction(symbol("prefetch_write_instruction")), _ARG1:Operand _ARG2:Operand .Operands, _DEST, _SPAN) => .K ... </k>
5454
```
5555

5656
#### Assert Inhabited (`std::intrinsics::assert_inhabited`)
@@ -63,13 +63,13 @@ error with `#AssertInhabitedFailure` if we see that following the intrinsic. Oth
6363

6464
```k
6565
syntax MIRError ::= "AssertInhabitedFailure"
66-
rule <k> #execIntrinsic(IntrinsicFunction(symbol("assert_inhabited")), .Operands, _DEST)
66+
rule <k> #execIntrinsic(IntrinsicFunction(symbol("assert_inhabited")), .Operands, _DEST, _SPAN)
6767
~> #continueAt(noBasicBlockIdx)
6868
=> AssertInhabitedFailure
6969
...
7070
</k>
7171
72-
rule <k> #execIntrinsic(IntrinsicFunction(symbol("assert_inhabited")), .Operands, _DEST)
72+
rule <k> #execIntrinsic(IntrinsicFunction(symbol("assert_inhabited")), .Operands, _DEST, _SPAN)
7373
=> .K
7474
...
7575
</k>
@@ -88,7 +88,7 @@ Execution gets stuck (no matching rule) when operands have different types or un
8888

8989
```k
9090
// Raw eq: dereference operands, extract types, and delegate to typed comparison
91-
rule <k> #execIntrinsic(IntrinsicFunction(symbol("raw_eq")), ARG1:Operand ARG2:Operand .Operands, PLACE)
91+
rule <k> #execIntrinsic(IntrinsicFunction(symbol("raw_eq")), ARG1:Operand ARG2:Operand .Operands, PLACE, _SPAN)
9292
=> #execRawEqTyped(PLACE, #withDeref(ARG1), #extractOperandType(#withDeref(ARG1), LOCALS),
9393
#withDeref(ARG2), #extractOperandType(#withDeref(ARG2), LOCALS))
9494
... </k>
@@ -134,11 +134,11 @@ through a dereferenced pointer. We extract the place from the pointer operand, a
134134
write the value to that location.
135135

136136
```k
137-
rule <k> #execIntrinsic(IntrinsicFunction(symbol("volatile_store")), operandCopy(place(LOCAL, PROJ)) ARG2:Operand .Operands, _DEST)
137+
rule <k> #execIntrinsic(IntrinsicFunction(symbol("volatile_store")), operandCopy(place(LOCAL, PROJ)) ARG2:Operand .Operands, _DEST, _SPAN)
138138
=> #setLocalValue(place(LOCAL, appendP(PROJ, projectionElemDeref .ProjectionElems)), ARG2)
139139
... </k>
140140
141-
rule <k> #execIntrinsic(IntrinsicFunction(symbol("volatile_store")), operandMove(place(LOCAL, PROJ)) ARG2:Operand .Operands, _DEST)
141+
rule <k> #execIntrinsic(IntrinsicFunction(symbol("volatile_store")), operandMove(place(LOCAL, PROJ)) ARG2:Operand .Operands, _DEST, _SPAN)
142142
=> #setLocalValue(place(LOCAL, appendP(PROJ, projectionElemDeref .ProjectionElems)), ARG2)
143143
... </k>
144144
```
@@ -153,12 +153,12 @@ the second argument, so the returned difference is always positive.
153153

154154

155155
```k
156-
rule <k> #execIntrinsic(IntrinsicFunction(symbol("ptr_offset_from")), ARG1:Operand ARG2:Operand .Operands, DEST)
156+
rule <k> #execIntrinsic(IntrinsicFunction(symbol("ptr_offset_from")), ARG1:Operand ARG2:Operand .Operands, DEST, _SPAN)
157157
=> #ptrOffsetDiff(ARG1, ARG2, true, DEST)
158158
...
159159
</k>
160160
161-
rule <k> #execIntrinsic(IntrinsicFunction(symbol("ptr_offset_from_unsigned")), ARG1:Operand ARG2:Operand .Operands, DEST)
161+
rule <k> #execIntrinsic(IntrinsicFunction(symbol("ptr_offset_from_unsigned")), ARG1:Operand ARG2:Operand .Operands, DEST, _SPAN)
162162
=> #ptrOffsetDiff(ARG1, ARG2, false, DEST)
163163
...
164164
</k>

kmir/src/kmir/kdist/mir-semantics/kmir.md

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -306,30 +306,30 @@ The call stack is not necessarily empty at this point so it is left untouched.
306306
where the returned result should go.
307307

308308
```k
309-
syntax KItem ::= #execTerminatorCall(fty: Ty, func: MonoItemKind, args: Operands, destination: Place, target: MaybeBasicBlockIdx, unwind: UnwindAction)
309+
syntax KItem ::= #execTerminatorCall(fty: Ty, func: MonoItemKind, args: Operands, destination: Place, target: MaybeBasicBlockIdx, unwind: UnwindAction, Span)
310310
311-
rule <k> #execTerminator(terminator(terminatorKindCall(operandConstant(constOperand(_, _, mirConst(constantKindZeroSized, Ty, _))), ARGS, DEST, TARGET, UNWIND), _SPAN))
312-
=> #execTerminatorCall(Ty, lookupFunction(Ty), ARGS, DEST, TARGET, UNWIND)
311+
rule <k> #execTerminator(terminator(terminatorKindCall(operandConstant(constOperand(_, _, mirConst(constantKindZeroSized, Ty, _))), ARGS, DEST, TARGET, UNWIND), SPAN))
312+
=> #execTerminatorCall(Ty, lookupFunction(Ty), ARGS, DEST, TARGET, UNWIND, SPAN)
313313
...
314314
</k>
315315
316-
rule <k> #execTerminator(terminator(terminatorKindCall(operandMove(place(local(I), .ProjectionElems)), ARGS, DEST, TARGET, UNWIND), _SPAN))
317-
=> #execTerminatorCall(tyOfLocal(getLocal(LOCALS, I)), lookupFunction(tyOfLocal(getLocal(LOCALS, I))), ARGS, DEST, TARGET, UNWIND)
316+
rule <k> #execTerminator(terminator(terminatorKindCall(operandMove(place(local(I), .ProjectionElems)), ARGS, DEST, TARGET, UNWIND), SPAN))
317+
=> #execTerminatorCall(tyOfLocal(getLocal(LOCALS, I)), lookupFunction(tyOfLocal(getLocal(LOCALS, I))), ARGS, DEST, TARGET, UNWIND, SPAN)
318318
...
319319
</k>
320320
<locals> LOCALS </locals>
321321
322322
// Intrinsic function call - execute directly without state switching
323323
rule [termCallIntrinsic]:
324-
<k> #execTerminatorCall(_, FUNC, ARGS, DEST, TARGET, _UNWIND) ~> _
325-
=> #execIntrinsic(FUNC, ARGS, DEST) ~> #continueAt(TARGET)
324+
<k> #execTerminatorCall(_, FUNC, ARGS, DEST, TARGET, _UNWIND, SPAN) ~> _
325+
=> #execIntrinsic(FUNC, ARGS, DEST, SPAN) ~> #continueAt(TARGET)
326326
</k>
327327
requires isIntrinsicFunction(FUNC)
328328
329329
// Regular function call - full state switching and stack setup
330330
rule [termCallFunction]:
331-
<k> #execTerminatorCall(FTY, FUNC, ARGS, DEST, TARGET, UNWIND) ~> _
332-
=> #setUpCalleeData(FUNC, ARGS)
331+
<k> #execTerminatorCall(FTY, FUNC, ARGS, DEST, TARGET, UNWIND, SPAN) ~> _
332+
=> #setUpCalleeData(FUNC, ARGS, SPAN)
333333
</k>
334334
<currentFunc> CALLER => FTY </currentFunc>
335335
<currentFrame>
@@ -357,12 +357,13 @@ The local data has to be set up for the call, which requires information about t
357357
An operand may be a `Reference` (the only way a function could access another function call's `local` variables). For this case, the stack height in the `Reference` must be incremented because a stack frame is added.
358358

359359
```k
360-
syntax KItem ::= #setUpCalleeData(MonoItemKind, Operands)
360+
syntax KItem ::= #setUpCalleeData(MonoItemKind, Operands, Span)
361361
362362
// reserve space for local variables and copy/move arguments from old locals into their place
363363
rule [setupCalleeData]: <k> #setUpCalleeData(
364364
monoItemFn(_, _, someBody(body((FIRST:BasicBlock _) #as BLOCKS, NEWLOCALS, _, _, _, _))),
365-
ARGS
365+
ARGS,
366+
_SPAN
366367
)
367368
=>
368369
#setArgsFromStack(1, ARGS) ~> #execBlock(FIRST)
@@ -391,7 +392,7 @@ An operand may be a `Reference` (the only way a function could access another fu
391392
392393
syntax KItem ::= #setArgsFromStack ( Int, Operands)
393394
| #setArgFromStack ( Int, Operand)
394-
| #execIntrinsic ( MonoItemKind, Operands, Place )
395+
| #execIntrinsic ( MonoItemKind, Operands, Place, Span )
395396
396397
// once all arguments have been retrieved, execute
397398
rule <k> #setArgsFromStack(_, .Operands) ~> CONT => CONT </k>
@@ -453,7 +454,8 @@ Therefore a heuristics is used here:
453454
monoItemFn(_, _, someBody(body((FIRST:BasicBlock _) #as BLOCKS, NEWLOCALS, _, _, _, _))),
454455
operandMove(place(local(CLOSURE:Int), .ProjectionElems))
455456
operandMove(place(local(TUPLE), .ProjectionElems))
456-
.Operands
457+
.Operands,
458+
_SPAN
457459
)
458460
=>
459461
#setTupleArgs(2, getValue(LOCALS, TUPLE)) ~> #execBlock(FIRST)
@@ -482,7 +484,8 @@ Therefore a heuristics is used here:
482484
monoItemFn(_, _, someBody(body((FIRST:BasicBlock _) #as BLOCKS, NEWLOCALS, _, _, _, _))),
483485
operandMove(place(local(CLOSURE:Int), .ProjectionElems))
484486
operandMove(place(local(TUPLE), .ProjectionElems))
485-
.Operands
487+
.Operands,
488+
_SPAN
486489
)
487490
=>
488491
#setTupleArgs(2, getValue(LOCALS, TUPLE)) ~> #execBlock(FIRST)

kmir/src/kmir/testing/fixtures.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@ def pytest_configure(config) -> None:
2020
sys.setrecursionlimit(1000000)
2121

2222

23-
def assert_or_update_show_output(actual_text: str, expected_file: Path, *, update: bool) -> None:
23+
def assert_or_update_show_output(
24+
actual_text: str, expected_file: Path, *, update: bool, path_replacements: dict[str, str] | None = None
25+
) -> None:
26+
if path_replacements:
27+
for old, new in path_replacements.items():
28+
actual_text = actual_text.replace(old, new)
2429
if update:
2530
expected_file.write_text(actual_text)
2631
else:

kmir/src/kmir/utils.py

Lines changed: 127 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,16 @@
44
from pathlib import Path
55
from typing import TYPE_CHECKING, Sequence
66

7+
from pyk.kast.inner import KApply, KLabel, KSequence, KToken
8+
79
if TYPE_CHECKING:
810
from pyk.cterm.show import CTermShow
11+
from pyk.kast.inner import KInner
912
from pyk.kcfg.kcfg import KCFG
1013
from pyk.proof.reachability import APRProof
1114

15+
from .smir import SMIRInfo
16+
1217

1318
def _rule_to_markdown_link(rule: object) -> str:
1419
"""Render a single rule as a Markdown file link if possible.
@@ -231,7 +236,123 @@ def _path_nodes(source_id: int, path: Sequence[KCFG.Successor]) -> list[int]:
231236
return lines
232237

233238

234-
def render_leaf_k_cells(proof: APRProof, cterm_show: CTermShow) -> list[str]:
239+
def _drill(term: KInner, *path: tuple[str, int]) -> KInner | None:
240+
"""Navigate a nested KApply by a sequence of (label_name, arg_index) steps.
241+
242+
Returns the nested KInner at the end of the path, or None if any step
243+
doesn't match (wrong label, insufficient args, or not a KApply).
244+
"""
245+
246+
current: KInner = term
247+
for label, index in path:
248+
match current:
249+
case KApply(label=KLabel(name=name), args=args) if name == label and index < len(args):
250+
current = args[index]
251+
case _:
252+
return None
253+
return current
254+
255+
256+
def _extract_alloc_id(operands: KInner) -> int | None:
257+
"""Extract allocId from the first constant operand's provenance, if present."""
258+
259+
const_operand = 'constOperand(_,_,_)_BODY_ConstOperand_Span_MaybeUserTypeAnnotationIndex_MirConst'
260+
mir_const = 'mirConst(_,_,_)_TYPES_MirConst_ConstantKind_Ty_MirConstId'
261+
262+
ptrs = _drill(
263+
operands,
264+
('Operands::append', 0), # first operand
265+
('Operand::Constant', 0), # constOperand
266+
(const_operand, 2), # mirConst (3rd arg)
267+
(mir_const, 0), # ConstantKind::Allocated
268+
('ConstantKind::Allocated', 0), # allocation
269+
('allocation', 1), # provenanceMap (2nd arg)
270+
('provenanceMap', 0), # provenance entries
271+
)
272+
if ptrs is None:
273+
return None
274+
275+
match ptrs:
276+
case KApply(
277+
label=KLabel(name='ProvenanceMapEntries::append'),
278+
args=[
279+
KApply(
280+
label=KLabel(name='provenanceMapEntry'),
281+
args=[_, KApply(label=KLabel(name='allocId'), args=[KToken(token=n)])],
282+
),
283+
*_,
284+
],
285+
):
286+
return int(n)
287+
case _:
288+
return None
289+
290+
291+
def _annotate_unknown_function(k_cell: KInner, smir_info: SMIRInfo) -> list[str]:
292+
"""If the k cell is `#setUpCalleeData` for `** UNKNOWN FUNCTION **`, return annotation lines with decoded info."""
293+
294+
from .alloc import Allocation, AllocId, AllocInfo, Memory
295+
from .linker import _demangle
296+
297+
setup_call_label = '#setUpCalleeData(_,_,_)_KMIR-CONTROL-FLOW_KItem_MonoItemKind_Operands_Span'
298+
299+
annotations: list[str] = []
300+
301+
match k_cell:
302+
case KSequence(items=(KApply(label=KLabel(name=label_name), args=args), *_)) | KApply(
303+
label=KLabel(name=label_name), args=args
304+
) if (label_name == setup_call_label):
305+
match args:
306+
case [
307+
KApply(
308+
label=KLabel(name='MonoItemKind::MonoItemFn'),
309+
args=[
310+
KApply(args=[KToken(token=symbol_name)]),
311+
KApply(label=KLabel(name='defId(_)_BODY_DefId_Int'), args=[KToken(token=def_id_str)]),
312+
_,
313+
],
314+
),
315+
operands,
316+
KApply(label=KLabel(name='span'), args=[KToken(token=span_str)]),
317+
] if (
318+
symbol_name == '\"** UNKNOWN FUNCTION **\"'
319+
):
320+
def_id = int(def_id_str)
321+
span = int(span_str)
322+
case _:
323+
return []
324+
case _:
325+
return []
326+
327+
# Use extracted DefId for function name
328+
func_sym = smir_info.function_symbols.get(def_id, {})
329+
if name := func_sym.get('NormalSym') or func_sym.get('IntrinsicSym'):
330+
try:
331+
name = _demangle(name)
332+
except Exception:
333+
pass
334+
annotations.append(f' >> function: {name}')
335+
336+
# Use extracted Span for call site
337+
if span in smir_info.spans:
338+
path, start_row, start_col, _, _ = smir_info.spans[span]
339+
annotations.append(f' >> call span: {path}:{start_row}:{start_col}')
340+
341+
# Extract allocId from provenance and try to decode the referenced string
342+
alloc_id = _extract_alloc_id(operands)
343+
if alloc_id is not None:
344+
match smir_info.allocs.get(AllocId(alloc_id)):
345+
case AllocInfo(global_alloc=Memory(allocation=Allocation(bytez=bytez))):
346+
try:
347+
message = bytes(b for b in bytez if b is not None).decode('utf-8')
348+
annotations.append(f' >> message: {message!r}')
349+
except ValueError:
350+
pass
351+
352+
return annotations
353+
354+
355+
def render_leaf_k_cells(proof: APRProof, cterm_show: CTermShow, smir_info: SMIRInfo | None = None) -> list[str]:
235356
"""Render the <k> cell for every leaf node in the proof."""
236357

237358
leaves = sorted(
@@ -249,13 +370,18 @@ def render_leaf_k_cells(proof: APRProof, cterm_show: CTermShow) -> list[str]:
249370
k_cell = leaf.cterm.cell('K_CELL')
250371
k_lines = cterm_show.print_lines(k_cell)
251372
except KeyError:
373+
k_cell = None
252374
k_lines = ['<K_CELL unavailable>']
253375

254376
if not k_lines:
255377
lines.append(' (empty)')
256378
else:
257379
lines.extend(f' {k_line}' for k_line in k_lines)
258380

381+
if smir_info is not None and k_cell is not None:
382+
annotations = _annotate_unknown_function(k_cell, smir_info)
383+
lines.extend(annotations)
384+
259385
if idx != len(leaves) - 1:
260386
lines.append('')
261387

kmir/src/tests/integration/data/prove-rs/show/symbolic-args-fail.main.cli-stats-leaves.expected

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,7 @@ Leaf paths from init:
3232
LEAF <k> CELLS
3333
---------------
3434
Node 3:
35-
#setUpCalleeData ( monoItemFn ( ... name: symbol ( "** UNKNOWN FUNCTION **" ) , id: defId ( 38 ) , body: noBody ) , operandConstant ( constOperand ( ... span: span ( 32 ) , userTy: noUserTypeAnnotationIndex , const: mirConst ( ... kind: constantKindAllocated ( allocation ( ... bytes: b"\x00\x00\x00\x00\x00\x00\x00\x00\x17\x00\x00\x00\x00\x00\x00\x00" , provenance: provenanceMap ( ... ptrs: provenanceMapEntry ( ... offset: 0 , allocId: allocId ( 1 ) ) .ProvenanceMapEntries ) , align: align ( 8 ) , mutability: mutabilityMut ) ) , ty: ty ( 39 ) , id: mirConstId ( 25 ) ) ) ) .Operands ) ~> .K
35+
#setUpCalleeData ( monoItemFn ( ... name: symbol ( "** UNKNOWN FUNCTION **" ) , id: defId ( 38 ) , body: noBody ) , operandConstant ( constOperand ( ... span: span ( 32 ) , userTy: noUserTypeAnnotationIndex , const: mirConst ( ... kind: constantKindAllocated ( allocation ( ... bytes: b"\x00\x00\x00\x00\x00\x00\x00\x00\x17\x00\x00\x00\x00\x00\x00\x00" , provenance: provenanceMap ( ... ptrs: provenanceMapEntry ( ... offset: 0 , allocId: allocId ( 1 ) ) .ProvenanceMapEntries ) , align: align ( 8 ) , mutability: mutabilityMut ) ) , ty: ty ( 39 ) , id: mirConstId ( 25 ) ) ) ) .Operands , span ( 117 ) ) ~> .K
36+
>> function: core::panicking::panic::h941160ead03e2d54
37+
>> call span: <REPO>/kmir/src/tests/integration/data/prove-rs/symbolic-args-fail.rs:53:5
38+
>> message: 'assertion failed: false'

kmir/src/tests/integration/test_cli.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
from kmir.kmir import KMIR
1818

1919
PROVE_RS_DIR = (Path(__file__).parent / 'data' / 'prove-rs').resolve(strict=True)
20+
# Repo root: used to normalise absolute paths in expected-output snapshots so
21+
# they don't differ between local checkouts and CI (e.g. symbolic-args-fail.main.cli-stats-leaves).
22+
_REPO_ROOT = str(Path(__file__).resolve().parents[4])
23+
_PATH_REPLACEMENTS: dict[str, str] = {_REPO_ROOT + '/': '<REPO>/'}
2024

2125

2226
def _prove_and_store(
@@ -120,6 +124,7 @@ def test_cli_show_statistics_and_leaves(
120124
out,
121125
PROVE_RS_DIR / f'show/{src.stem}.{start_symbol}.cli-stats-leaves.expected',
122126
update=update_expected_output,
127+
path_replacements=_PATH_REPLACEMENTS,
123128
)
124129

125130

0 commit comments

Comments
 (0)