Skip to content

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

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.

Activity

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

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