mchalupa / dg

[LLVM Static Slicer] Various program analyses, construction of dependence graphs and program slicing of LLVM bitcode.
MIT License
474 stars 131 forks source link

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

Open lzaoral opened 2 years ago

lzaoral commented 2 years ago

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 commented 2 years ago

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.