dg icon indicating copy to clipboard operation
dg copied to clipboard

Incorrect slicing of functions ending with a `noreturn` call.

Open lzaoral opened this issue 4 years ago • 1 comments

Consider the following C program

#include <assert.h>
#include <stdlib.h>

void test_assert(int arg) {
    assert(arg);
}

int main(void)
{
    test_assert(1);
    exit(0);
}

and slice it like this:

$ clang -O0 -c -emit-llvm test.c -o test.bc
$ llvm-slicer -c __assert_fail test.bc

Problem: The sliced bitcode now has a reachable unreachable instruction because test_assert will always return:

; Function Attrs: noinline nounwind optnone sspstrong uwtable
define dso_local void @test_assert(i32 %0) #0 !dbg !11 {
  %2 = alloca i32, align 4
  store i32 %0, i32* %2, align 4
  %3 = load i32, i32* %2, align 4, !dbg !15
  %4 = icmp ne i32 %3, 0, !dbg !15
  br i1 %4, label %safe_return, label %5, !dbg !18

5:                                                ; preds = %1
  call void @__assert_fail(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str, i64 0, i64 0), i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.
str.1, i64 0, i64 0), i32 5, i8* getelementptr inbounds ([22 x i8], [22 x i8]* @__PRETTY_FUNCTION__.test_assert, i64 0, i64 0)) #2, !dbg !15
  unreachable, !dbg !15

safe_return:                                      ; preds = %1
  ret void
}

; Function Attrs: noreturn nounwind
declare void @__assert_fail(i8*, i8*, i32, i8*) #1

; Function Attrs: noinline nounwind optnone sspstrong uwtable
define dso_local i32 @main() #0 !dbg !19 {
  call void @test_assert(i32 1), !dbg !22
  unreachable, !dbg !23
}

EDIT: typo

lzaoral avatar Nov 23 '21 12:11 lzaoral

The issue is actually more complex than just error(3). The same applies to any function (e.g. not just main) that does not return and is decorated by _Noreturn function specifier from C11 (the same probably holds for __attribute__((noreturn)) as well). Note that such function will have the noreturn function attribute in LLVM.

#include <assert.h>
#include <stdlib.h>
#include <stdnoreturn.h>

void test_assert(int arg) {
    assert(arg);
}

noreturn void foo(void) {
    exit(0);
}

int main(void)
{
    test_assert(1);
    foo();
}

It's important to check bodies of all such functions though because as C11 says:

If the function declared _Noreturn returns, the behaviour is undefined.

So just replacing all undefined instructions in IR after calls of functions with noreturn attributes will hide this issue.

lzaoral avatar Dec 29 '21 15:12 lzaoral