Fix type checking and result type computation for ? : operator.

This was non-standard in various ways, mainly in regard to pointer types. It has been rewritten to closely follow the specification in the C standards.

Several helper functions dealing with types have been introduced. They are currently only used for ? :, but they might also be useful for other purposes.

New tests are also introduced to check the behavior for the ? : operator.

This fixes #35 (including the initializer-specific case).
This commit is contained in:
Stephen Heumann 2022-06-23 22:04:03 -05:00
parent 15dc3a46c4
commit 102d6873a3
7 changed files with 304 additions and 78 deletions

View File

@ -65,6 +65,7 @@ var
{----}
lastwasconst: boolean; {did the last GenerateCode result in an integer constant?}
lastconst: longint; {last integer constant from GenerateCode}
lastWasNullPtrConst: boolean; {did last GenerateCode give a null ptr const?}
{---------------------------------------------------------------}
procedure AssignmentConversion (t1, t2: typePtr; isConstant: boolean;
@ -570,6 +571,25 @@ if tree <> nil then begin
end; {DisposeTree}
procedure ValueExpressionConversions;
{ Perform type conversions applicable to an expression used }
{ for its value. These include lvalue conversion (removing }
{ qualifiers), array-to-pointer conversion, and }
{ function-to-pointer conversion. See C17 section 6.3.2.1. }
{ }
{ variables: }
{ expressionType - set to type after conversions }
begin {ValueExpressionConversions}
expressionType := Unqualify(expressionType);
if expressionType^.kind = arrayType then
expressionType := MakePointerTo(expressionType^.aType)
else if expressionType^.kind = functionType then
expressionType := MakePointerTo(expressionType);
end; {ValueExpressionConversions}
procedure AssignmentConversion {t1, t2: typePtr; isConstant: boolean;
value: longint; genCode, checkConst: boolean};
@ -2772,7 +2792,7 @@ var
doingScalar: boolean; {temp; for assignment operators}
et: baseTypeEnum; {temp storage for a base type}
i: integer; {loop variable}
isString: boolean; {was the ? : a string?}
isNullPtrConst: boolean; {is this a null pointer constant?}
isVolatile: boolean; {is this a volatile op?}
lType: typePtr; {type of operands}
kind: typeKind; {temp type kind}
@ -2780,6 +2800,7 @@ var
t1: integer; {temporary work space label number}
tlastwasconst: boolean; {temp lastwasconst}
tlastconst: longint; {temp lastconst}
tlastWasNullPtrConst: boolean; {temp lastWasNullPtrConst}
tp: tokenPtr; {work pointer}
tType: typePtr; {temp type of operand}
@ -3528,6 +3549,7 @@ var
begin {GenerateCode}
lastwasconst := false;
isNullPtrConst := false;
case tree^.token.kind of
parameterOper:
@ -3590,6 +3612,7 @@ case tree^.token.kind of
Gen1t(pc_ldc, tree^.token.ival, cgWord);
lastwasconst := true;
lastconst := tree^.token.ival;
isNullPtrConst := tree^.token.ival = 0;
if tree^.token.kind = intConst then
expressionType := intPtr
else if tree^.token.kind = uintConst then
@ -3612,6 +3635,7 @@ case tree^.token.kind of
expressionType := ulongPtr;
lastwasconst := true;
lastconst := tree^.token.lval;
isNullPtrConst := tree^.token.lval = 0;
end; {case longConst}
longlongConst,ulonglongConst: begin
@ -3624,6 +3648,7 @@ case tree^.token.kind of
lastwasconst := true;
lastconst := tree^.token.qval.lo;
end; {if}
isNullPtrConst := (tree^.token.qval.hi = 0) and (tree^.token.qval.lo = 0);
end; {case longlongConst}
floatConst: begin
@ -4565,78 +4590,71 @@ case tree^.token.kind of
GenerateCode(tree^.left); {evaluate the condition}
CompareToZero(pc_neq);
GenerateCode(tree^.middle); {evaluate true expression}
ValueExpressionConversions;
lType := expressionType;
tlastwasconst := lastwasconst;
tlastconst := lastconst;
tlastWasNullPtrConst := lastWasNullPtrConst;
GenerateCode(tree^.right); {evaluate false expression}
isString := false; {handle string operands}
if lType^.kind in [arrayType,pointerType] then
if lType^.aType^.baseType = cgUByte then begin
with expressionType^ do
if kind in [arrayType,pointerType] then begin
if aType^.baseType = cgUByte then
isString := true
else if (kind = pointerType)
and (CompTypes(lType,expressionType)) then
{it's all OK}
else
Error(47)
end {if}
else if (kind = scalarType)
and lastWasConst
and (lastConst = 0) then
et := UsualBinaryConversions(lType)
{it's all OK}
else
ValueExpressionConversions;
{check, compute, and convert types}
if (lType^.kind = pointerType) or (expressionType^.kind = pointerType)
then begin
if tlastWasNullPtrConst then begin
if lType^.kind = scalarType then
Gen2(pc_cnn, ord(lType^.baseType), ord(cgULong));
end {if}
else if lastWasNullPtrConst then begin
if expressionType^.kind = scalarType then
Gen2(pc_cnv, ord(expressionType^.baseType), ord(cgULong));
expressionType := lType;
end {if}
else if lType^.kind <> expressionType^.kind then {not both pointers}
Error(47)
else if IsVoid(lType^.pType) or IsVoid(expressionType^.pType) then begin
if not looseTypeChecks then
if (lType^.pType^.kind = functionType) or
(expressionType^.pType^.kind = functionType) then
Error(47);
lType := voidPtrPtr;
expressionType := voidPtrPtr;
end; {if}
with expressionType^ do
if kind in [arrayType,pointerType] then
if aType^.baseType in [cgByte,cgUByte] then begin
if kind = pointerType then begin
if tlastwasconst and (tlastconst = 0) then
{it's all OK}
else if CompTypes(lType, expressionType) then
{it's all OK}
else
Error(47);
end {if}
else
expressionType := MakePointerTo(MakeQualifiedType(voidPtr,
lType^.pType^.qualifiers+expressionType^.pType^.qualifiers));
end {else if}
else if CompTypes(Unqualify(lType^.pType),
Unqualify(expressionType^.pType)) then begin
if not looseTypeChecks then
if not StrictCompTypes(Unqualify(lType^.pType),
Unqualify(expressionType^.pType)) then
Error(47);
et := UsualBinaryConversions(lType);
lType := voidPtrPtr;
expressionType := voidPtrPtr;
end; {if}
{generate the operation}
if lType^.kind in [structType, unionType, arrayType] then begin
expressionType := MakePointerTo(MakeQualifiedType(MakeCompositeType(
Unqualify(lType^.pType),Unqualify(expressionType^.pType)),
lType^.pType^.qualifiers+expressionType^.pType^.qualifiers));
end {else if}
else
Error(47);
et := cgULong;
end {if}
else if lType^.kind in [structType, unionType] then begin
if not CompTypes(lType, expressionType) then
Error(47);
Gen0(pc_bno);
Gen0t(pc_tri, cgULong);
et := cgULong;
end {if}
else begin
if expressionType^.kind = pointerType then
tType := expressionType
else
tType := lType;
if (expressionType^.kind = scalarType)
and (expressionType^.baseType = cgVoid)
and (lType^.kind = scalarType)
and (lType^.baseType = cgVoid) then
if IsVoid(lType) and IsVoid(expressionType) then
et := cgVoid
else
et := UsualBinaryConversions(lType);
Gen0(pc_bno);
Gen0t(pc_tri, et);
end; {else}
if isString then {set the type for strings}
expressionType := stringTypePtr;
{generate the operation}
Gen0(pc_bno);
Gen0t(pc_tri, et);
end; {case colonch}
castoper: begin {(cast)}
GenerateCode(tree^.left);
if lastWasNullPtrConst then
if expressionType^.kind = scalarType then
if tree^.castType^.kind = pointerType then
if IsVoid(tree^.castType^.pType) then
if tree^.castType^.pType^.qualifiers = [] then
isNullPtrConst := true;
Cast(tree^.castType);
end; {case castoper}
@ -4646,6 +4664,7 @@ case tree^.token.kind of
end; {case}
if doDispose then
dispose(tree);
lastWasNullPtrConst := isNullPtrConst;
end; {GenerateCode}

View File

@ -164,6 +164,16 @@ procedure InitSymbol;
{ Initialize the symbol table module }
function IsVoid (tp: typePtr): boolean;
{ Check to see if a type is void }
{ }
{ Parameters: }
{ tp - type to check }
{ }
{ Returns: True if the type is void, else false }
function LabelToDisp (lab: integer): integer; extern;
{ convert a local label number to a stack frame displacement }
@ -192,6 +202,17 @@ function MakePointerTo (pType: typePtr): typePtr;
{ returns: the pointer type }
function MakeCompositeType (t1, t2: typePtr): typePtr;
{ Make the composite type of two compatible types. }
{ See C17 section 6.2.7. }
{ }
{ parameters: }
{ t1,t2 - the input types (must be compatible) }
{ }
{ returns: pointer to the composite type }
function MakeQualifiedType (origType: typePtr; qualifiers: typeQualifierSet):
typePtr;
@ -427,26 +448,6 @@ var
p1, p2: parameterPtr; {for tracing parameter lists}
pt1,pt2: typePtr; {pointer types}
function IsVoid (tp: typePtr): boolean;
{ Check to see if a type is void }
{ }
{ Parameters: }
{ tp - type to check }
{ }
{ Returns: True if the type is void, else false }
begin {IsVoid}
IsVoid := false;
if tp = voidPtr then
IsVoid := true
else if tp^.kind = scalarType then
if tp^.baseType = cgVoid then
IsVoid := true;
end; {IsVoid}
begin {CompTypes}
CompTypes := false; {assume the types are not compatible}
kind1 := t1^.kind; {get these for efficiency}
@ -1703,6 +1704,122 @@ constCharPtr^.qualifiers := [tqConst];
end; {InitSymbol}
function IsVoid {tp: typePtr): boolean};
{ Check to see if a type is void }
{ }
{ Parameters: }
{ tp - type to check }
{ }
{ Returns: True if the type is void, else false }
begin {IsVoid}
IsVoid := false;
if tp = voidPtr then
IsVoid := true
else if tp^.kind = scalarType then
if tp^.baseType = cgVoid then
IsVoid := true;
end; {IsVoid}
function CopyType (tp: typePtr): typePtr;
{ Make a new copy of a type, so it can be modified. }
{ }
{ Parameters: }
{ tp - type to copy }
{ }
{ Returns: The new copy of the type }
var
tType: typePtr; {the new copy of the type}
p1,p2: parameterPtr; {parameter ptrs for copying prototypes}
pPtr: ^parameterPtr; {temp for copying prototypes}
begin {CopyType}
if tp^.kind in [structType,unionType] then
Error(57);
tType := pointer(Malloc(sizeof(typeRecord)));
tType^ := tp^; {copy type record}
tType^.saveDisp := 0;
if tp^.kind = functionType then {copy prototype parameter list}
if tp^.prototyped then begin
p1 := tp^.parameterList;
pPtr := @tType^.parameterList;
while p1 <> nil do begin
p2 := pointer(Malloc(sizeof(parameterRecord)));
p2^ := p1^;
pPtr^ := p2;
pPtr := @p2^.next;
p1 := p1^.next;
end; {while}
end; {if}
CopyType := tType;
end; {CopyType}
function MakeCompositeType {t1, t2: typePtr): typePtr};
{ Make the composite type of two compatible types. }
{ See C17 section 6.2.7. }
{ }
{ parameters: }
{ t1,t2 - the input types (should be compatible) }
{ }
{ returns: pointer to the composite type }
var
compType: typePtr; {the composite type}
tType: typePtr; {temp type}
p1,p2: parameterPtr; {parameter ptrs for handling prototypes}
begin {MakeCompositeType}
compType := t2; {default to t2}
if t1 <> t2 then
if t1^.kind = t2^.kind then begin
if t2^.kind = functionType then {switch fn types if only t1 is prototyped}
if not t2^.prototyped then
if t1^.prototyped then begin
compType := t1;
t1 := t2;
t2 := compType;
end; {if}
{apply recursively for derived types}
if t2^.kind in [arrayType,pointerType,functionType] then begin
tType := MakeCompositeType(t1^.aType,t2^.aType);
if tType <> t2^.aType then begin
compType := CopyType(compType);
compType^.aType := tType;
end; {if}
end; {if}
if t2^.kind = arrayType then {get array size from t1 if needed}
if t2^.size = 0 then
if t1^.size <> 0 then
if t1^.aType^.size = t2^.aType^.size then begin
if compType = t2 then
compType := CopyType(t2);
CompType^.size := t1^.size;
CompType^.elements := t1^.elements;
end; {if}
if t2^.kind = functionType then {compose function parameter types}
if t1^.prototyped and t2^.prototyped then begin
if compType = t2 then
compType := CopyType(t2);
p1 := t1^.parameterList;
p2 := compType^.parameterList;
while (p1 <> nil) and (p2 <> nil) do begin
p2^.parameterType :=
MakeCompositeType(p1^.parameterType,p2^.parameterType);
p1 := p1^.next;
p2 := p2^.next;
end; {while}
end;
end; {if}
MakeCompositeType := compType;
end; {MakeCompositeType}
function MakePascalType {origType: typePtr): typePtr};
{ make a version of a type with the pascal qualifier applied }

View File

@ -28,3 +28,4 @@
{1} c11sassert.c
{1} c11unicode.c
{1} c11uchar.c
{1} c11ternary.c

View File

@ -0,0 +1,57 @@
/*
* Test the ? : operator.
*
* The basic properties tested should hold back to C89,
* but a C11 feature (_Generic) is used to test them.
*/
#define assert_type(e,t) (void)_Generic((e), t:(e))
int main(void) {
int i = 1;
long l = 2;
double d = 3;
struct S {int i;} s = {4};
const struct S t = {5};
const void *cvp = &i;
void *vp = &i;
const int *cip = &i;
volatile int *vip = 0;
int *ip = &i;
const char *ccp = 0;
int (*fp1)() = 0;
int (*fp2)(int (*)[40]) = 0;
int (*fp3)(int (*)[]) = 0;
assert_type(1?i:l, long);
assert_type(1?d:i, double);
assert_type(1?s:t, struct S);
1?(void)2:(void)3;
assert_type(1?ip:ip, int *);
assert_type(1?ip:cip, const int *);
assert_type(1?cip:ip, const int *);
assert_type(1?0:ip, int *);
assert_type(0?0LL:ip, int *);
assert_type(1?(void*)0:ip, int *);
assert_type(1?cip:0, const int *);
assert_type(1?cip:0LL, const int *);
assert_type(1?cip:(char)0.0, const int *);
assert_type(1?cip:(void*)0, const int *);
assert_type(1?(void*)(void*)0:ip, void *);
assert_type(1?(void*)ip:ip, void *);
assert_type(1?cip:(void*)(void*)0, const void *);
assert_type(1?(void*)ip:cip, const void *);
assert_type(1?main:main, int(*)(void));
assert_type(1?main:0, int(*)(void));
assert_type(1?(const void*)cip:(void*)ip, const void *);
assert_type(1?cvp:cip, const void *);
assert_type(1?vip:0, volatile int *);
assert_type(1?cip:vip, const volatile int *);
assert_type(1?vp:ccp, const void *);
assert_type(1?ip:cip, const int *);
assert_type(1?vp:ip, void *);
assert_type(1?fp1:fp2, int (*)(int (*)[40]));
assert_type(1?fp2:fp3, int (*)(int (*)[40]));
assert_type(1?fp2:0, int (*)(int (*)[40]));
assert_type(1?fp2:(void*)0, int (*)(int (*)[40]));
}

View File

@ -0,0 +1,27 @@
/* Deviance Test 7.8.0.1: Ensure invalid operand types for ?: are detected */
int printf(const char *, ...);
int main(void) {
int i = 1;
struct S {int i;} s = {4};
int *ip = &i;
long *lp = 0;
const int *cip = &i;
/* each statement below should give an error */
1 ? i : s;
1 ? s : i;
1 ? i : (void)0;
1 ? ip : lp;
/* these are illegal in standard C, but allowed by loose type checks */
#pragma ignore 24
1 ? main : (void*)(void*)0;
1 ? &ip : &cip;
/* should give an error, but currently does not in ORCA/C */
1 ? cip : (char)+0.0;
printf ("Failed Deviance Test 7.8.0.1\n");
}

View File

@ -51,6 +51,7 @@
{1} D7.6.6.1.CC
{1} D7.6.7.1.CC
{1} D7.6.8.1.CC
{1} D7.8.0.1.CC
{1} D8.7.0.1.CC
{1} D8.8.0.1.CC
{1} D9.2.0.1.CC

View File

@ -584,7 +584,7 @@ First, setting bit 5 causes pointer assignments that discard type qualifiers to
Second, setting bit 5 causes type compatibility checks involving function pointers to ignore the prototyped parameter types. If bit 5 is clear, the prototyped parameter types (if available) must be compatible.
Third, setting bit 5 causes certain comparisons involving pointers to be permitted even though they violate constraints specified in the C standards. If bit 5 is clear, the rules in the standards will be followed strictly.
Third, setting bit 5 causes certain comparisons involving pointers, as well as certain uses of the ? : operator with pointer operands, to be permitted even though they violate constraints specified in the C standards. If bit 5 is clear, the rules in the standards will be followed strictly.
Fourth, setting bit 5 causes ORCA/C to treat basic types with the same representation as mutually compatible. This affects the following pairs of types: short and int, unsigned short and unsigned int, char and unsigned char. Historically, ORCA/C essentially treated each of these pairs as being the same type, so it never reported type conflicts between them. If bit 5 is set, it will continue to do so. If bit 5 is clear, it will treat all of the above types as distinct and mutually incompatible, as specified by the C standards.
@ -1825,6 +1825,10 @@ int foo(int[42]);
(Devin Reade)
189. Some combinations of operand types were not properly supported for the ? : operator. This could result in a spurious error, or could cause the ? : expression to have an incorrect type. One case where a spurious error would be produced is for expressions like i?0:p, where p is a pointer.
(Jay Krell, Devin Reade)
-- Bugs from C 2.1.0 that have been fixed -----------------------------------
1. In some situations, fread() reread the first 1K or so of the file.