Description
Currently, -itypes-for-extern
is implemented as a bunch of special cases that modify the generated types at rewriting time. These extra cases are making it harder for me to verify that the code is correct, and the rest of 3C is unaware that this transformation is happening, which is liable to cause errors or other confusing behavior. Instead, -itypes-for-extern
should be integrated into the constraint graph. To a first approximation, in the current 3C codebase, that would mean constraining the "unchecked" sides of itypes to wild, though 3C will probably have to evolve a bit before that approach becomes viable (see "Obstacles to the proposed change" below).
Problems with the current design
Here's an example I ran into today that illustrates the weakness of the current design well. 3c -itypes-for-extern -addcr
on the following:
typedef int *p_int;
void foo(void) {
p_int x;
}
produces:
typedef int *p_int;
void foo(void) _Checked {
p_int x = ((void *)0); // error: local variable in a checked scope must have a checked type
}
The solved type of p_int
is _Ptr<int>
, so 3C assumed that x
had a fully checked type and marked foo
as _Checked
. But -itypes-for-extern
violated this assumption.
A few other problems with the current design:
-
In the example program in Should
DeclRewriter::buildItypeDecl
use the actual internalPVConstraint
? #704, forcing the unchecked side of an itype to all-wild causes a compile error in combination with 3C's solution for the rest of the program. The fix proposed in ShouldDeclRewriter::buildItypeDecl
use the actual internalPVConstraint
? #704 is to just stop forcing the unchecked side to all-wild and use its actual solution. But-itypes-for-extern
wants to keep the unchecked side all-wild. To do that without causing a compile error,-itypes-for-extern
will need to add constraints to ensure that the rest of the solution is compatible with the all-wild unchecked side. -
Currently, when 3C infers a generic signature for a function, it decides between
_For_any
and_Itype_for_any
based on whether the itype string returned byFunctionDeclBuilder::buildDeclVar
is nonempty:
checkedc-clang/clang/lib/3C/DeclRewriter.cpp
Lines 624 to 629 in 94cd56c
That's a hack, but it does work correctly with-itypes-for-extern
now becauseFunctionDeclBuilder::buildDeclVar
checks for-itypes-for-extern
and forces the generation of an itype if the flag is on. If we want to clean up the generic inference to be based on the constraint graph rather than a string generated during rewriting, then we need to have the information about the itype available in the constraint graph.
Obstacles to the proposed change
According to John, the main reason that -itypes-for-extern
isn't integrated into the constraint graph now is that under the current design of liberal itypes for functions, the notion of the "internal" type is coupled to "unchecked side of the itype", and "external" to "checked side of the itype". Thus, in an example like:
void test(int *a) { int *b = a; }
if -itypes-for-extern
constrains the "unchecked" (i.e., "internal") side to wild, that will force b
to be wild:
void test(int *a : itype(_Ptr<int>)) { int *b = a; }
That's undesirable because Checked C actually allows either side of the itype to be used either inside or outside the function. We want -itypes-for-extern
to be able to produce the following, as it does in the current hacky implementation:
void test(int *a : itype(_Ptr<int>)) { _Ptr<int> b = a; }
We may be able to solve this by redesigning the constraint graph for liberal itypes to reflect the fact that either side of an itype can be used either inside or outside the function: see #743.