Skip to content

Extend state representation to track errno across control-flow#1284

Draft
antoniofrighetto wants to merge 1 commit intoAliveToolkit:masterfrom
antoniofrighetto:feature/handle-errno-for-malloc
Draft

Extend state representation to track errno across control-flow#1284
antoniofrighetto wants to merge 1 commit intoAliveToolkit:masterfrom
antoniofrighetto:feature/handle-errno-for-malloc

Conversation

@antoniofrighetto
Copy link
Contributor

An attempt to handle errno in order to prevent Alive2 from treating store-to-load forwarding as valid refinements, if may alias errno. This is achieved by adding a new SMT expression to model errno meant to be used on malloc failure. Also treat malloc et alia as may write errno via LLVM errnomem location kind while translating from LLVM IR.


A thank to Nuno for direction on this!

An attempt to handle `errno` in order to prevent Alive2 from treating
store-to-load forwarding as valid refinements, if may alias `errno`.
This is achieved by adding a new SMT expression to model `errno` meant
to be used on malloc failure. Also treat malloc et alia as may write
errno via LLVM `errnomem` location kind while translating from LLVM IR.

// Create a new symbolic variable that represents errno if the allocation
// fails.
if (blockKind == MALLOC || blockKind == CXX_NEW) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don’t seem to model realloc as of now, correct?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we do support realloc!

Comment on lines +2339 to +2340
expr errno_on_failure = expr::mkFreshVar("#malloc_errno",
expr::mkUInt(0, 32));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m experiencing the following false positive for test attrs/allocsize.srctgt.ll:

declare ptr @my_malloc(i32) allocsize(0)

define ptr @src() {
#0:
  %p = call ptr @my_malloc(i32 23) allocsize(0)
  ret ptr %p
}
=>
declare ptr @my_malloc(i32) allocsize(0)

define ptr @tgt() {
#0:
  %p = call ptr @my_malloc(i32 23) dereferenceable_or_null(23) allocsize(0)
  ret ptr %p
}


src_state.getReturnErrno():
(ite (or malloc_never_fails |#alloc_nondet_nonnull!3|)
     errno_init
     |#malloc_errno!4|)



tgt_state.getReturnErrno():
(ite (or malloc_never_fails |#alloc_nondet_nonnull!6|)
     errno_init
     |#malloc_errno!7|)

Transformation doesn't verify!

ERROR: Mismatch in errno at return

Possibly we shouldn’t use fresh unique symbolic variables for malloc_errno: I tried switching from using mkFreshVar w/ addNonDetVar to using the dedicated helper getFreshNondetVar instead. This fixes this issue but in doing so we don’t ; ERROR: Mismatch in errno at return anymore for the two new added tests (so we allow the refinement as valid). Any idea on what I'm missing?

@regehr
Copy link
Contributor

regehr commented Feb 4, 2026

this is super cool, I never thought about errno being something special we'd need to handle!


declare noalias ptr @malloc(i64) memory(inaccessiblemem: readwrite, errnomem: write)

; ERROR: Mismatch in errno at return
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not the expected behavior. LLVM wants to allow removal of allocation functions.

This is annoying, maybe we need to model alloc functions as written to errno only non-deterministically?

@RalfJung @nikic

}

expr errno_on_write = expr::mkFreshVar((name + "#errno").c_str(),
expr::mkUInt(0, 32));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of mkUInt, you can use errno_val.
I would also move this call inside mkIf_fold to avoid creating the expression altogether

std::variant<smt::DisjointExpr<StateValue>, StateValue> return_val;
std::variant<smt::DisjointExpr<Memory>, Memory> return_memory;
std::set<smt::expr> return_undef_vars;
smt::expr return_errno;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can use the std::variant trick above to keep a DisjointExpr during state execution. This can shrink final expr size.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants