Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Example of error caused by UB of incrementing a NULL pointer

This code :

int *p = nullptr;
p++;

cause undefined behaviour as it was discussed in Is incrementing a null pointer well-defined?

But when explaining fellows why they should avoid UB, besides saying it is bad because UB means that anything could happen, I like to have some example demonstating it. I have tons of them for access to an array past the limits but I could not find a single one for that.

I even tried

int testptr(int *p) {
    intptr_t ip;
    int *p2 = p + 1;
    ip = (intptr_t) p2;
    if (p == nullptr) {
        ip *= 2;
    }
    else {
        ip *= -2;
    } return (int) ip;
}

in a separate compilation unit hoping that an optimizing compiler would skip the test because when p is null, line int *p2 = p + 1; is UB, and compilers are allowed to assume that code does not contain UB.

But gcc 4.8.2 (I have no useable gcc 4.9) and clang 3.4.1 both answer a positive value !

Could someone suggest some more clever code or another optimizing compiler to exhibit a problem when incrementing a null pointer ?

like image 639
Serge Ballesta Avatar asked Apr 24 '15 10:04

Serge Ballesta


1 Answers

How about this example:

int main(int argc, char* argv[])
{
    int a[] = { 111, 222 };

    int *p = (argc > 1) ? &a[0] : nullptr;
    p++;
    p--;

    return (p == nullptr);
}

At face value, this code says: 'If there are any command line arguments, initialise p to point to the first member of a[], otherwise initialise it to null. Then increment it, then decrement it, and tell me if it's null.'

On the face of it this should return '0' (indicating p is non-null) if we supply a command line argument, and '1' (indicating null) if we don't. Note that at no point do we dereference p, and if we supply an argument then p always points within the bounds of a[].

Compiling with the command line clang -S --std=c++11 -O2 nulltest.cpp (Cygwin clang 3.5.1) yields the following generated code:

    .text
    .def     main;
    .scl    2;
    .type   32;
    .endef
    .globl  main
    .align  16, 0x90
main:                                   # @main
.Ltmp0:
.seh_proc main
# BB#0:
    pushq   %rbp
.Ltmp1:
    .seh_pushreg 5
    movq    %rsp, %rbp
.Ltmp2:
    .seh_setframe 5, 0
.Ltmp3:
    .seh_endprologue
    callq   __main
    xorl    %eax, %eax
    popq    %rbp
    retq
.Leh_func_end0:
.Ltmp4:
    .seh_endproc

This code says 'return 0'. It doesn't even bother to check the number of command line args.

(And interestingly, commenting out the decrement has no effect on the generated code.)

like image 86
Jeremy Avatar answered Oct 05 '22 23:10

Jeremy