Hacker News new | ask | show | jobs
by duneroadrunner 2645 days ago
Thanks for noticing :) It's been quite a while since I worked on the code, but I believe that the translator intentionally left types declared as "char {star}" unmodified assuming that they were being used as strings [1] rather "regular" array buffers. I'm guessing that dealing with strings would have been a lot more work because it would require providing safe compatible replacements for all the standard C library string functions.

I think you should find that array buffers of other types, like "unsigned char" or "const unsigned char", and their associated pointer iterators are translated to their corresponding macros. I'd be interested if you find otherwise. If you're interested, the relevant code for the translator is in the "safercpp" subdirectory [2]. It's not super-well commented so if you have any questions feel free to post them in the "issues" section of the repository.

[1] https://github.com/duneroadrunner/SaferCPlusPlus-AutoTransla...

[2] https://github.com/duneroadrunner/SaferCPlusPlus-AutoTransla...

1 comments

OK, Here's a non-string function where the translator is trying to deal with C written like it's 1980:

    static unsigned countZeros(MSE_LH_ARRAY_ITERATOR_TYPE(const unsigned char) data,
        size_t size, size_t pos)
    {
      MSE_LH_ARRAY_ITERATOR_TYPE(const unsigned char)  start = data + pos;
      MSE_LH_ARRAY_ITERATOR_TYPE(const unsigned char)  end = start + 
          MAX_SUPPORTED_DEFLATE_LENGTH;
      if(end > data + size) end = data + size;
      data = start;
      while(data != end && *data == 0) ++data;
      /*subtracting two addresses returned as 32-bit number
          (max value is MAX_SUPPORTED_DEFLATE_LENGTH)*/
      return (unsigned)(data - start);
    }
What guarantees that the "while" loop will not run away and take "data" outside the array bounds?

I proposed a version of C with slices and references, where you could write that like this:

    static unsigned countZeros(const unsigned char &(data)[size],
        size_t size, size_t pos)
    {
      const unsigned char &(data1)[size-pos] = data[pos:size-pos]; // slice
      size_t cnt = 0;
      while (cnt < LENGTH(data1) && cnt < MAX_SUPPORTED_DEFLATE_LENGTH && data1[cnt] == 0) ++cnt;
      return(cnt);
    }
The "data" parameter has size info, so the language knows how big it is. The "work" variable is a slice of "data". This eliminates the need for pointer arithmetic. Much pointer arithmetic in C, especially where you have a pointer partway into an array, is an attempt to emulate a slice.

Automatically extracting slice usage from code with pointer arithmetic is a tough problem. But not impossible. When you see code constructing something like

    data = start;
    while(data != end && *data == 0) ++data;
you have to recognize that as subscripting.

    while(data != end && *data == 0) ++data;
    return (unsigned)(data - start);
should become first

    data = start;
    size_t dataix;
    while(&data[dataix] != end && data[dataix] == 0) ++dataix;
    return (unsigned)(&data[dataix] - start);
by substituting subscripting for pointer arithmetic.

Next, when you see an offset array being created, as in

    start = data + pos;
turn that into a slice:

    const unsigned char &(data1)[size-pos] = data[pos:size-pos]; // slice
The slice is the same pointer, but the there's now valid size information associated with it.

If you do transformations like that, you get a version of C where subscript checking is possible. You can then hoist or prove out many of the subscript checks. Here, the compiler would be expected to understand that if an array subscript is less than LENGTH of the array, it's safe. LENGTH here, as I wrote in my paper, refers to the length of the array as known to the compiler from the array declaration. Here, array lengths can be expressions evaluated at declaration time. That's how length info gets passed around.

    const unsigned char &(data)[size]
as a parameter means "this is an array of size "size". "size" comes in via another parameter. The function can assume "size" is valid, and all callers must check that, either at compile time or run time.

If you can't write an expression for the size of something, you have a big problem with your program.

> What guarantees that the "while" loop will not run away and take "data" outside the array bounds?

What do you mean "the array bounds"? The code is memory safe. "data" is an iterator that knows exactly what array/container it's pointing to, and that container knows its own size. Dereferences are bounds checked (by default).

This translated code is not intended to be performance optimal. The translator does not add, remove or rearrange any of the original source code elements, it simply replaces some of them with macros that are defined as functionally equivalent, memory safe C++ substitutes for the original element. Doing it this way has the benefit of allowing you to "disable" the memory safety mechanisms by reverting the macro definitions to the original (unsafe) elements.

I have not yet gotten around to addressing performance of the translated code. In order to preserve the ability to revert back to pure C code, there would need to be an additional set of macros (like maybe an "array view" macro) that could be mapped to their (safe) high performance C++ counterparts but that would be more restricted in their usage.

But at this point I think the value of that is questionable. If you need your code to be memory safe and high performance, the most expedient thing to do is to just accept the translated code as C++ code (or SaferCPlusPlus code) and re-optimize the performance bottlenecks as idiomatic SaferCPlusPlus code. SaferCPlusPlus is, along with Rust, the fastest [1] option for memory (and data race) safe programming.

And if you don't like the C++ language as whole, just (define and) stick to a subset you're comfortable with, right? I mean, (I think your proposal is fine as an extension of C, but) I don't see the point in extending the C language with things like views/slices/spans, when the C language is already extended with those. It's called C++ (or some subset thereof) right? And with C++ you can solve the memory (and data race) issues much more comprehensively and performantly (if that's a word :) than with any extension to C. No?

[1] https://github.com/duneroadrunner/SaferCPlusPlus-BenchmarksG...