Skip to content

Commit 12fd211

Browse files
Treat the bounds of _Nt_checked as one less than the written length (#653)
The Checked C bounds for constant sized `_Nt_checked` arrays is one less than the written size because the written size counts the null terminator while the Checked C bounds do not. To make this change, the declared bounds of constant size arrays must be added to the array bounds inference information after pointer type constraint solving. This lets `_Checked` and `_Nt_checked` array be treated differently. This also fixes an error in `canBeNtArray` that caused `ensureNtCorrect `to add incorrect constraints. The function checked if a type was a `PointerType` or `ArrayType`, but it could be a wrapper type around one of these types. For example, `ParenType` and `DecayedType` instances were both treated as not pointers or arrays even if the type they wrapped was a pointer or array type. Fixes #396
1 parent f9d1cbb commit 12fd211

File tree

10 files changed

+183
-40
lines changed

10 files changed

+183
-40
lines changed

clang/include/clang/3C/AVarBoundsInfo.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,8 @@ class AVarBoundsInfo {
197197
bool isValidBoundVariable(clang::Decl *D);
198198

199199
void insertDeclaredBounds(clang::Decl *D, ABounds *B);
200+
void insertDeclaredBounds(BoundsKey BK, ABounds *B);
201+
200202
bool mergeBounds(BoundsKey L, BoundsPriority P, ABounds *B);
201203
bool removeBounds(BoundsKey L, BoundsPriority P = Invalid);
202204
bool replaceBounds(BoundsKey L, BoundsPriority P, ABounds *B);
@@ -290,6 +292,8 @@ class AVarBoundsInfo {
290292
// If yes, provide the index of the parameter.
291293
bool isFuncParamBoundsKey(BoundsKey BK, unsigned &PIdx);
292294

295+
void addConstantArrayBounds(ProgramInfo &I);
296+
293297
private:
294298
friend class AvarBoundsInference;
295299
friend class CtxSensitiveBoundsKeyHandler;

clang/include/clang/3C/ConstraintVariables.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,6 @@ class PointerVariableConstraint : public ConstraintVariable {
410410

411411
public:
412412
std::string getTy() const { return BaseType; }
413-
bool getArrPresent() const;
414413
// Check if the outermost pointer is an unsized array.
415414
bool isTopAtomUnsizedArr() const;
416415
// Check if any of the pointers is either a sized or unsized arr.
@@ -532,6 +531,10 @@ class PointerVariableConstraint : public ConstraintVariable {
532531
bool hasArr(const EnvironmentMap &E, int AIdx = -1) const override;
533532
bool hasNtArr(const EnvironmentMap &E, int AIdx = -1) const override;
534533

534+
bool isNtConstantArr(const EnvironmentMap &E) const;
535+
bool isConstantArr() const;
536+
unsigned long getConstantArrSize() const;
537+
535538
void equateArgumentConstraints(ProgramInfo &I) override;
536539

537540
bool isPartOfFunctionPrototype() const { return PartOfFuncPrototype; }

clang/lib/3C/3C.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,12 @@ bool _3CInterface::solveConstraints() {
580580
dumpConstraintOutputJson(FINAL_OUTPUT_SUFFIX, GlobalProgramInfo);
581581

582582
if (_3COpts.AllTypes) {
583+
// Add declared bounds for all constant sized arrays. This needs to happen
584+
// after constraint solving because the bound added depends on whether the
585+
// array is NTARR or ARR.
586+
GlobalProgramInfo.getABoundsInfo().addConstantArrayBounds(
587+
GlobalProgramInfo);
588+
583589
if (DebugArrSolver)
584590
GlobalProgramInfo.getABoundsInfo().dumpAVarGraph(
585591
"arr_bounds_initial.dot");

clang/lib/3C/AVarBoundsInfo.cpp

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -640,10 +640,7 @@ bool AVarBoundsInfo::isValidBoundVariable(clang::Decl *D) {
640640
return false;
641641
}
642642

643-
void AVarBoundsInfo::insertDeclaredBounds(clang::Decl *D, ABounds *B) {
644-
assert(isValidBoundVariable(D) && "Declaration not a valid bounds variable");
645-
BoundsKey BK;
646-
tryGetVariable(D, BK);
643+
void AVarBoundsInfo::insertDeclaredBounds(BoundsKey BK, ABounds *B) {
647644
if (B != nullptr) {
648645
// If there is already bounds information, release it.
649646
removeBounds(BK);
@@ -656,6 +653,13 @@ void AVarBoundsInfo::insertDeclaredBounds(clang::Decl *D, ABounds *B) {
656653
}
657654
}
658655

656+
void AVarBoundsInfo::insertDeclaredBounds(clang::Decl *D, ABounds *B) {
657+
assert(isValidBoundVariable(D) && "Declaration not a valid bounds variable");
658+
BoundsKey BK;
659+
tryGetVariable(D, BK);
660+
insertDeclaredBounds(BK, B);
661+
}
662+
659663
bool AVarBoundsInfo::tryGetVariable(clang::Decl *D, BoundsKey &R) {
660664
bool RetVal = false;
661665
if (isValidBoundVariable(D)) {
@@ -1471,3 +1475,35 @@ std::set<BoundsKey> AVarBoundsInfo::getCtxSensFieldBoundsKey(Expr *E,
14711475
}
14721476
return Ret;
14731477
}
1478+
1479+
// Adds declared bounds for all constant sized arrays. This needs to happen
1480+
// after constraint solving because the bounds for a _Nt_checked array and a
1481+
// _Checked array are different even if they are written with the same length.
1482+
// The Checked C bounds for a _Nt_checked array do not include the null
1483+
// terminator, but the length as written in the source code does.
1484+
void AVarBoundsInfo::addConstantArrayBounds(ProgramInfo &I) {
1485+
for (auto VarEntry : I.getVarMap()) {
1486+
if (auto *VarPCV = dyn_cast<PVConstraint>(VarEntry.second)) {
1487+
if (VarPCV->hasBoundsKey() && VarPCV->isConstantArr()) {
1488+
// Lookup the declared size of the array. This is known because it is
1489+
// written in the source and was stored during constraint generation.
1490+
unsigned int ConstantCount = VarPCV->getConstantArrSize();
1491+
1492+
// Check if this array solved to NTARR. If it did, subtract one from the
1493+
// length to account for the null terminator.
1494+
const EnvironmentMap &Env = I.getConstraints().getVariables();
1495+
if (VarPCV->isNtConstantArr(Env)) {
1496+
assert("Size zero constant array should not solve to NTARR" &&
1497+
ConstantCount != 0);
1498+
ConstantCount--;
1499+
}
1500+
1501+
// Insert this as a declared constant count bound for the constraint
1502+
// variable.
1503+
BoundsKey CBKey = getConstKey(ConstantCount);
1504+
ABounds *NB = new CountBound(CBKey);
1505+
insertDeclaredBounds(VarPCV->getBoundsKey(), NB);
1506+
}
1507+
}
1508+
}
1509+
}

clang/lib/3C/ConstraintResolver.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ CSetBkeyPair ConstraintResolver::getExprConstraintVars(Expr *E) {
382382
if (auto *PCV = dyn_cast<PVConstraint>(CV))
383383
// On the other hand, CheckedC does let you take the address of
384384
// constant sized arrays.
385-
if (!PCV->getArrPresent())
385+
if (!PCV->isConstantArr())
386386
PCV->constrainOuterTo(CS, CS.getPtr(), true);
387387
// Add a VarAtom to UOExpr's PVConstraint, for &.
388388
Ret = std::make_pair(addAtomAll(T.first, CS.getPtr(), CS), T.second);

clang/lib/3C/ConstraintVariables.cpp

Lines changed: 36 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -330,10 +330,6 @@ PointerVariableConstraint::PointerVariableConstraint(
330330
}
331331
InferredGenericIndex = SourceGenericIndex;
332332

333-
bool VarCreated = false;
334-
bool IsArr = false;
335-
bool IsIncompleteArr = false;
336-
bool IsTopMost = true;
337333
uint32_t TypeIdx = 0;
338334
std::string Npre = InFunc ? ((*InFunc) + ":") : "";
339335
VarAtom::VarKind VK =
@@ -355,6 +351,8 @@ PointerVariableConstraint::PointerVariableConstraint(
355351
TLoc = D->getTypeSourceInfo()->getTypeLoc();
356352

357353
while (Ty->isPointerType() || Ty->isArrayType()) {
354+
bool VarCreated = false;
355+
358356
// Is this a VarArg type?
359357
std::string TyName = tyToStr(Ty);
360358
if (isVarArgType(TyName)) {
@@ -404,9 +402,6 @@ PointerVariableConstraint::PointerVariableConstraint(
404402
}
405403

406404
if (Ty->isArrayType() || Ty->isIncompleteArrayType()) {
407-
IsArr = true;
408-
IsIncompleteArr = Ty->isIncompleteArrayType();
409-
410405
// Boil off the typedefs in the array case.
411406
// TODO this will need to change to properly account for typedefs
412407
bool Boiling = true;
@@ -445,13 +440,6 @@ PointerVariableConstraint::PointerVariableConstraint(
445440
ArrSizeStrs[TypeIdx] = SizeStr;
446441
}
447442
}
448-
449-
// If this is the top-most pointer variable?
450-
if (hasBoundsKey() && IsTopMost) {
451-
BoundsKey CBKey = ABInfo.getConstKey(CAT->getSize().getZExtValue());
452-
ABounds *NB = new CountBound(CBKey);
453-
ABInfo.insertDeclaredBounds(D, NB);
454-
}
455443
} else {
456444
ArrSizes[TypeIdx] =
457445
std::pair<OriginalArrType, uint64_t>(O_UnSizedArray, 0);
@@ -479,28 +467,29 @@ PointerVariableConstraint::PointerVariableConstraint(
479467
}
480468

481469
// This type is not a constant atom. We need to create a VarAtom for this.
482-
483470
if (!VarCreated) {
484471
VarAtom *VA = CS.getFreshVar(Npre + N, VK);
485472
Vars.push_back(VA);
486473
SrcVars.push_back(CS.getWild());
487474

488-
// Incomplete arrays are lower bounded to ARR because the transformation
489-
// int[] -> _Ptr<int> is permitted while int[1] -> _Ptr<int> is not.
490-
if (IsIncompleteArr)
491-
CS.addConstraint(CS.createGeq(VA, CS.getArr(), false));
492-
else if (IsArr)
475+
// Incomplete arrays are not given ARR as an upper bound because the
476+
// transformation int[] -> _Ptr<int> is permitted but int[1] -> _Ptr<int>
477+
// is not.
478+
if (ArrSizes[TypeIdx].first == O_SizedArray) {
493479
CS.addConstraint(CS.createGeq(CS.getArr(), VA, false));
480+
// A constant array declared with size 0 cannot be _Nt_checked. Checked
481+
// C requires that _Nt_checked arrays are not empty since the declared
482+
// size of the array includes the null terminator.
483+
if (ArrSizes[TypeIdx].second == 0)
484+
CS.addConstraint(CS.createGeq(VA, CS.getArr(), false));
485+
}
494486
}
495487

496488
// Prepare for next level of pointer
497-
VarCreated = false;
498-
IsArr = false;
499489
TypeIdx++;
500490
Npre = Npre + "*";
501-
VK = VarAtom::
502-
V_Other; // only the outermost pointer considered a param/return
503-
IsTopMost = false;
491+
// Only the outermost pointer considered a param/return
492+
VK = VarAtom::V_Other;
504493
if (!TLoc.isNull())
505494
TLoc = TLoc.getNextTypeLoc();
506495
}
@@ -1483,9 +1472,28 @@ bool PointerVariableConstraint::hasNtArr(const EnvironmentMap &E,
14831472
return false;
14841473
}
14851474

1486-
bool PointerVariableConstraint::getArrPresent() const {
1487-
return llvm::any_of(ArrSizes,
1488-
[](auto E) { return E.second.first != O_Pointer; });
1475+
bool PointerVariableConstraint::isNtConstantArr(const EnvironmentMap &E) const {
1476+
// First check that this pointer is a constant sized array. This function is
1477+
// only looking for constant sized arrays, so other array pointers should
1478+
// return false.
1479+
if (isConstantArr()) {
1480+
assert("Atoms vector for array type can't be empty." && !Vars.empty());
1481+
// This is a constant sized array. It might have solved to WILD, ARR, or
1482+
// NTARR, but we should only return true if we know it's NTARR.
1483+
const ConstAtom *PtrType = getSolution(Vars[0], E);
1484+
return isa<NTArrAtom>(PtrType);
1485+
}
1486+
return false;
1487+
}
1488+
1489+
bool PointerVariableConstraint::isConstantArr() const {
1490+
return ArrSizes.find(0) != ArrSizes.end() &&
1491+
ArrSizes.at(0).first == O_SizedArray;
1492+
}
1493+
1494+
unsigned long PointerVariableConstraint::getConstantArrSize() const {
1495+
assert("Pointer must be a constant array to get size." && isConstantArr());
1496+
return ArrSizes.at(0).second;
14891497
}
14901498

14911499
bool PointerVariableConstraint::isTopAtomUnsizedArr() const {

clang/lib/3C/Utils.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,9 +209,12 @@ bool isNullableType(const clang::QualType &QT) {
209209
}
210210

211211
bool canBeNtArray(const clang::QualType &QT) {
212-
if (const auto &Ptr = dyn_cast<clang::PointerType>(QT))
212+
// First get the canonical type so that the following checks will not have to
213+
// account for ParenType, DecayedType, or other variants used by clang.
214+
QualType Canon = QT.getCanonicalType();
215+
if (const auto &Ptr = dyn_cast<clang::PointerType>(Canon))
213216
return isNullableType(Ptr->getPointeeType());
214-
if (const auto &Arr = dyn_cast<clang::ArrayType>(QT))
217+
if (const auto &Arr = dyn_cast<clang::ArrayType>(Canon))
215218
return isNullableType(Arr->getElementType());
216219
return false;
217220
}

clang/test/3C/canonical_type_cast.c

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,9 @@ void f(int *p) {
1414
}
1515

1616
void g(int p[]) {
17-
//CHECK_NOALL: void g(int p[]) {
18-
//CHECK_ALL: void g(_Ptr<int> p) {
17+
//CHECK: void g(_Ptr<int> p) {
1918
int *x = (int *)p;
20-
//CHECK_NOALL: int *x = (int *)p;
21-
//CHECK_ALL: _Ptr<int> x = (_Ptr<int>)p;
19+
//CHECK: _Ptr<int> x = (_Ptr<int>)p;
2220
}
2321

2422
/* A very similar issue with function pointers */

clang/test/3C/cant_be_nt.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,27 @@ float* dar(struct foobar f) {
4646
//CHECK: _Ptr<float> dar(struct foobar f) {
4747
return f.ptr;
4848
}
49+
50+
// Test a bug I found in canBeNtArray triggered by some specific types.
51+
// The P >= ARR constraint was incorrectly added to these variables, causing
52+
// them to solve to WILD instead of NTARR.
53+
54+
void force_nt_arr(char *s : itype(_Nt_array_ptr<char>));
55+
56+
void decayed_type(char s[10]) {
57+
//CHECK_NOALL: void decayed_type(char s[10]) {
58+
//CHECK_ALL: void decayed_type(char s _Nt_checked[10]) {
59+
force_nt_arr(s);
60+
}
61+
62+
void paren_type(char *(s)) {
63+
//CHECK_NOALL: void paren_type(char *(s) : itype(_Ptr<char>)) {
64+
//CHECK_ALL: void paren_type(_Nt_array_ptr<char> s) {
65+
force_nt_arr(s);
66+
}
67+
68+
void decayed_paren_type(char (s)[10]) {
69+
//CHECK_NOALL: void decayed_paren_type(char (s)[10]) {
70+
//CHECK_ALL: void decayed_paren_type(char s _Nt_checked[10]) {
71+
force_nt_arr(s);
72+
}

clang/test/3C/nt_const_arr.c

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// RUN: rm -rf %t*
2+
// RUN: 3c -base-dir=%S -addcr %s -- | FileCheck -match-full-lines -check-prefixes="CHECK,CHECK_NOALL" %s
3+
// RUN: 3c -base-dir=%S -alltypes -addcr %s -- | FileCheck -match-full-lines -check-prefixes="CHECK,CHECK_ALL" %s
4+
// RUN: 3c -base-dir=%S -alltypes -addcr %s -- | %clang -c -x c -o /dev/null -
5+
// RUN: 3c -base-dir=%S -alltypes -output-dir=%t.checked %s --
6+
// RUN: 3c -base-dir=%t.checked -alltypes %t.checked/nt_const_arr.c -- | diff %t.checked/nt_const_arr.c -
7+
8+
// A _Nt_checked constant sized array declared with size `n` should be treated
9+
// as having bounds `count(n - 1)` durring bounds inference because the null
10+
// terminator is included in the declared length but not in the Checked C
11+
// bounds.
12+
13+
unsigned long strlen(const char *s : itype(_Nt_array_ptr<const char>));
14+
15+
void foo() {
16+
char foo[5] = "test";
17+
//CHECK_ALL: char foo _Nt_checked[5] = "test";
18+
//CHECK_NOALL: char foo[5] = "test";
19+
char *bar = foo;
20+
//CHECK_ALL: _Nt_array_ptr<char> bar : count(4) = foo;
21+
//CHECK_NOALL: char *bar = foo;
22+
strlen(bar);
23+
24+
char *baz = foo;
25+
//CHECK_ALL: _Array_ptr<char> baz : count(4) = foo;
26+
//CHECK_NOALL: char *baz = foo;
27+
(void) baz[0];
28+
}
29+
30+
void arr_param(char foo[10]) {
31+
//CHECK_ALL: void arr_param(char foo _Nt_checked[10]) _Checked {
32+
//CHECK_NOALL: void arr_param(char foo[10]) {
33+
char *bar = foo;
34+
//CHECK_ALL:_Nt_array_ptr<char> bar : count(9) = foo;
35+
//CHECK_NOALL: char *bar = foo;
36+
strlen(bar);
37+
}
38+
39+
void empty() {
40+
// _Nt_checked can't be empty, so this should solve to WILD
41+
char empty_nt[0];
42+
char *bar = empty_nt;
43+
//CHECK: char empty_nt[0];
44+
//CHECK: char *bar = empty_nt;
45+
strlen(bar);
46+
47+
48+
// We can have an empty string by declaring arr with size 1
49+
char empty_str[1] = "";
50+
char *baz = empty_str;
51+
//CHECK_ALL: char empty_str _Nt_checked[1] = "";
52+
//CHECK_NOALL: char empty_str[1] = "";
53+
//CHECK_ALL: _Nt_array_ptr<char> baz : count(0) = empty_str;
54+
//CHECK_NOALL: char *baz = empty_str;
55+
strlen(baz);
56+
57+
// _Checked can also be empty
58+
char empty_checked[0];
59+
//CHECK_ALL: char empty_checked _Checked[0];
60+
//CHECK_NOALL: char empty_checked[0];
61+
}

0 commit comments

Comments
 (0)