Skip to content

Commit 3caa72a

Browse files
authored
Support to disable Checked C type checking (checkedc#874)
The Checked C conversion tool is built using clang as a library. We want the Checked C conversion tool to be usable interactively. In this scenario, the conversion tool will be run and introduce some annotations. The programmer may then introduce additional annotations and re-run the tool. This means that the conversion tool needs to support partially annotated code. However, partially annotated code could fail type-checking and consequently, the generated clang AST might not be complete, impeding the tool. This changes adds a new flag -fcheckedc-convert-tool to the Checked C clang compiler that suppresses diagnostics due to partially annotated Checked C code. This includes includes type checking errors cause by checked and unchecked pointers mismatches, missing initializers, and missing bounds annotations. As an example, suppose want to convert the following code: void foo(_Ptr<int> z) { int *x = z; } Current clang emits an error: error: initializing 'int *' with an expression of an incompatible type '_Ptr<int>' int *x = z; and the AST produced is incomplete: ./clang -cc1 -ast-dump foo.c: `-FunctionDecl 0x10f2fbd0 </home/machiry/checkedc/dd.c:1:1, line:3:1> line:1:6 foo 'void (_Ptr<int>)' |-ParmVarDecl 0x10f2faf8 <col:10, col:20> col:20 used z '_Ptr<int>' `-CompoundStmt 0x10f301c8 <col:23, line:3:1> `-DeclStmt 0x10f301b0 <line:2:3, col:13> `-VarDecl 0x10f2fd08 <col:3, col:8> col:8 invalid x 'int *' 1 error generated. When the flag is turned on, diagnostics that might be caused by partially annotated programs are suppressed at the point of diagnostic message generation. clang doesn't really differentiate whether the cause of an error is syntactic, type checking, or some other semantic checking problem. It throws away expressions when errors are found, to avoid spurious or confusing error messages. This means that suppressing the errors at the point of generation is the best way to present a complete AST to the conversion tool. The flag should not be turned on for code generation because the resulting code may be incorrect. It is only meant to be turned on when ASTs are being created.
1 parent d3473b1 commit 3caa72a

File tree

9 files changed

+149
-36
lines changed

9 files changed

+149
-36
lines changed

clang/include/clang/Basic/LangOptions.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ LANGOPT(CPlusPlus17 , 1, 0, "C++17")
9494
LANGOPT(CPlusPlus2a , 1, 0, "C++2a")
9595
LANGOPT(ObjC , 1, 0, "Objective-C")
9696
LANGOPT(CheckedC , 1, 0, "Checked C extension")
97+
LANGOPT(CheckedCConverter , 1, 0, "Automated Checked C converter mode")
9798
BENIGN_LANGOPT(ObjCDefaultSynthProperties , 1, 0,
9899
"Objective-C auto-synthesized properties")
99100
BENIGN_LANGOPT(EncodeExtendedBlockSig , 1, 0,

clang/include/clang/Driver/Options.td

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -800,7 +800,9 @@ def fbuiltin_module_map : Flag <["-"], "fbuiltin-module-map">, Group<f_Group>,
800800
def fcheckedc_extension : Flag<["-"], "fcheckedc-extension">, Group<f_Group>, Flags<[CC1Option]>,
801801
HelpText<"Accept Checked C extension">;
802802
def fno_checkedc_extension : Flag<["-"], "fno-checkedc-extension">, Group<f_Group>, Flags<[CC1Option]>,
803-
HelpText<"Do ont accept Checked C extension">;
803+
HelpText<"Do not accept Checked C extension">;
804+
def fcheckedc_convert_tool : Flag<["-"], "fcheckedc-convert-tool">, Group<f_Group>, Flags<[CC1Option]>,
805+
HelpText<"Enable Checked C converter tool mode (supposed to be run by tools that needs only AST)">;
804806
def fdump_extracted_comparison_facts : Flag<["-"], "fdump-extracted-comparison-facts">, Group<f_Group>, Flags<[CC1Option]>,
805807
HelpText<"Dump extracted comparison facts">;
806808
def fdump_widened_bounds : Flag<["-"], "fdump-widened-bounds">, Group<f_Group>, Flags<[CC1Option]>,

clang/lib/AST/ASTContext.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8804,7 +8804,9 @@ QualType ASTContext::mergeFunctionTypes(QualType lhs, QualType rhs,
88048804
// For unchecked return types, a return with
88058805
// bounds is compatible with a return without bounds.
88068806
// The merged type includes the bounds.
8807-
if (!retType->isUncheckedPointerType())
8807+
// Ignore the error if we do not want to consider checked pointers.
8808+
if (!retType->isUncheckedPointerType() &&
8809+
!getLangOpts().CheckedCConverter)
88088810
return QualType();
88098811
if (!lReturnAnnots.IsEmpty() && rReturnAnnots.IsEmpty()) {
88108812
ReturnAnnots = lReturnAnnots;
@@ -8858,7 +8860,9 @@ QualType ASTContext::mergeFunctionTypes(QualType lhs, QualType rhs,
88588860
// For unchecked parameter types, a parameter with
88598861
// bounds is compatible with a parameter without bounds.
88608862
// The merged type includes the bounds.
8861-
if (!paramType->isUncheckedPointerType())
8863+
// Ignored the error if we do not want to consider checked pointers.
8864+
if (!paramType->isUncheckedPointerType() &&
8865+
!getLangOpts().CheckedCConverter)
88628866
return QualType();
88638867
if (!lBounds.IsEmpty() && rBounds.IsEmpty()) {
88648868
bounds.push_back(lBounds);

clang/lib/Driver/ToolChains/Clang.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4816,6 +4816,7 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
48164816
Args.AddLastArg(CmdArgs, options::OPT_fcheckedc_extension);
48174817
Args.AddLastArg(CmdArgs, options::OPT_fno_checkedc_extension);
48184818
Args.AddLastArg(CmdArgs, options::OPT_fdump_inferred_bounds);
4819+
Args.AddLastArg(CmdArgs, options::OPT_fcheckedc_convert_tool);
48194820
Args.AddLastArg(CmdArgs, options::OPT_finject_verifier_calls);
48204821
Args.AddLastArg(CmdArgs, options::OPT_funchecked_pointers_dynamic_check);
48214822

clang/lib/Frontend/CompilerInvocation.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2634,6 +2634,10 @@ static void ParseLangArgs(LangOptions &Opts, ArgList &Args, InputKind IK,
26342634
} else
26352635
Opts.CheckedC = true;
26362636
}
2637+
2638+
if (Args.hasArg(OPT_fcheckedc_convert_tool))
2639+
Opts.CheckedCConverter = true;
2640+
26372641
if (Args.hasArg(OPT_fno_checkedc_extension))
26382642
Opts.CheckedC = false;
26392643

clang/lib/Sema/SemaBounds.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2093,6 +2093,12 @@ namespace {
20932093
BoundsCheckKind CheckKind,
20942094
CheckedScopeSpecifier CSS,
20952095
EquivExprSets *EquivExprs) {
2096+
2097+
// If we are running to Checked C converter (AST only) tool, then disable
2098+
// bounds checking.
2099+
if (S.getLangOpts().CheckedCConverter)
2100+
return;
2101+
20962102
ProofFailure Cause;
20972103
ProofResult Result;
20982104
ProofStmtKind ProofKind;
@@ -5385,6 +5391,9 @@ namespace {
53855391
!ToType->isFunctionPointerType())
53865392
return;
53875393

5394+
if (S.getLangOpts().CheckedCConverter)
5395+
return;
5396+
53885397
// Skip lvalue-to-rvalue casts because they preserve types (except that
53895398
// qualifers are removed). The lvalue type should be a checked pointer
53905399
// type too.

clang/lib/Sema/SemaDecl.cpp

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12656,31 +12656,33 @@ void Sema::ActOnUninitializedDecl(Decl *RealDecl) {
1265612656
if (InCheckedScope && Var->hasInteropTypeExpr())
1265712657
Ty = Var->getInteropType();
1265812658

12659-
if (Ty->isCheckedPointerPtrType())
12659+
if (Ty->isCheckedPointerPtrType() && !getLangOpts().CheckedCConverter)
1266012660
Diag(Var->getLocation(), diag::err_initializer_expected_for_ptr)
1266112661
<< Var;
12662-
else if (B && !B->isInvalid() && !B->isUnknown() && !Ty->isArrayType())
12662+
else if (B && !B->isInvalid() && !B->isUnknown() &&
12663+
!Ty->isArrayType() && !getLangOpts().CheckedCConverter)
1266312664
Diag(Var->getLocation(), diag::err_initializer_expected_with_bounds)
1266412665
<< Var;
1266512666

1266612667
// An unchecked pointer in a checked scope with a bounds expression must
1266712668
// be initialized
1266812669
if (Ty->isUncheckedPointerType() && InCheckedScope &&
12669-
Var->hasBoundsExpr())
12670+
Var->hasBoundsExpr() && !getLangOpts().CheckedCConverter)
1267012671
Diag(Var->getLocation(),
1267112672
diag::err_initializer_expected_for_unchecked_pointer)
1267212673
<< Var;
1267312674

1267412675
// An integer with a bounds expression must be initialized
12675-
if (Ty->isIntegerType() && Var->hasBoundsExpr())
12676+
if (Ty->isIntegerType() && Var->hasBoundsExpr() &&
12677+
!getLangOpts().CheckedCConverter)
1267612678
Diag(Var->getLocation(),
1267712679
diag::err_initializer_expected_for_integer)
1267812680
<< Var;
1267912681

1268012682
// struct/union and array with checked pointer members must have
1268112683
// initializers.
1268212684
// array with checked ptr element
12683-
if (Ty->isArrayType()) {
12685+
if (Ty->isArrayType() && !getLangOpts().CheckedCConverter) {
1268412686
// if this is an array type, check the element type of the array,
1268512687
// potentially with type qualifiers missing
1268612688
if (Type::HasCheckedValue == Ty->getPointeeOrArrayElementType()->
@@ -12689,7 +12691,7 @@ void Sema::ActOnUninitializedDecl(Decl *RealDecl) {
1268912691
<< Var;
1269012692
}
1269112693
// RecordType(struct/union) with checked pointer member
12692-
if (Ty->isRecordType()) {
12694+
if (Ty->isRecordType() && !getLangOpts().CheckedCConverter) {
1269312695
const RecordType *RT = Ty->getAs<RecordType>();
1269412696
switch (RT->containsCheckedValue(InCheckedScope)) {
1269512697
default:
@@ -13094,7 +13096,7 @@ void Sema::CheckCompleteVariableDeclaration(VarDecl *var) {
1309413096
CurInitSegLoc));
1309513097
}
1309613098

13097-
if (getLangOpts().CheckedC)
13099+
if (getLangOpts().CheckedC && !getLangOpts().CheckedCConverter)
1309813100
CheckTopLevelBoundsDecls(var);
1309913101

1310013102
// All the following checks are C++ only.
@@ -14855,7 +14857,7 @@ Decl *Sema::ActOnFinishFunctionBody(Decl *dcl, Stmt *Body,
1485514857
// meant to pop the context added in ActOnStartOfFunctionDef().
1485614858
ExitFunctionBodyRAII ExitRAII(*this, isLambdaCallOperator(FD));
1485714859

14858-
if (getLangOpts().CheckedC)
14860+
if (getLangOpts().CheckedC && !getLangOpts().CheckedCConverter)
1485914861
CheckFunctionBodyBoundsDecls(FD, Body);
1486014862

1486114863
if (FD) {

clang/lib/Sema/SemaExpr.cpp

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4786,7 +4786,8 @@ Sema::CreateBuiltinArraySubscriptExpr(Expr *Base, SourceLocation LLoc,
47864786
BaseExpr = LHSExp;
47874787
IndexExpr = RHSExp;
47884788
// Array subscripting not allowed on ptr<T> values
4789-
if (PTy->getKind() == CheckedPointerKind::Ptr) {
4789+
if (PTy->getKind() == CheckedPointerKind::Ptr &&
4790+
!getLangOpts().CheckedCConverter) {
47904791
return ExprError(Diag(LLoc, diag::err_typecheck_ptr_subscript)
47914792
<< LHSTy << LHSExp->getSourceRange() << RHSExp->getSourceRange());
47924793
}
@@ -4808,7 +4809,8 @@ Sema::CreateBuiltinArraySubscriptExpr(Expr *Base, SourceLocation LLoc,
48084809
BaseExpr = RHSExp;
48094810
IndexExpr = LHSExp;
48104811
// Array subscripting not allowed on ptr<T> values
4811-
if (PTy->getKind() == CheckedPointerKind::Ptr) {
4812+
if (PTy->getKind() == CheckedPointerKind::Ptr &&
4813+
!getLangOpts().CheckedCConverter) {
48124814
return ExprError(Diag(LLoc, diag::err_typecheck_ptr_subscript)
48134815
<< RHSTy << LHSExp->getSourceRange() << RHSExp->getSourceRange());
48144816
}
@@ -7161,7 +7163,7 @@ static QualType checkConditionalPointerCompatibility(Sema &S, ExprResult &LHS,
71617163
incompatibleCheckedPointer = resultKind != CheckedPointerKind::Unchecked && CompositeTy.isNull();
71627164
}
71637165
else if (lhsKind == CheckedPointerKind::Unchecked) {
7164-
// The rhs must be a checked ponter type. The least upper bound is determined
7166+
// The rhs must be a checked pointer type. The least upper bound is determined
71657167
// as follows:
71667168
// Unchecked ^ Array = Array
71677169
// Unchecked ^ NtArray = Array
@@ -7199,7 +7201,7 @@ static QualType checkConditionalPointerCompatibility(Sema &S, ExprResult &LHS,
71997201
// Must have different kinds of checked pointers (_Ptr vs.
72007202
// _Array_ptr or _Nt_Array_ptr). Implicit conversions between these
72017203
// kinds of pointers are not allowed.
7202-
incompatibleCheckedPointer = true;
7204+
incompatibleCheckedPointer = !S.getLangOpts().CheckedCConverter;
72037205
// _Array_ptr is less likely to cause spurious downstream warnings.
72047206
resultKind = CheckedPointerKind::Array;
72057207
}
@@ -7363,7 +7365,7 @@ static bool checkUncheckedPointerIntegerMismatch(Sema &S, ExprResult &Int,
73637365
return false;
73647366

73657367
const PointerType *ptrTy = PointerExpr->getType()->getAs<PointerType>();
7366-
if (ptrTy->isChecked()) {
7368+
if (ptrTy->isChecked() && !S.getLangOpts().CheckedCConverter) {
73677369
return false;
73687370
}
73697371

@@ -8263,22 +8265,29 @@ checkPointerTypesForAssignment(Sema &S, QualType LHSType, QualType RHSType) {
82638265
// - Allow implicit conversions from any kind of pointer to _Ptr
82648266
// or _Array_ptr
82658267

8266-
if (rhkind != CheckedPointerKind::Unchecked &&
8267-
lhkind == CheckedPointerKind::Unchecked)
8268-
return Sema::Incompatible;
8268+
if (!S.getLangOpts().CheckedCConverter) {
8269+
if (rhkind != CheckedPointerKind::Unchecked &&
8270+
lhkind == CheckedPointerKind::Unchecked)
8271+
return Sema::Incompatible;
82698272

8270-
if (lhkind == CheckedPointerKind::NtArray &&
8271-
rhkind != CheckedPointerKind::NtArray)
8272-
return Sema::Incompatible;
8273+
if (lhkind == CheckedPointerKind::NtArray &&
8274+
rhkind != CheckedPointerKind::NtArray)
8275+
return Sema::Incompatible;
8276+
}
82738277

82748278
// C99 6.5.16.1p1 (constraint 3): both operands are pointers to qualified or
82758279
// unqualified versions of compatible types, ...
82768280
QualType ltrans = QualType(lhptee, 0), rtrans = QualType(rhptee, 0);
82778281
if (!S.Context.pointeeTypesAreAssignable(ltrans, rtrans)) {
82788282
// None of the language extensions below are allowed for pointers
82798283
// that are checked pointers or that contain checked types.
8280-
if (LHSType->isOrContainsCheckedType() || RHSType->isOrContainsCheckedType())
8281-
return Sema::Incompatible;
8284+
if (LHSType->isOrContainsCheckedType() ||
8285+
RHSType->isOrContainsCheckedType()) {
8286+
// If ignoring checked pointers are enabled then assignments containing
8287+
// checked pointers is always compatible.
8288+
return S.getLangOpts().CheckedCConverter ? Sema::Compatible :
8289+
Sema::Incompatible;
8290+
}
82828291

82838292
// Check if the pointee types are compatible ignoring the sign.
82848293
// We explicitly check for char so that we catch "char" vs
@@ -9772,19 +9781,21 @@ QualType Sema::CheckRemainderOperands(
97729781
/// Diagnose invalid arithmetic on two void pointers.
97739782
static void diagnoseArithmeticOnTwoVoidPointers(Sema &S, SourceLocation Loc,
97749783
Expr *LHSExpr, Expr *RHSExpr) {
9775-
bool isCheckedPointerType = LHSExpr->getType()->isCheckedPointerType() ||
9776-
RHSExpr->getType()->isCheckedPointerType();
9777-
S.Diag(Loc, S.getLangOpts().CPlusPlus || isCheckedPointerType
9778-
? diag::err_typecheck_pointer_arith_void_type
9779-
: diag::ext_gnu_void_ptr)
9780-
<< 1 /* two pointers */ << LHSExpr->getSourceRange()
9781-
<< RHSExpr->getSourceRange();
9784+
bool isCheckedPointerType = (LHSExpr->getType()->isCheckedPointerType() ||
9785+
RHSExpr->getType()->isCheckedPointerType()) &&
9786+
!S.getLangOpts().CheckedCConverter;
9787+
S.Diag(Loc, S.getLangOpts().CPlusPlus || isCheckedPointerType
9788+
? diag::err_typecheck_pointer_arith_void_type
9789+
: diag::ext_gnu_void_ptr)
9790+
<< 1 /* two pointers */ << LHSExpr->getSourceRange()
9791+
<< RHSExpr->getSourceRange();
97829792
}
97839793

97849794
/// Diagnose invalid arithmetic on a void pointer.
97859795
static void diagnoseArithmeticOnVoidPointer(Sema &S, SourceLocation Loc,
97869796
Expr *Pointer) {
9787-
bool isCheckedPointerType = Pointer->getType()->isCheckedPointerType();
9797+
bool isCheckedPointerType = Pointer->getType()->isCheckedPointerType() &&
9798+
!S.getLangOpts().CheckedCConverter;
97889799
S.Diag(Loc, S.getLangOpts().CPlusPlus || isCheckedPointerType
97899800
? diag::err_typecheck_pointer_arith_void_type
97909801
: diag::ext_gnu_void_ptr)
@@ -9880,7 +9891,7 @@ static bool checkArithmeticOpPointerOperand(Sema &S, SourceLocation Loc,
98809891

98819892
if (!ResType->isAnyPointerType()) return true;
98829893

9883-
if (ResType->isCheckedPointerPtrType()) {
9894+
if (ResType->isCheckedPointerPtrType() && !S.getLangOpts().CheckedCConverter) {
98849895
diagnoseArithmeticOnPtrPointerType(S, Loc, Operand);
98859896
return false;
98869897
}
@@ -9942,8 +9953,9 @@ static bool checkArithmeticBinOpPointerOperands(Sema &S, SourceLocation Loc,
99429953
else diagnoseArithmeticOnTwoVoidPointers(S, Loc, LHSExpr, RHSExpr);
99439954

99449955
return !(S.getLangOpts().CPlusPlus ||
9945-
LHSExpr->getType()->isCheckedPointerType() ||
9946-
RHSExpr->getType()->isCheckedPointerType());
9956+
((LHSExpr->getType()->isCheckedPointerType() ||
9957+
RHSExpr->getType()->isCheckedPointerType()) &&
9958+
!S.getLangOpts().CheckedCConverter));
99479959
}
99489960

99499961
bool isLHSFuncPtr = isLHSPointer && LHSPointeeTy->isFunctionType();
@@ -9955,7 +9967,7 @@ static bool checkArithmeticBinOpPointerOperands(Sema &S, SourceLocation Loc,
99559967
else diagnoseArithmeticOnTwoFunctionPointers(S, Loc, LHSExpr, RHSExpr);
99569968

99579969
// We don't have to check if the function pointers are checked. Only _Ptrs to
9958-
// function types are allowd and arithmetic on _Ptrs is covered by another
9970+
// function types are allowed and arithmetic on _Ptrs is covered by another
99599971
// diagnostic.
99609972
return !S.getLangOpts().CPlusPlus;
99619973
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// This is a test case to check support for AST generation in
2+
// the presence of invalid checked pointer usage.
3+
// https://github.com/microsoft/checkedc-clang/pull/847#issuecomment-652375065
4+
//
5+
// This test checks that the compiler does not generate any error while trying
6+
// to generate AST for programs even when the Checked C type checking fails.
7+
//
8+
// RUN: %clang -cc1 -verify -fcheckedc-convert-tool %s
9+
// expected-no-diagnostics
10+
11+
struct st1 {
12+
_Array_ptr<int> q;
13+
};
14+
15+
int bar(_Array_ptr<int> q : count(l), unsigned l) {
16+
unsigned i;
17+
for (i=0; i<l; i++) {
18+
q[i] = 0;
19+
}
20+
return 0;
21+
}
22+
23+
_Array_ptr<int> baz(_Ptr<int> q, unsigned z) : count(5) {
24+
int *p;
25+
return p;
26+
}
27+
28+
int zzz(int *o, unsigned l) {
29+
return 0;
30+
}
31+
32+
void fpt(_Ptr<int(_Array_ptr<int> arr : count(i), int i)> j) {
33+
return;
34+
}
35+
36+
int foo(void) {
37+
// No initializer.
38+
_Array_ptr<int> r : count(5);
39+
_Nt_array_ptr<char> z;
40+
_Array_ptr<_Ptr<int>> n;
41+
struct st1 o;
42+
char *l;
43+
_Ptr<int> q;
44+
_Ptr<int(_Array_ptr<int> arr : count(i), int i)> j;
45+
int *p;
46+
void *v;
47+
// different types of assignments.
48+
q = p;
49+
r = p;
50+
p = r;
51+
q = p;
52+
r = p;
53+
l = z;
54+
z = l;
55+
r = z;
56+
n = p !=0 ? p : q;
57+
n = p;
58+
p = n;
59+
v = n;
60+
n = v;
61+
*n = v;
62+
*n = p;
63+
*n = q;
64+
*n = r;
65+
r = *n;
66+
o.q = p;
67+
n = o.q;
68+
// assigning ptr to array.
69+
p = baz(q, 0);
70+
// assigning unchecked pointer to checked ptr.
71+
p = baz(p, 0);
72+
// Function pointers.
73+
fpt(&bar);
74+
fpt(&zzz);
75+
fpt(&baz);
76+
j = baz;
77+
return 0;
78+
}

0 commit comments

Comments
 (0)