Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GenC: replacing a VLA field should be forbidden #1288

Open
mario-bucev opened this issue Jun 7, 2022 · 1 comment
Open

GenC: replacing a VLA field should be forbidden #1288

mario-bucev opened this issue Jun 7, 2022 · 1 comment
Labels
bug genc C code generator

Comments

@mario-bucev
Copy link
Collaborator

We should perform such checks because the following snippet results in a C code exhibiting UB:

import stainless.io._
import stainless.lang._
import stainless.annotation._

object GenCArrayFieldReplaceVLA {

  @cCode.noMangling
  case class Buffer(var array: Array[Int], extra: Int) // `array` is a VLA

  @cCode.noMangling
  def replace(b: Buffer): Unit = {
    b.array = Array.fill(20)(444)
  }


  @cCode.`export`
  def main(): Int = {
    implicit val state = stainless.io.newState
    val b = Buffer(Array.fill(10)(1), 42)
    replace(b)
    assert(b.array(14) == 444) // Valid VC
    StdOut.println(b.array(14))
    0
  }

}

This gets translated to:

typedef struct {
  int32_t* data;
  int32_t length;
} array_int32;

typedef struct {
  array_int32 array;
  int32_t extra;
} Buffer;

int32_t main(void) {
    int32_t stainless_buffer[10] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 };
    array_int32 norm = (array_int32) { .data = stainless_buffer, .length = 10 };
    Buffer b = (Buffer) { .array = norm, .extra = 42 };
    replace(&b);
    printf("%d\n", b.array.data[14]);
    return 0;
}

static void replace(Buffer* b) {
    int32_t stainless_buffer[20] = { 444, 444, 444, 444, 444, 444, 444, 444, 444, 444, 444, 444, 444, 444, 444, 444, 444, 444, 444, 444 };
    array_int32 norm = (array_int32) { .data = stainless_buffer, .length = 20 };
    b->array = norm; // refers to a local array that will go out of scope!
}

Running Valgrind on the compiled code yields an "invalid read".

Note: though this issue looks similar to #1287, these issues are different.
Here, we must forbid replacing a VLA array field because the lifetime of the local array ends when the function returns. In the other issue, we do not have this problem because Buffer has a fixed-size array field (and not a pointer).
These issues may both be resolved by forbidding any array replacement, though for #1287 it may be a bit too restrictive.

@mario-bucev mario-bucev added bug genc C code generator labels Jun 7, 2022
@mario-bucev
Copy link
Collaborator Author

mario-bucev commented Jun 8, 2022

Somewhat related:

import stainless.io._
import stainless.lang._
import stainless.annotation._

object VLAUpdate {
  @cCode.`export`
  def main(): Int = {
    implicit val state = stainless.io.newState

    val cond = true
    val arrr = Array.fill(3)(Array.fill(5)(123))

    if (cond) {
      arrr(0) = Array.fill(10)(1)
    }

    StdOut.println(arrr(0)(9))
    0
  }

}

The VLA declared in the if statement does not live long enough, as its lifetime is bound to the scope of the if:

typedef struct {
  int32_t* data;
  int32_t length;
} array_int32;

typedef struct {
  array_int32* data;
  int32_t length;
} array_array_int32;

int32_t main(void) {
    bool cond = true;
    
    int32_t stainless_buffer_0[5] = { 123, 123, 123, 123, 123 };
    array_int32 norm_0 = (array_int32) { .data = stainless_buffer_0, .length = 5 };
    int32_t stainless_buffer_1[5] = { 123, 123, 123, 123, 123 };
    array_int32 norm_1 = (array_int32) { .data = stainless_buffer_1, .length = 5 };
    int32_t stainless_buffer_2[5] = { 123, 123, 123, 123, 123 };
    array_int32 norm_2 = (array_int32) { .data = stainless_buffer_2, .length = 5 };
    
    array_int32 stainless_buffer_3[3] = { norm_0, norm_1, norm_2 };
    array_array_int32 arrr = (array_array_int32) { .data = stainless_buffer_3, .length = 3 };
    
    if (cond) {
        int32_t stainless_buffer_4[10] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 };
        array_int32 norm = (array_int32) { .data = stainless_buffer_4, .length = 10 };
        arrr.data[0] = norm;
    } // arrr.data[0] invalidated
    
    printf("%d\n", arrr.data[0].data[9]);
    return 0;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug genc C code generator
Projects
None yet
Development

No branches or pull requests

1 participant