From 7848e50218108dca6f3e7a80d48f90ce48d5d168 Mon Sep 17 00:00:00 2001 From: Stephen Heumann Date: Fri, 10 Sep 2021 21:00:23 -0500 Subject: [PATCH] Implement stricter type checks for comparisons. These rules are used if loose type checks are disabled. They are intended to strictly implement the constraints in C17 sections 6.5.9 and 6.5.10. This patch also fixes a bug where object pointer comparisons to "const void *" should be permitted but were not. --- Expression.pas | 45 ++++++++++++++++++++++++++++++++++++++------- cc.notes | 6 ++++-- 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/Expression.pas b/Expression.pas index d12bdb1..75e7062 100644 --- a/Expression.pas +++ b/Expression.pas @@ -3337,28 +3337,59 @@ var end; {FunctionCall} - procedure CompareCompatible (var t1,t2: typePtr); + procedure CompareCompatible (var t1,t2: typePtr; equality: boolean); { Make sure that it is legal to compare t1 to t2 } + { } + { parameters: } + { t1,t2 - the types to compare } + { equality - is this for an (in)equality comparison? } begin {CompareCompatible} if (t1^.kind = functionType) or (t2^.kind = functionType) then begin if not CompTypes(t1, t2) then + Error(47) + else if not looseTypeChecks and not equality then Error(47); end {if} else if t1^.kind in [pointerType,arrayType] then begin if t2^.kind in [pointerType,arrayType] then begin - if (t1^.ptype = voidPtr) or (t2^.ptype = voidPtr) then - else if not CompTypes(t1^.ptype, t2^.ptype) then + if CompTypes(t1^.ptype, t2^.ptype) then begin + if not looseTypeChecks and not equality then + if t1^.ptype^.kind = functionType then + Error(47); + end {if} + else if (t1^.ptype^.kind=scalarType) and (t1^.ptype^.basetype=cgVoid) + then begin + if not looseTypeChecks then begin + if not equality then + Error(47) + else if (not tlastwasconst) or (tlastconst <> 0) then + if t2^.ptype^.kind = functionType then + Error(47); + end {if} + end {else if} + else if (t2^.ptype^.kind=scalarType) and (t2^.ptype^.basetype=cgVoid) + then begin + if not looseTypeChecks then begin + if not equality then + Error(47) + else if (not lastwasconst) or (lastconst <> 0) then + if t1^.ptype^.kind = functionType then + Error(47); + end {if} + end {else if} + else Error(47); t2 := ulongPtr; end {if} - else if (not lastwasconst) or (lastconst <> 0) then + else if (not lastwasconst) or (lastconst <> 0) + or (not equality and not looseTypeChecks) then Error(47); t1 := ulongPtr; end {if} else if expressionType^.kind in [pointerType,arrayType] then begin - if (not tlastwasconst) or (tlastconst <> 0) then + if (not equality) or (not tlastwasconst) or (tlastconst <> 0) then Error(47); t2 := ulongPtr; end; {else if} @@ -4269,7 +4300,7 @@ case tree^.token.kind of tlastwasconst := lastwasconst; tlastconst := lastconst; GenerateCode(tree^.right); - CompareCompatible(ltype, expressionType); + CompareCompatible(ltype, expressionType, true); if tree^.token.kind = eqeqop then Gen0t(pc_equ, UsualBinaryConversions(lType)) else @@ -4284,7 +4315,7 @@ case tree^.token.kind of GenerateCode(tree^.left); lType := expressionType; GenerateCode(tree^.right); - CompareCompatible(ltype, expressionType); + CompareCompatible(ltype, expressionType, false); if tree^.token.kind = lteqop then Gen0t(pc_leq, UsualBinaryConversions(lType)) else if tree^.token.kind = gteqop then diff --git a/cc.notes b/cc.notes index 2af217c..ed66314 100644 --- a/cc.notes +++ b/cc.notes @@ -475,13 +475,15 @@ Bit 2 (a value of 4) controls whether spurious tokens are allowed after an #endi Bit 4 (a value of 16) controls whether ORCA/C follows C99-style rules for declaration placement and block scopes. See "New Language Features," above. -Bit 5 (a value of 32) controls whether type compatibility checks should strictly follow the C standards, or whether looser rules should be used in certain cases. If this bit is set, the looser rules will be followed, matching ORCA/C's historical behavior. Bit 5 is currently set by default, but new code should avoid relying on this. There are three specific situations where bit 5 currently has an effect: +Bit 5 (a value of 32) controls whether type compatibility checks should strictly follow the C standards, or whether looser rules should be used in certain cases. If this bit is set, the looser rules will be followed, matching ORCA/C's historical behavior. Bit 5 is currently set by default, but new code should avoid relying on this. There are four specific situations where bit 5 currently has an effect: First, setting bit 5 causes pointer assignments that discard type qualifiers to be allowed. For example, this affects an assignment from an expression of type "const int *" to a variable of type "int *", because it discards the "const" qualifier from the type pointed to. These assignments are prohibited by the C standards, but ORCA/C historically allowed them. If bit 5 is set it will still allow them, but if bit 5 is clear it will give an error. 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 the types "char" and "unsigned char" to be treated as compatible with each other for most purposes. These types have the same representation in ORCA/C, but the C standards specify that they are nonetheless two distinct types and are not mutually compatible. Therefore, any standard-conforming C compiler should produce a diagnostic message if these two types or types derived from them are used in a situation where the types are required to be compatible, as in the following example: +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. + +Fourth, setting bit 5 causes the types "char" and "unsigned char" to be treated as compatible with each other for most purposes. These types have the same representation in ORCA/C, but the C standards specify that they are nonetheless two distinct types and are not mutually compatible. Therefore, any standard-conforming C compiler should produce a diagnostic message if these two types or types derived from them are used in a situation where the types are required to be compatible, as in the following example: unsigned char uc; char *p = &uc; /* &uc has type unsigned char *, incompatible with char *. */