Skip to content

Bug: Ultimate C translation generates incorrect union field accesses #759

@bahnwaerter

Description

@bahnwaerter

The current C translation generates incorrect accesses to an union field.This issue can be reproduced with Ultimate Automizer (0.3.0-dev-23b7c4e042 with OpenJDK 21.0.9) using the following input:

union u {
    int a;
    int b;
};

int main()
{
    union u foo;

    foo.a = 10;
    foo.b = 20;

    //@ assert (foo.a == 20);
    //@ assert (foo.b == 20);

    return 0;
}

The input program is not proven to be correct as expected, because the ACSL assertion foo.a == 20 is found to be violated. The reason for this erroneous result is the incorrect generation of accesses to the union fields. The generated Boogie code for the accesses looks like this:

[...]
main_~foo~0#1!a := 10;
main_~foo~0#1!b := 20;
assert 20 == main_~foo~0#1!a;
assert 20 == main_~foo~0#1!b;
[...]

The incorrectly generated Boogie code shows that writing to the last field of a union does not invalidate the values ​​written by previous accesses. Instead, reading all fields of the union should return the last written value (assuming, according to the C standard, that the value can be reinterpreted in a suitable representation, which is the case in this example with two identical types). This error only occurs when field accesses are generated based on a LocalLValue. If the access is based on a HeapLValue, the C translation for a union works as expected, as a slightly modified program of the example witnesses:

union u {
    int a;
    int b;
};

int main()
{
    union u foo;

    foo.a = 10;
    foo.b = 20;

    int a = foo.a;
    int b = foo.b;

    //@ assert (a == 20);
    //@ assert (b == 20);

    return 0;
}

The modified program is proven to be correct, as expected. The correctly generated Boogie code for the accesses looks like this:

[...]
call write~int(10, { base: main_~#foo~0#1!base, offset: main_~#foo~0#1!offset }, 4);
call write~int(20, { base: main_~#foo~0#1!base, offset: main_~#foo~0#1!offset }, 4);
[...]
call main_#t~mem0#1 := read~int({ base: main_~#foo~0#1!base, offset: main_~#foo~0#1!offset }, 4);
main_~a~0#1 := main_#t~mem0#1;
[...]
call main_#t~mem1#1 := read~int({ base: main_~#foo~0#1!base, offset: main_~#foo~0#1!offset }, 4);
main_~b~0#1 := main_#t~mem1#1;
[...]
assert 20 == main_~a~0#1;
assert 20 == main_~b~0#1;
[...]

Since access to the shared memory is granted for all fields of a union, the reinterpreted value of the last written field is always correctly returned.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions