checkedc icon indicating copy to clipboard operation
checkedc copied to clipboard

Disallow ptr<void>

Open dtarditi opened this issue 6 years ago • 2 comments

I think we should disallow ptr<void>. It has been used mainly in bounds-safe interfaces, but those uses were errors. Using ptr<void> in a bounds-safe interface provides the illusion that the use is safe, when it almost always isn't. We would still allow array_ptr<void>.

In the Checked C specification, ptr<void> points to at least 1 byte of storage. Converting a value to ptr<void> means that only 1 byte of data can be accessed validly by the ptr<void>. Before we introduced generic functions, we had the following bounds-safe interface for bsearch:

void *bsearch(const void *key : byte_count(size),
              const void *base : byte_count(nmemb * size),
              size_t nmemb, size_t size,
              int ((*compar)(const void *, const void *)) :
                  itype(ptr<int(ptr<const void>, ptr<const void>)>)) :
       byte_count(size);

The bounds-safe interface for the comparison function compar is incorrect when comparing pointers to any types larger than 1 byte.

The problem is that ptr<T> is supposed to point to a single object of type T. There are no objects of type void in C.

From the C11 specification (page 41):

The void type comprises an empty set of values; it is an incomplete object type that cannot be completed.

I tried prototyping this change in the compiler and found two issues:

  1. Some instantiations of generic functions may fail because they result in illegal types: if you have a generic function whose type includes ptr<T>, it becomes illegal to replace T with void. This is fine. We can issue an error message when this happens.
  2. When a function with a bounds-safe interface is called in an unchecked scope without type arguments, we implicitly supply the void type for each missing type argument. This can result in an illegal instantiation. In that case, we need to decide what to do. One possibility is to remove the bounds-safe interface from the instantiated function type. The function couldn't be called with arguments with checked types in this case.

In checked code, array_ptr<void> can be converted to a pointer type that can be used to dereference memory only if has an associated bounds. I think that's the right thing to happen. It would still be possible to specify an incorrect bounds-safe interface that uses an array_ptr<void> without a length, when the parameter is used to access memory. This isn't a problem specific to array_ptr<void>. It affects any other parameter with an array_ptr type without a bounds that is used to access memory.

dtarditi avatar Nov 28 '18 00:11 dtarditi

It appears to me that array_ptr<void> should also be disallowed for the same reasons as those that prompt ptr<void> to be disallowed.

Section 2.2 of Checked C Specification says:

array_ptr<T >: this is a pointer to an element of an array of type T values. A variable of type array_ptr that is used to access memory must have an associated declaration of the valid bounds for the variable.

For example, assume the following declaration.

array_ptr<void> p : count(10);  // has bounds; initializer required

what is the storage space allocated when initializing p? Is it 10 bytes? Wouldn't the same incongruence that is a consequence of the C11 Spec for void pointed by you to support disallowance of ptr<void>, apply here? How should a function that takes an argument of type array_ptr<void> use the argument?

Looking at it from a different angle,

Section 2.3 para 3 of Checked C Specification says:

All array references to checked array types are bounds checked. C has the rule that an “array of T ” is converted implicitly to a “pointer to T ” in a number of situations. This rule is extended to convert a “checked array of T ” to an “array_ptr to T ”.

As you know, C does not allow the following declaration

void p[10];

because void is an incomplete type. This implies an array pointer to void type would never occur and/or is illegal.

Correspondingly, the following declaration in Checked C would also be illegal

void p checked[10];

Consequently, its potential conversion per the above quoted conversion rule from Checked C Specification Section 2.3, to array_ptr<void> p : count(10) would never occur and/or is illegal.

So, it appears to me that array_ptr<void> (and nt_array_ptr<void> as a follow on) has no legal semantics and should also be disallowed. It also feels more congruent to disallow void type to participate in all - not just one - of the new pointer types of Checked C.

Happy to learn of your thoughts.

Thanks!

bharadwajy avatar Nov 28 '18 16:11 bharadwajy

Re array_ptr<void>: It's true that array_ptr<void> p : count(n) doesn't make sense because the bound isn't well-defined, and the compiler already rejects it. But array_ptr<void> p : byte_count(n) has a well-defined bound, and it's potentially useful as type for uninitialized memory allocated by malloc that can be safely converted to any array_ptr<T> where T doesn't contain checked pointers (see #458), so I'm not convinced that it should be disallowed.

Note that nt_array_ptr<void> is already rejected (regardless of bounds) because it's unclear what it would mean for it to be null-terminated.

mattmccutchen-cci avatar Jul 01 '21 12:07 mattmccutchen-cci