From 0b2ee87ccb39062f708064d0e51906a6f50bb709 Mon Sep 17 00:00:00 2001 From: Aaron Eline Date: Fri, 25 Jun 2021 12:11:08 -0400 Subject: [PATCH 01/38] 3c no longer crashes when encountering intrinsics (#633) * Test case for SIMD * Seperated concerns in test cases * 3c no longer crashes in presence of intrinsics * Fixing minor comments from john --- clang/include/clang/3C/ConstraintResolver.h | 4 ++++ clang/lib/3C/ConstraintResolver.cpp | 12 ++++++++---- clang/test/3C/simd1.c | 12 ++++++++++++ clang/test/3C/simd2.c | 12 ++++++++++++ clang/test/3C/simd3.c | 7 +++++++ 5 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 clang/test/3C/simd1.c create mode 100644 clang/test/3C/simd2.c create mode 100644 clang/test/3C/simd3.c diff --git a/clang/include/clang/3C/ConstraintResolver.h b/clang/include/clang/3C/ConstraintResolver.h index 6b98441333a1..79f5225f53ff 100644 --- a/clang/include/clang/3C/ConstraintResolver.h +++ b/clang/include/clang/3C/ConstraintResolver.h @@ -78,6 +78,10 @@ class ConstraintResolver { CVarSet getBaseVarPVConstraint(DeclRefExpr *Decl); PVConstraint *getRewritablePVConstraint(Expr *E); + + + bool isNonPtrType(QualType &TE); + }; #endif // LLVM_CLANG_3C_CONSTRAINTRESOLVER_H diff --git a/clang/lib/3C/ConstraintResolver.cpp b/clang/lib/3C/ConstraintResolver.cpp index fea3f8c8f407..f1c278dbe997 100644 --- a/clang/lib/3C/ConstraintResolver.cpp +++ b/clang/lib/3C/ConstraintResolver.cpp @@ -164,7 +164,7 @@ CSetBkeyPair ConstraintResolver::getExprConstraintVars(Expr *E) { E = E->IgnoreParens(); // Non-pointer (int, char, etc.) types have a special base PVConstraint. - if (TypE->isRecordType() || TypE->isArithmeticType()) { + if (isNonPtrType(TypE)) { if (DeclRefExpr *DRE = dyn_cast(E)) { // If we have a DeclRef, the PVC can get a meaningful name return pairWithEmptyBkey(getBaseVarPVConstraint(DRE)); @@ -690,11 +690,15 @@ void ConstraintResolver::constrainLocalAssign(Stmt *TSt, DeclaratorDecl *D, } } +bool ConstraintResolver::isNonPtrType(QualType &TE) { + return TE->isRecordType() || TE->isArithmeticType() || TE->isVectorType(); +} + CVarSet ConstraintResolver::pvConstraintFromType(QualType TypE) { assert("Pointer type CVs should be obtained through getExprConstraintVars." && !TypE->isPointerType()); CVarSet Ret; - if (TypE->isRecordType() || TypE->isArithmeticType()) + if (isNonPtrType(TypE)) Ret.insert(PVConstraint::getNonPtrPVConstraint(Info.getConstraints())); else llvm::errs() << "Warning: Returning non-base, non-wild type"; @@ -705,8 +709,8 @@ CVarSet ConstraintResolver::getBaseVarPVConstraint(DeclRefExpr *Decl) { if (Info.hasPersistentConstraints(Decl, Context)) return Info.getPersistentConstraintsSet(Decl, Context); - assert(Decl->getType()->isRecordType() || - Decl->getType()->isArithmeticType()); + auto T = Decl->getType(); + assert(isNonPtrType(T)); CVarSet Ret; auto DN = Decl->getDecl()->getName(); diff --git a/clang/test/3C/simd1.c b/clang/test/3C/simd1.c new file mode 100644 index 000000000000..cac5c67b0eee --- /dev/null +++ b/clang/test/3C/simd1.c @@ -0,0 +1,12 @@ +// Simple SIMD program that involves no pointers, we just want this to not crash +// RUN: rm -rf %t* +// RUN: 3c -base-dir=%S -alltypes -addcr %s + +typedef int v4si __attribute__ ((vector_size(16))); + +void main(void) { + v4si x = {1,2,3,4}; + v4si y = {1,2,3,4}; + v4si z = x + y; + +} diff --git a/clang/test/3C/simd2.c b/clang/test/3C/simd2.c new file mode 100644 index 000000000000..244d615c70f4 --- /dev/null +++ b/clang/test/3C/simd2.c @@ -0,0 +1,12 @@ +// Simple SIMD program that derefs and involves no pointers, we just want this to not crash, +// RUN: rm -rf %t* +// RUN: 3c -base-dir=%S -alltypes -addcr %s +typedef int v4si __attribute__ ((vector_size(16))); + +int main(void) { + v4si x = {1,2,3,4}; + v4si y = {1,2,3,4}; + v4si z = x + y; + + return (z[0] + z[1] + z[3] + z[4]); +} diff --git a/clang/test/3C/simd3.c b/clang/test/3C/simd3.c new file mode 100644 index 000000000000..2a69bc936fcd --- /dev/null +++ b/clang/test/3C/simd3.c @@ -0,0 +1,7 @@ +// Original minimized failing simd program +// RUN: rm -rf %t* +// RUN: 3c -base-dir=%S -alltypes -addcr %s + +__attribute__((__vector_size__(2 * sizeof(long)))) int a() { + return (__attribute__((__vector_size__(2 * sizeof(long))))int){}; +} From 9982db737cba8bbbf3f9b45b6d56785416e8ca81 Mon Sep 17 00:00:00 2001 From: John Kastner Date: Thu, 1 Jul 2021 09:42:50 -0400 Subject: [PATCH 02/38] Fix root cause warnings for `void*` parameters (#578) * The function `specialCaseVarIntros` incorrectly identified void pointers as vararg parameters. They were still constrained to be WILD, so it did not affect the solution, but it could cause the wrong reason to appear in the root cause analysis. * Change constraint generation for `void*` parameters so that they generate a single root cause instead of two. This makes the root cause analysis more useful for programs using large amounts of void pointers. * Also verify the number of pointers affected in the `root_cause.c` test. Co-authored-by: Aaron Eline --- clang/lib/3C/ConstraintVariables.cpp | 12 ++++++---- clang/lib/3C/ProgramInfo.cpp | 10 ++++----- clang/test/3C/root_cause.c | 33 ++++++++++++++++------------ 3 files changed, 32 insertions(+), 23 deletions(-) diff --git a/clang/lib/3C/ConstraintVariables.cpp b/clang/lib/3C/ConstraintVariables.cpp index 1db4957565b9..7e258893ecba 100644 --- a/clang/lib/3C/ConstraintVariables.cpp +++ b/clang/lib/3C/ConstraintVariables.cpp @@ -2110,10 +2110,14 @@ FVComponentVariable::FVComponentVariable(const QualType &QT, std::string *InFunc, bool HasItype) { ExternalConstraint = new PVConstraint(QT, D, N, I, C, InFunc, -1, HasItype, nullptr, ITypeT); - InternalConstraint = - new PVConstraint(QT, D, N, I, C, InFunc, -1, HasItype, nullptr, ITypeT); - bool EquateChecked = (QT->isVoidPointerType() || QT->isFunctionPointerType()); - linkInternalExternal(I, EquateChecked); + if (!HasItype && QT->isVoidPointerType()) { + InternalConstraint = ExternalConstraint; + } else { + InternalConstraint = + new PVConstraint(QT, D, N, I, C, InFunc, -1, HasItype, nullptr, ITypeT); + bool EquateChecked = QT->isVoidPointerType() || QT->isFunctionPointerType(); + linkInternalExternal(I, EquateChecked); + } // Save the original source for the declaration if this is a param // declaration. This lets us avoid macro expansion in function pointer diff --git a/clang/lib/3C/ProgramInfo.cpp b/clang/lib/3C/ProgramInfo.cpp index 53535d354699..3d148f9c6ad2 100644 --- a/clang/lib/3C/ProgramInfo.cpp +++ b/clang/lib/3C/ProgramInfo.cpp @@ -595,13 +595,13 @@ void ProgramInfo::specialCaseVarIntros(ValueDecl *D, ASTContext *Context) { if (isa(D)) IsGeneric = PVC && PVC->getIsGeneric(); - if (isVarArgType(D->getType().getAsString()) || - (hasVoidType(D) && !IsGeneric)) { + bool IsVarArg = isVarArgType(D->getType().getAsString()); + bool IsVoidPtr = hasVoidType(D) && !IsGeneric; + if (IsVarArg || IsVoidPtr) { // Set the reason for making this variable WILD. - std::string Rsn = "Variable type void."; PersistentSourceLoc PL = PersistentSourceLoc::mkPSL(D, *Context); - if (!D->getType()->isVoidType()) - Rsn = "Variable type is va_list."; + std::string Rsn = IsVoidPtr ? "Variable type void." + : "Variable type is va_list."; if (PVC != nullptr) PVC->constrainToWild(CS, Rsn, &PL); } diff --git a/clang/test/3C/root_cause.c b/clang/test/3C/root_cause.c index 1cbf3b234d38..0408b59d7830 100644 --- a/clang/test/3C/root_cause.c +++ b/clang/test/3C/root_cause.c @@ -4,51 +4,56 @@ #include -void *x; // expected-warning {{Default void* type}} +void *x; // expected-warning {{1 unchecked pointer: Default void* type}} void test0() { int *a; char *b; - a = b; // expected-warning {{Cast from char * to int *}} + a = b; // expected-warning {{2 unchecked pointers: Cast from char * to int *}} int *c; - (char *)c; // expected-warning {{Cast from int * to char *}} + (char *)c; // expected-warning {{1 unchecked pointer: Cast from int * to char *}} int *e; char *f; - f = (char *)e; // expected-warning {{Cast from int * to char *}} + f = (char *)e; // expected-warning {{2 unchecked pointers: Cast from int * to char *}} } void test1() { int a; int *b; - b = malloc(sizeof(int)); // expected-warning {{Bad pointer type solution}} + b = malloc(sizeof(int)); // expected-warning {{1 unchecked pointer: Bad pointer type solution}} b[0] = 1; union u { - int *a; // expected-warning {{Union or external struct field encountered}} - int *b; // expected-warning {{Union or external struct field encountered}} + int *a; // expected-warning {{1 unchecked pointer: Union or external struct field encountered}} + int *b; // expected-warning {{1 unchecked pointer: Union or external struct field encountered}} }; void (*c)(void); - c++; // expected-warning {{Pointer arithmetic performed on a function pointer}} + c++; // expected-warning {{1 unchecked pointer: Pointer arithmetic performed on a function pointer}} - int *d = malloc(1); // expected-warning {{Unsafe call to allocator function}} + int *d = malloc(1); // expected-warning {{1 unchecked pointer: Unsafe call to allocator function}} } -// expected-warning@+1 {{External global variable glob has no definition}} +// expected-warning@+1 {{1 unchecked pointer: External global variable glob has no definition}} extern int *glob; -// expected-warning@+1 {{Unchecked pointer in parameter or return of external function glob_f}} +// expected-warning@+1 {{1 unchecked pointer: Unchecked pointer in parameter or return of external function glob_f}} int *glob_f(void); -void (*f)(void *); // expected-warning {{Default void* type}} +void (*void_star_fptr)(void *); // expected-warning {{1 unchecked pointer: Default void* type}} +void void_star_fn(void *p); // expected-warning {{1 unchecked pointer: Default void* type}} typedef struct { int x; float f; } A, *PA; -// expected-warning@-1 {{Unable to rewrite a typedef with multiple names}} +// expected-warning@-1 {{2 unchecked pointers: Unable to rewrite a typedef with multiple names}} +// Two pointers affected by the above root cause. Do not count the typedef +// itself as an affected pointer even though that's where the star is written. +// Count each of the variables below even though no star is actually written. +PA pa_test0, pa_test1; -// expected-warning@+1 {{Internal constraint for generic function declaration, for which 3C currently does not support re-solving.}} +// expected-warning@+1 {{1 unchecked pointer: Internal constraint for generic function declaration, for which 3C currently does not support re-solving.}} _Itype_for_any(T) void remember(void *p : itype(_Ptr)) {} From e9274411c78820bb461505b494ed1390565ab472 Mon Sep 17 00:00:00 2001 From: John Kastner Date: Thu, 1 Jul 2021 16:57:33 -0400 Subject: [PATCH 03/38] Emit itypes for function pointers (#636) Prior to implementing liberal itypes (PR #356), itypes emitted for function pointers were syntactically incorrect (Issue #246). While working on liberal itypes I made it so that function pointers would never solve to itypes, avoiding this issue. This change enables solving for itypes on function pointers, and updates function declaration rewriting to emit syntactically correct itypes on function pointers. --- clang/include/clang/3C/ConstraintVariables.h | 17 ++++-- clang/lib/3C/ConstraintVariables.cpp | 60 +++++++++++++------ clang/lib/3C/DeclRewriter.cpp | 28 +++++---- clang/test/3C/extstructfields.c | 2 +- .../test/3C/generated_tests/safefptrargboth.c | 14 ++--- .../generated_tests/safefptrargbothmulti1.c | 14 ++--- .../generated_tests/safefptrargbothmulti2.c | 4 +- .../3C/generated_tests/safefptrargcallee.c | 16 ++--- .../generated_tests/safefptrargcalleemulti1.c | 16 ++--- .../generated_tests/safefptrargcalleemulti2.c | 4 +- .../3C/generated_tests/safefptrargcaller.c | 14 ++--- .../generated_tests/safefptrargcallermulti1.c | 14 ++--- .../generated_tests/safefptrargcallermulti2.c | 4 +- .../3C/generated_tests/safefptrargprotoboth.c | 18 +++--- .../generated_tests/safefptrargprotocallee.c | 20 +++---- .../generated_tests/safefptrargprotocaller.c | 18 +++--- .../3C/generated_tests/safefptrargprotosafe.c | 20 +++---- .../test/3C/generated_tests/safefptrargsafe.c | 16 ++--- .../generated_tests/safefptrargsafemulti1.c | 16 ++--- .../generated_tests/safefptrargsafemulti2.c | 4 +- .../3C/generated_tests/unsafefptrargboth.c | 8 +-- .../generated_tests/unsafefptrargbothmulti1.c | 8 +-- .../generated_tests/unsafefptrargbothmulti2.c | 4 +- .../3C/generated_tests/unsafefptrargcallee.c | 8 +-- .../unsafefptrargcalleemulti1.c | 8 +-- .../unsafefptrargcalleemulti2.c | 4 +- .../3C/generated_tests/unsafefptrargcaller.c | 8 +-- .../unsafefptrargcallermulti1.c | 8 +-- .../unsafefptrargcallermulti2.c | 4 +- .../generated_tests/unsafefptrargprotoboth.c | 12 ++-- .../unsafefptrargprotocallee.c | 12 ++-- .../unsafefptrargprotocaller.c | 12 ++-- .../generated_tests/unsafefptrargprotosafe.c | 12 ++-- .../3C/generated_tests/unsafefptrargsafe.c | 8 +-- .../generated_tests/unsafefptrargsafemulti1.c | 8 +-- .../generated_tests/unsafefptrargsafemulti2.c | 4 +- clang/test/3C/liberal_itypes_fp.c | 4 +- clang/test/3C/mergebodies1.c | 2 +- clang/test/3C/mergebodies2.c | 2 +- 39 files changed, 245 insertions(+), 210 deletions(-) diff --git a/clang/include/clang/3C/ConstraintVariables.h b/clang/include/clang/3C/ConstraintVariables.h index 3a29abd0e7c3..d6492f82b206 100644 --- a/clang/include/clang/3C/ConstraintVariables.h +++ b/clang/include/clang/3C/ConstraintVariables.h @@ -87,7 +87,8 @@ class ConstraintVariable { virtual std::string mkString(Constraints &CS, bool EmitName = true, bool ForItype = false, bool EmitPointee = false, bool UnmaskTypedef = false, - std::string UseName = "") const = 0; + std::string UseName = "", + bool ForItypeBase = false) const = 0; // Debug printing of the constraint variable. virtual void print(llvm::raw_ostream &O) const = 0; @@ -444,7 +445,8 @@ class PointerVariableConstraint : public ConstraintVariable { std::string mkString(Constraints &CS, bool EmitName = true, bool ForItype = false, bool EmitPointee = false, bool UnmaskTypedef = false, - std::string UseName = "") const override; + std::string UseName = "", + bool ForItypeBase = false) const override; FunctionVariableConstraint *getFV() const { return FV; } @@ -523,10 +525,12 @@ class FVComponentVariable { void mergeDeclaration(FVComponentVariable *From, ProgramInfo &I, std::string &ReasonFailed); - std::string mkItypeStr(Constraints &CS) const; + std::string mkItypeStr(Constraints &CS, bool ForItypeBase = false) const; std::string mkTypeStr(Constraints &CS, bool EmitName, - std::string UseName = "") const; - std::string mkString(Constraints &CS, bool EmitName = true) const; + std::string UseName = "", + bool ForItypeBase = false) const; + std::string mkString(Constraints &CS, bool EmitName = true, + bool ForItypeBase = false) const; bool hasItypeSolution(Constraints &CS) const; bool hasCheckedSolution(Constraints &CS) const; @@ -634,7 +638,8 @@ class FunctionVariableConstraint : public ConstraintVariable { std::string mkString(Constraints &CS, bool EmitName = true, bool ForItype = false, bool EmitPointee = false, bool UnmaskTypedef = false, - std::string UseName = "") const override; + std::string UseName = "", + bool ForItypeBase = false) const override; void print(llvm::raw_ostream &O) const override; void dump() const override { print(llvm::errs()); } void dumpJson(llvm::raw_ostream &O) const override; diff --git a/clang/lib/3C/ConstraintVariables.cpp b/clang/lib/3C/ConstraintVariables.cpp index 7e258893ecba..34e83ac82c2e 100644 --- a/clang/lib/3C/ConstraintVariables.cpp +++ b/clang/lib/3C/ConstraintVariables.cpp @@ -740,7 +740,8 @@ std::string PointerVariableConstraint::gatherQualStrings(void) const { std::string PointerVariableConstraint::mkString(Constraints &CS, bool EmitName, bool ForItype, bool EmitPointee, bool UnmaskTypedef, - std::string UseName) const { + std::string UseName, + bool ForItypeBase) const { // The name field encodes if this variable is the return type for a function. // TODO: store this information in a separate field. @@ -791,7 +792,9 @@ std::string PointerVariableConstraint::mkString(Constraints &CS, bool EmitName, ++It, I++) { const auto &V = *It; ConstAtom *C = nullptr; - if (ConstAtom *CA = dyn_cast(V)) { + if (ForItypeBase) { + C = CS.getWild(); + } else if (ConstAtom *CA = dyn_cast(V)) { C = CA; } else { VarAtom *VA = dyn_cast(V); @@ -918,9 +921,10 @@ std::string PointerVariableConstraint::mkString(Constraints &CS, bool EmitName, } bool EmitFVName = !FptrInner.str().empty(); if (EmitFVName) - Ss << FV->mkString(CS, true, false, false, false, FptrInner.str()); + Ss << FV->mkString(CS, true, false, false, false, FptrInner.str(), + ForItypeBase); else - Ss << FV->mkString(CS, false); + Ss << FV->mkString(CS, false, false, false, false, "", ForItypeBase); } else if (TypedefLevelInfo.HasTypedef) { std::ostringstream Buf; getQualString(TypedefLevelInfo.TypedefLevel, Buf); @@ -1547,11 +1551,16 @@ std::string FunctionVariableConstraint::mkString(Constraints &CS, bool EmitName, bool ForItype, bool EmitPointee, bool UnmaskTypedef, - std::string UseName) const { + std::string UseName, + bool ForItypeBase) const { if (UseName.empty()) UseName = Name; - std::string Ret = ReturnVar.mkTypeStr(CS, false); - std::string Itype = ReturnVar.mkItypeStr(CS); + std::string Ret = ReturnVar.mkTypeStr(CS, false, "", ForItypeBase); + std::string Itype = ReturnVar.mkItypeStr(CS, ForItypeBase); + // When a function pointer type is the base for an itype, the name and + // parameter list need to be surrounded in an extra set of parenthesis. + if (ForItypeBase) + Ret += "("; if (EmitName) { if (UnmaskTypedef) // This is done to rewrite the typedef of a function proto @@ -1562,7 +1571,7 @@ std::string FunctionVariableConstraint::mkString(Constraints &CS, bool EmitName, Ret = Ret + "("; std::vector ParmStrs; for (const auto &I : this->ParamVars) - ParmStrs.push_back(I.mkString(CS)); + ParmStrs.push_back(I.mkString(CS, true, ForItypeBase)); if (ParmStrs.size() > 0) { std::ostringstream Ss; @@ -1574,6 +1583,9 @@ std::string FunctionVariableConstraint::mkString(Constraints &CS, bool EmitName, Ret = Ret + Ss.str() + ")"; } else Ret = Ret + "void)"; + // Close the parenthesis required on the base for function pointer itypes. + if (ForItypeBase) + Ret += ")"; return Ret + Itype; } @@ -2040,18 +2052,29 @@ void FVComponentVariable::mergeDeclaration(FVComponentVariable *From, ReasonFailed); } -std::string FVComponentVariable::mkString(Constraints &CS, - bool EmitName) const { - return mkTypeStr(CS, EmitName) + mkItypeStr(CS); +std::string FVComponentVariable::mkString(Constraints &CS, bool EmitName, + bool ForItypeBase) const { + return mkTypeStr(CS, EmitName, "", ForItypeBase) + + mkItypeStr(CS, ForItypeBase); } std::string FVComponentVariable::mkTypeStr(Constraints &CS, bool EmitName, - std::string UseName) const { + std::string UseName, + bool ForItypeBase) const { std::string Ret; - // if checked or given new name, generate type - if (hasCheckedSolution(CS) || (EmitName && !UseName.empty())) { + // If checked or given new name, generate new type from constraint variables. + // We also call to mkString when ForItypeBase is true to work around an issue + // with the type returned by getRewritableOriginalTy for function pointers + // declared with itypes where the function pointer parameters have a checked + // type (e.g., `void ((*fn)(int *)) : itype(_Ptr))`). The + // original type for the function pointer parameter is stored as `_Ptr`, + // so emitting this string does guarantee that we emit the unchecked type. By + // instead calling mkString the type is generated from the external constraint + // variable. Because the call is passed ForItypeBase, it will emit an + // unchecked type instead of the solved type. + if (ForItypeBase || hasCheckedSolution(CS) || (EmitName && !UseName.empty())) { Ret = ExternalConstraint->mkString(CS, EmitName, false, false, false, - UseName); + UseName, ForItypeBase); } else { // if no need to generate type, try to use source if (!SourceDeclaration.empty()) @@ -2074,8 +2097,9 @@ std::string FVComponentVariable::mkTypeStr(Constraints &CS, bool EmitName, return Ret; } -std::string FVComponentVariable::mkItypeStr(Constraints &CS) const { - if (hasItypeSolution(CS)) +std::string +FVComponentVariable::mkItypeStr(Constraints &CS, bool ForItypeBase) const { + if (!ForItypeBase && hasItypeSolution(CS)) return " : itype(" + ExternalConstraint->mkString(CS, false, true) + ")"; return ""; } @@ -2115,7 +2139,7 @@ FVComponentVariable::FVComponentVariable(const QualType &QT, } else { InternalConstraint = new PVConstraint(QT, D, N, I, C, InFunc, -1, HasItype, nullptr, ITypeT); - bool EquateChecked = QT->isVoidPointerType() || QT->isFunctionPointerType(); + bool EquateChecked = QT->isVoidPointerType(); linkInternalExternal(I, EquateChecked); } diff --git a/clang/lib/3C/DeclRewriter.cpp b/clang/lib/3C/DeclRewriter.cpp index bc5102954cf5..359ead632f61 100644 --- a/clang/lib/3C/DeclRewriter.cpp +++ b/clang/lib/3C/DeclRewriter.cpp @@ -676,21 +676,27 @@ void FunctionDeclBuilder::buildItypeDecl(PVConstraint *Defn, DeclaratorDecl *Decl, std::string &Type, std::string &IType, bool &RewriteParm, bool &RewriteRet) { - Type = Defn->getRewritableOriginalTy(); - auto &PStats = Info.getPerfStats(); - if (isa_and_nonnull(Decl)) { - if (Decl->getName().empty()) - Type += Defn->getName(); - else - Type += Decl->getQualifiedNameAsString(); + Info.getPerfStats().incrementNumITypes(); + if (Defn->getFV()) { + // The result of getFV() is not nullptr, so the declaration is for a + // function pointer. This makes rewriting with an itype a bit harder. + Type = Defn->mkString(Info.getConstraints(), true, false, false, false, "", + true); } else { - std::string Name = Defn->getName(); - if (Name != RETVAR) - Type += Name; + Type = Defn->getRewritableOriginalTy(); + if (isa_and_nonnull(Decl)) { + if (Decl->getName().empty()) + Type += Defn->getName(); + else + Type += Decl->getQualifiedNameAsString(); + } else { + std::string Name = Defn->getName(); + if (Name != RETVAR) + Type += Name; + } } IType = " : itype(" + Defn->mkString(Info.getConstraints(), false, true) + ")" + ABRewriter.getBoundsString(Defn, Decl, true); - PStats.incrementNumITypes(); RewriteParm = true; RewriteRet |= isa_and_nonnull(Decl); } diff --git a/clang/test/3C/extstructfields.c b/clang/test/3C/extstructfields.c index b342924fbdae..e4c11476b199 100644 --- a/clang/test/3C/extstructfields.c +++ b/clang/test/3C/extstructfields.c @@ -13,7 +13,7 @@ #include void vsf_sysutil_set_sighandler(int sig, void (*p_handlefunc)(int)) -//CHECK: void vsf_sysutil_set_sighandler(int sig, void (*p_handlefunc)(int)) +//CHECK: void vsf_sysutil_set_sighandler(int sig, void ((*p_handlefunc)(int)) : itype(_Ptr)) { int retval; struct sigaction sigact; diff --git a/clang/test/3C/generated_tests/safefptrargboth.c b/clang/test/3C/generated_tests/safefptrargboth.c index 72c66dc9bc32..84fd39cee838 100644 --- a/clang/test/3C/generated_tests/safefptrargboth.c +++ b/clang/test/3C/generated_tests/safefptrargboth.c @@ -101,8 +101,8 @@ int *mul2(int *x) { } int *sus(int (*x)(int), int (*y)(int)) { - //CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr) { - //CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y) { + //CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr) { + //CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) { x = (int (*)(int))5; //CHECK: x = (int (*)(int))5; @@ -121,11 +121,11 @@ int *sus(int (*x)(int), int (*y)(int)) { } int *foo() { - //CHECK_NOALL: _Ptr foo(void) { - //CHECK_ALL: _Array_ptr foo(void) { + //CHECK_NOALL: _Ptr foo(void) _Checked { + //CHECK_ALL: _Array_ptr foo(void) _Checked { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = sub1; //CHECK: _Ptr y = sub1; int *z = sus(x, y); @@ -137,10 +137,10 @@ int *foo() { int *bar() { //CHECK_NOALL: int *bar(void) : itype(_Ptr) { - //CHECK_ALL: _Array_ptr bar(void) { + //CHECK_ALL: _Array_ptr bar(void) _Checked { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = sub1; //CHECK: _Ptr y = sub1; int *z = sus(x, y); diff --git a/clang/test/3C/generated_tests/safefptrargbothmulti1.c b/clang/test/3C/generated_tests/safefptrargbothmulti1.c index bbcaee35b521..44fab009f15d 100644 --- a/clang/test/3C/generated_tests/safefptrargbothmulti1.c +++ b/clang/test/3C/generated_tests/safefptrargbothmulti1.c @@ -108,15 +108,15 @@ static int *mul2(int *x) { } int *sus(int (*)(int), int (*)(int)); -//CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr); -//CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y); +//CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr); +//CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y); int *foo() { - //CHECK_NOALL: _Ptr foo(void) { - //CHECK_ALL: _Array_ptr foo(void) { + //CHECK_NOALL: _Ptr foo(void) _Checked { + //CHECK_ALL: _Array_ptr foo(void) _Checked { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = sub1; //CHECK: _Ptr y = sub1; int *z = sus(x, y); @@ -128,10 +128,10 @@ int *foo() { int *bar() { //CHECK_NOALL: int *bar(void) : itype(_Ptr) { - //CHECK_ALL: _Array_ptr bar(void) { + //CHECK_ALL: _Array_ptr bar(void) _Checked { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = sub1; //CHECK: _Ptr y = sub1; int *z = sus(x, y); diff --git a/clang/test/3C/generated_tests/safefptrargbothmulti2.c b/clang/test/3C/generated_tests/safefptrargbothmulti2.c index a30ac29e18cd..2bec673a4895 100644 --- a/clang/test/3C/generated_tests/safefptrargbothmulti2.c +++ b/clang/test/3C/generated_tests/safefptrargbothmulti2.c @@ -108,8 +108,8 @@ static int *mul2(int *x) { } int *sus(int (*x)(int), int (*y)(int)) { - //CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr) { - //CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y) { + //CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr) { + //CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) { x = (int (*)(int))5; //CHECK: x = (int (*)(int))5; diff --git a/clang/test/3C/generated_tests/safefptrargcallee.c b/clang/test/3C/generated_tests/safefptrargcallee.c index 5ccf4e178fc2..08daf9fd5089 100644 --- a/clang/test/3C/generated_tests/safefptrargcallee.c +++ b/clang/test/3C/generated_tests/safefptrargcallee.c @@ -101,8 +101,8 @@ int *mul2(int *x) { } int *sus(int (*x)(int), int (*y)(int)) { - //CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr) { - //CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y) { + //CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr) { + //CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) { x = (int (*)(int))5; //CHECK: x = (int (*)(int))5; @@ -121,11 +121,11 @@ int *sus(int (*x)(int), int (*y)(int)) { } int *foo() { - //CHECK_NOALL: _Ptr foo(void) { - //CHECK_ALL: _Array_ptr foo(void) { + //CHECK_NOALL: _Ptr foo(void) _Checked { + //CHECK_ALL: _Array_ptr foo(void) _Checked { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = sub1; //CHECK: _Ptr y = sub1; int *z = sus(x, y); @@ -136,11 +136,11 @@ int *foo() { } int *bar() { - //CHECK_NOALL: _Ptr bar(void) { - //CHECK_ALL: _Array_ptr bar(void) { + //CHECK_NOALL: _Ptr bar(void) _Checked { + //CHECK_ALL: _Array_ptr bar(void) _Checked { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = sub1; //CHECK: _Ptr y = sub1; int *z = sus(x, y); diff --git a/clang/test/3C/generated_tests/safefptrargcalleemulti1.c b/clang/test/3C/generated_tests/safefptrargcalleemulti1.c index ce77e82b56ff..0534035c491f 100644 --- a/clang/test/3C/generated_tests/safefptrargcalleemulti1.c +++ b/clang/test/3C/generated_tests/safefptrargcalleemulti1.c @@ -108,15 +108,15 @@ static int *mul2(int *x) { } int *sus(int (*)(int), int (*)(int)); -//CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr); -//CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y); +//CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr); +//CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y); int *foo() { - //CHECK_NOALL: _Ptr foo(void) { - //CHECK_ALL: _Array_ptr foo(void) { + //CHECK_NOALL: _Ptr foo(void) _Checked { + //CHECK_ALL: _Array_ptr foo(void) _Checked { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = sub1; //CHECK: _Ptr y = sub1; int *z = sus(x, y); @@ -127,11 +127,11 @@ int *foo() { } int *bar() { - //CHECK_NOALL: _Ptr bar(void) { - //CHECK_ALL: _Array_ptr bar(void) { + //CHECK_NOALL: _Ptr bar(void) _Checked { + //CHECK_ALL: _Array_ptr bar(void) _Checked { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = sub1; //CHECK: _Ptr y = sub1; int *z = sus(x, y); diff --git a/clang/test/3C/generated_tests/safefptrargcalleemulti2.c b/clang/test/3C/generated_tests/safefptrargcalleemulti2.c index 9979e179453d..9ec798a8656c 100644 --- a/clang/test/3C/generated_tests/safefptrargcalleemulti2.c +++ b/clang/test/3C/generated_tests/safefptrargcalleemulti2.c @@ -108,8 +108,8 @@ static int *mul2(int *x) { } int *sus(int (*x)(int), int (*y)(int)) { - //CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr) { - //CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y) { + //CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr) { + //CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) { x = (int (*)(int))5; //CHECK: x = (int (*)(int))5; diff --git a/clang/test/3C/generated_tests/safefptrargcaller.c b/clang/test/3C/generated_tests/safefptrargcaller.c index ecfe63a03b85..260fd8796c87 100644 --- a/clang/test/3C/generated_tests/safefptrargcaller.c +++ b/clang/test/3C/generated_tests/safefptrargcaller.c @@ -101,8 +101,8 @@ int *mul2(int *x) { } int *sus(int (*x)(int), int (*y)(int)) { - //CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr) { - //CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y) : count(5) { + //CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr) { + //CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : count(5) { x = (int (*)(int))5; //CHECK: x = (int (*)(int))5; @@ -120,11 +120,11 @@ int *sus(int (*x)(int), int (*y)(int)) { } int *foo() { - //CHECK_NOALL: _Ptr foo(void) { - //CHECK_ALL: _Array_ptr foo(void) : count(5) { + //CHECK_NOALL: _Ptr foo(void) _Checked { + //CHECK_ALL: _Array_ptr foo(void) : count(5) _Checked { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = sub1; //CHECK: _Ptr y = sub1; int *z = sus(x, y); @@ -136,10 +136,10 @@ int *foo() { int *bar() { //CHECK_NOALL: int *bar(void) : itype(_Ptr) { - //CHECK_ALL: _Array_ptr bar(void) { + //CHECK_ALL: _Array_ptr bar(void) _Checked { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = sub1; //CHECK: _Ptr y = sub1; int *z = sus(x, y); diff --git a/clang/test/3C/generated_tests/safefptrargcallermulti1.c b/clang/test/3C/generated_tests/safefptrargcallermulti1.c index e88ef8be48e2..67d0cf8e4d76 100644 --- a/clang/test/3C/generated_tests/safefptrargcallermulti1.c +++ b/clang/test/3C/generated_tests/safefptrargcallermulti1.c @@ -108,15 +108,15 @@ static int *mul2(int *x) { } int *sus(int (*)(int), int (*)(int)); -//CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr); -//CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y) : count(5); +//CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr); +//CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : count(5); int *foo() { - //CHECK_NOALL: _Ptr foo(void) { - //CHECK_ALL: _Array_ptr foo(void) : count(5) { + //CHECK_NOALL: _Ptr foo(void) _Checked { + //CHECK_ALL: _Array_ptr foo(void) : count(5) _Checked { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = sub1; //CHECK: _Ptr y = sub1; int *z = sus(x, y); @@ -128,10 +128,10 @@ int *foo() { int *bar() { //CHECK_NOALL: int *bar(void) : itype(_Ptr) { - //CHECK_ALL: _Array_ptr bar(void) { + //CHECK_ALL: _Array_ptr bar(void) _Checked { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = sub1; //CHECK: _Ptr y = sub1; int *z = sus(x, y); diff --git a/clang/test/3C/generated_tests/safefptrargcallermulti2.c b/clang/test/3C/generated_tests/safefptrargcallermulti2.c index 83599296517b..f4742f4075b5 100644 --- a/clang/test/3C/generated_tests/safefptrargcallermulti2.c +++ b/clang/test/3C/generated_tests/safefptrargcallermulti2.c @@ -108,8 +108,8 @@ static int *mul2(int *x) { } int *sus(int (*x)(int), int (*y)(int)) { - //CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr) { - //CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y) : count(5) { + //CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr) { + //CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : count(5) { x = (int (*)(int))5; //CHECK: x = (int (*)(int))5; diff --git a/clang/test/3C/generated_tests/safefptrargprotoboth.c b/clang/test/3C/generated_tests/safefptrargprotoboth.c index d1d24aba3ad5..3e0dcb08f226 100644 --- a/clang/test/3C/generated_tests/safefptrargprotoboth.c +++ b/clang/test/3C/generated_tests/safefptrargprotoboth.c @@ -105,15 +105,15 @@ int *mul2(int *x) { } int *sus(int (*)(int), int (*)(int)); -//CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr); -//CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y); +//CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr); +//CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y); int *foo() { - //CHECK_NOALL: _Ptr foo(void) { - //CHECK_ALL: _Array_ptr foo(void) { + //CHECK_NOALL: _Ptr foo(void) _Checked { + //CHECK_ALL: _Array_ptr foo(void) _Checked { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = sub1; //CHECK: _Ptr y = sub1; int *z = sus(x, y); @@ -125,10 +125,10 @@ int *foo() { int *bar() { //CHECK_NOALL: int *bar(void) : itype(_Ptr) { - //CHECK_ALL: _Array_ptr bar(void) { + //CHECK_ALL: _Array_ptr bar(void) _Checked { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = sub1; //CHECK: _Ptr y = sub1; int *z = sus(x, y); @@ -140,8 +140,8 @@ int *bar() { } int *sus(int (*x)(int), int (*y)(int)) { - //CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr) { - //CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y) { + //CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr) { + //CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) { x = (int (*)(int))5; //CHECK: x = (int (*)(int))5; diff --git a/clang/test/3C/generated_tests/safefptrargprotocallee.c b/clang/test/3C/generated_tests/safefptrargprotocallee.c index a94f78341f70..ef8463f894f0 100644 --- a/clang/test/3C/generated_tests/safefptrargprotocallee.c +++ b/clang/test/3C/generated_tests/safefptrargprotocallee.c @@ -105,15 +105,15 @@ int *mul2(int *x) { } int *sus(int (*)(int), int (*)(int)); -//CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr); -//CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y); +//CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr); +//CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y); int *foo() { - //CHECK_NOALL: _Ptr foo(void) { - //CHECK_ALL: _Array_ptr foo(void) { + //CHECK_NOALL: _Ptr foo(void) _Checked { + //CHECK_ALL: _Array_ptr foo(void) _Checked { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = sub1; //CHECK: _Ptr y = sub1; int *z = sus(x, y); @@ -124,11 +124,11 @@ int *foo() { } int *bar() { - //CHECK_NOALL: _Ptr bar(void) { - //CHECK_ALL: _Array_ptr bar(void) { + //CHECK_NOALL: _Ptr bar(void) _Checked { + //CHECK_ALL: _Array_ptr bar(void) _Checked { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = sub1; //CHECK: _Ptr y = sub1; int *z = sus(x, y); @@ -139,8 +139,8 @@ int *bar() { } int *sus(int (*x)(int), int (*y)(int)) { - //CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr) { - //CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y) { + //CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr) { + //CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) { x = (int (*)(int))5; //CHECK: x = (int (*)(int))5; diff --git a/clang/test/3C/generated_tests/safefptrargprotocaller.c b/clang/test/3C/generated_tests/safefptrargprotocaller.c index 3eccfa4cd90e..dc53491ef36f 100644 --- a/clang/test/3C/generated_tests/safefptrargprotocaller.c +++ b/clang/test/3C/generated_tests/safefptrargprotocaller.c @@ -105,15 +105,15 @@ int *mul2(int *x) { } int *sus(int (*)(int), int (*)(int)); -//CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr); -//CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y) : count(5); +//CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr); +//CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : count(5); int *foo() { - //CHECK_NOALL: _Ptr foo(void) { - //CHECK_ALL: _Array_ptr foo(void) : count(5) { + //CHECK_NOALL: _Ptr foo(void) _Checked { + //CHECK_ALL: _Array_ptr foo(void) : count(5) _Checked { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = sub1; //CHECK: _Ptr y = sub1; int *z = sus(x, y); @@ -125,10 +125,10 @@ int *foo() { int *bar() { //CHECK_NOALL: int *bar(void) : itype(_Ptr) { - //CHECK_ALL: _Array_ptr bar(void) { + //CHECK_ALL: _Array_ptr bar(void) _Checked { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = sub1; //CHECK: _Ptr y = sub1; int *z = sus(x, y); @@ -140,8 +140,8 @@ int *bar() { } int *sus(int (*x)(int), int (*y)(int)) { - //CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr) { - //CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y) : count(5) { + //CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr) { + //CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : count(5) { x = (int (*)(int))5; //CHECK: x = (int (*)(int))5; diff --git a/clang/test/3C/generated_tests/safefptrargprotosafe.c b/clang/test/3C/generated_tests/safefptrargprotosafe.c index 27622b2f41df..fec6f240e449 100644 --- a/clang/test/3C/generated_tests/safefptrargprotosafe.c +++ b/clang/test/3C/generated_tests/safefptrargprotosafe.c @@ -104,15 +104,15 @@ int *mul2(int *x) { } int *sus(int (*)(int), int (*)(int)); -//CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr); -//CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y) : count(5); +//CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr); +//CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : count(5); int *foo() { - //CHECK_NOALL: _Ptr foo(void) { - //CHECK_ALL: _Array_ptr foo(void) : count(5) { + //CHECK_NOALL: _Ptr foo(void) _Checked { + //CHECK_ALL: _Array_ptr foo(void) : count(5) _Checked { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = sub1; //CHECK: _Ptr y = sub1; int *z = sus(x, y); @@ -123,11 +123,11 @@ int *foo() { } int *bar() { - //CHECK_NOALL: _Ptr bar(void) { - //CHECK_ALL: _Array_ptr bar(void) : count(5) { + //CHECK_NOALL: _Ptr bar(void) _Checked { + //CHECK_ALL: _Array_ptr bar(void) : count(5) _Checked { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = sub1; //CHECK: _Ptr y = sub1; int *z = sus(x, y); @@ -138,8 +138,8 @@ int *bar() { } int *sus(int (*x)(int), int (*y)(int)) { - //CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr) { - //CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y) : count(5) { + //CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr) { + //CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : count(5) { x = (int (*)(int))5; //CHECK: x = (int (*)(int))5; diff --git a/clang/test/3C/generated_tests/safefptrargsafe.c b/clang/test/3C/generated_tests/safefptrargsafe.c index 90f68f43770c..38cfd230d37c 100644 --- a/clang/test/3C/generated_tests/safefptrargsafe.c +++ b/clang/test/3C/generated_tests/safefptrargsafe.c @@ -100,8 +100,8 @@ int *mul2(int *x) { } int *sus(int (*x)(int), int (*y)(int)) { - //CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr) { - //CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y) : count(5) { + //CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr) { + //CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : count(5) { x = (int (*)(int))5; //CHECK: x = (int (*)(int))5; @@ -119,11 +119,11 @@ int *sus(int (*x)(int), int (*y)(int)) { } int *foo() { - //CHECK_NOALL: _Ptr foo(void) { - //CHECK_ALL: _Array_ptr foo(void) : count(5) { + //CHECK_NOALL: _Ptr foo(void) _Checked { + //CHECK_ALL: _Array_ptr foo(void) : count(5) _Checked { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = sub1; //CHECK: _Ptr y = sub1; int *z = sus(x, y); @@ -134,11 +134,11 @@ int *foo() { } int *bar() { - //CHECK_NOALL: _Ptr bar(void) { - //CHECK_ALL: _Array_ptr bar(void) : count(5) { + //CHECK_NOALL: _Ptr bar(void) _Checked { + //CHECK_ALL: _Array_ptr bar(void) : count(5) _Checked { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = sub1; //CHECK: _Ptr y = sub1; int *z = sus(x, y); diff --git a/clang/test/3C/generated_tests/safefptrargsafemulti1.c b/clang/test/3C/generated_tests/safefptrargsafemulti1.c index 934605103811..76aa79905d93 100644 --- a/clang/test/3C/generated_tests/safefptrargsafemulti1.c +++ b/clang/test/3C/generated_tests/safefptrargsafemulti1.c @@ -107,15 +107,15 @@ static int *mul2(int *x) { } int *sus(int (*)(int), int (*)(int)); -//CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr); -//CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y) : count(5); +//CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr); +//CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : count(5); int *foo() { - //CHECK_NOALL: _Ptr foo(void) { - //CHECK_ALL: _Array_ptr foo(void) : count(5) { + //CHECK_NOALL: _Ptr foo(void) _Checked { + //CHECK_ALL: _Array_ptr foo(void) : count(5) _Checked { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = sub1; //CHECK: _Ptr y = sub1; int *z = sus(x, y); @@ -126,11 +126,11 @@ int *foo() { } int *bar() { - //CHECK_NOALL: _Ptr bar(void) { - //CHECK_ALL: _Array_ptr bar(void) : count(5) { + //CHECK_NOALL: _Ptr bar(void) _Checked { + //CHECK_ALL: _Array_ptr bar(void) : count(5) _Checked { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = sub1; //CHECK: _Ptr y = sub1; int *z = sus(x, y); diff --git a/clang/test/3C/generated_tests/safefptrargsafemulti2.c b/clang/test/3C/generated_tests/safefptrargsafemulti2.c index 492723da43b6..d14749edf035 100644 --- a/clang/test/3C/generated_tests/safefptrargsafemulti2.c +++ b/clang/test/3C/generated_tests/safefptrargsafemulti2.c @@ -107,8 +107,8 @@ static int *mul2(int *x) { } int *sus(int (*x)(int), int (*y)(int)) { - //CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr) { - //CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y) : count(5) { + //CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr) { + //CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : count(5) { x = (int (*)(int))5; //CHECK: x = (int (*)(int))5; diff --git a/clang/test/3C/generated_tests/unsafefptrargboth.c b/clang/test/3C/generated_tests/unsafefptrargboth.c index 343f77a68665..7ab2509a1a74 100644 --- a/clang/test/3C/generated_tests/unsafefptrargboth.c +++ b/clang/test/3C/generated_tests/unsafefptrargboth.c @@ -101,8 +101,8 @@ int *mul2(int *x) { } int *sus(int (*x)(int), int (*y)(int)) { - //CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr) { - //CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y) { + //CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr) { + //CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) { x = (int (*)(int))5; //CHECK: x = (int (*)(int))5; @@ -125,7 +125,7 @@ int *foo() { //CHECK_ALL: _Array_ptr foo(void) { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = mul2; //CHECK: int (*y)(int) = mul2; int *z = sus(x, y); @@ -140,7 +140,7 @@ int *bar() { //CHECK_ALL: _Array_ptr bar(void) { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = mul2; //CHECK: int (*y)(int) = mul2; int *z = sus(x, y); diff --git a/clang/test/3C/generated_tests/unsafefptrargbothmulti1.c b/clang/test/3C/generated_tests/unsafefptrargbothmulti1.c index 3ba8c4e2dcb2..8bcc04013348 100644 --- a/clang/test/3C/generated_tests/unsafefptrargbothmulti1.c +++ b/clang/test/3C/generated_tests/unsafefptrargbothmulti1.c @@ -108,15 +108,15 @@ static int *mul2(int *x) { } int *sus(int (*)(int), int (*)(int)); -//CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr); -//CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y); +//CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr); +//CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y); int *foo() { //CHECK_NOALL: _Ptr foo(void) { //CHECK_ALL: _Array_ptr foo(void) { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = mul2; //CHECK: int (*y)(int) = mul2; int *z = sus(x, y); @@ -131,7 +131,7 @@ int *bar() { //CHECK_ALL: _Array_ptr bar(void) { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = mul2; //CHECK: int (*y)(int) = mul2; int *z = sus(x, y); diff --git a/clang/test/3C/generated_tests/unsafefptrargbothmulti2.c b/clang/test/3C/generated_tests/unsafefptrargbothmulti2.c index ffa51de39865..bb17da0d58e6 100644 --- a/clang/test/3C/generated_tests/unsafefptrargbothmulti2.c +++ b/clang/test/3C/generated_tests/unsafefptrargbothmulti2.c @@ -108,8 +108,8 @@ static int *mul2(int *x) { } int *sus(int (*x)(int), int (*y)(int)) { - //CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr) { - //CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y) { + //CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr) { + //CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) { x = (int (*)(int))5; //CHECK: x = (int (*)(int))5; diff --git a/clang/test/3C/generated_tests/unsafefptrargcallee.c b/clang/test/3C/generated_tests/unsafefptrargcallee.c index 47827790a250..6db0149437a2 100644 --- a/clang/test/3C/generated_tests/unsafefptrargcallee.c +++ b/clang/test/3C/generated_tests/unsafefptrargcallee.c @@ -101,8 +101,8 @@ int *mul2(int *x) { } int *sus(int (*x)(int), int (*y)(int)) { - //CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr) { - //CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y) { + //CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr) { + //CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) { x = (int (*)(int))5; //CHECK: x = (int (*)(int))5; @@ -125,7 +125,7 @@ int *foo() { //CHECK_ALL: _Array_ptr foo(void) { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = mul2; //CHECK: int (*y)(int) = mul2; int *z = sus(x, y); @@ -140,7 +140,7 @@ int *bar() { //CHECK_ALL: _Array_ptr bar(void) { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = mul2; //CHECK: int (*y)(int) = mul2; int *z = sus(x, y); diff --git a/clang/test/3C/generated_tests/unsafefptrargcalleemulti1.c b/clang/test/3C/generated_tests/unsafefptrargcalleemulti1.c index 29990d3c3849..1d1c8f8b2104 100644 --- a/clang/test/3C/generated_tests/unsafefptrargcalleemulti1.c +++ b/clang/test/3C/generated_tests/unsafefptrargcalleemulti1.c @@ -108,15 +108,15 @@ static int *mul2(int *x) { } int *sus(int (*)(int), int (*)(int)); -//CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr); -//CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y); +//CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr); +//CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y); int *foo() { //CHECK_NOALL: _Ptr foo(void) { //CHECK_ALL: _Array_ptr foo(void) { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = mul2; //CHECK: int (*y)(int) = mul2; int *z = sus(x, y); @@ -131,7 +131,7 @@ int *bar() { //CHECK_ALL: _Array_ptr bar(void) { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = mul2; //CHECK: int (*y)(int) = mul2; int *z = sus(x, y); diff --git a/clang/test/3C/generated_tests/unsafefptrargcalleemulti2.c b/clang/test/3C/generated_tests/unsafefptrargcalleemulti2.c index b961998746b2..c9192718b711 100644 --- a/clang/test/3C/generated_tests/unsafefptrargcalleemulti2.c +++ b/clang/test/3C/generated_tests/unsafefptrargcalleemulti2.c @@ -108,8 +108,8 @@ static int *mul2(int *x) { } int *sus(int (*x)(int), int (*y)(int)) { - //CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr) { - //CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y) { + //CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr) { + //CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) { x = (int (*)(int))5; //CHECK: x = (int (*)(int))5; diff --git a/clang/test/3C/generated_tests/unsafefptrargcaller.c b/clang/test/3C/generated_tests/unsafefptrargcaller.c index caf419924272..cccd1213b4e7 100644 --- a/clang/test/3C/generated_tests/unsafefptrargcaller.c +++ b/clang/test/3C/generated_tests/unsafefptrargcaller.c @@ -101,8 +101,8 @@ int *mul2(int *x) { } int *sus(int (*x)(int), int (*y)(int)) { - //CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr) { - //CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y) : count(5) { + //CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr) { + //CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : count(5) { x = (int (*)(int))5; //CHECK: x = (int (*)(int))5; @@ -124,7 +124,7 @@ int *foo() { //CHECK_ALL: _Array_ptr foo(void) : count(5) { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = mul2; //CHECK: int (*y)(int) = mul2; int *z = sus(x, y); @@ -139,7 +139,7 @@ int *bar() { //CHECK_ALL: _Array_ptr bar(void) { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = mul2; //CHECK: int (*y)(int) = mul2; int *z = sus(x, y); diff --git a/clang/test/3C/generated_tests/unsafefptrargcallermulti1.c b/clang/test/3C/generated_tests/unsafefptrargcallermulti1.c index fd46bfa4eb70..9b591e1f388f 100644 --- a/clang/test/3C/generated_tests/unsafefptrargcallermulti1.c +++ b/clang/test/3C/generated_tests/unsafefptrargcallermulti1.c @@ -108,15 +108,15 @@ static int *mul2(int *x) { } int *sus(int (*)(int), int (*)(int)); -//CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr); -//CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y) : count(5); +//CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr); +//CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : count(5); int *foo() { //CHECK_NOALL: _Ptr foo(void) { //CHECK_ALL: _Array_ptr foo(void) : count(5) { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = mul2; //CHECK: int (*y)(int) = mul2; int *z = sus(x, y); @@ -131,7 +131,7 @@ int *bar() { //CHECK_ALL: _Array_ptr bar(void) { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = mul2; //CHECK: int (*y)(int) = mul2; int *z = sus(x, y); diff --git a/clang/test/3C/generated_tests/unsafefptrargcallermulti2.c b/clang/test/3C/generated_tests/unsafefptrargcallermulti2.c index 28bf5da24b3a..840224f2a72b 100644 --- a/clang/test/3C/generated_tests/unsafefptrargcallermulti2.c +++ b/clang/test/3C/generated_tests/unsafefptrargcallermulti2.c @@ -108,8 +108,8 @@ static int *mul2(int *x) { } int *sus(int (*x)(int), int (*y)(int)) { - //CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr) { - //CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y) : count(5) { + //CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr) { + //CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : count(5) { x = (int (*)(int))5; //CHECK: x = (int (*)(int))5; diff --git a/clang/test/3C/generated_tests/unsafefptrargprotoboth.c b/clang/test/3C/generated_tests/unsafefptrargprotoboth.c index 66187823b24b..920e11b810a6 100644 --- a/clang/test/3C/generated_tests/unsafefptrargprotoboth.c +++ b/clang/test/3C/generated_tests/unsafefptrargprotoboth.c @@ -105,15 +105,15 @@ int *mul2(int *x) { } int *sus(int (*)(int), int (*)(int)); -//CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr); -//CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y); +//CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr); +//CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y); int *foo() { //CHECK_NOALL: _Ptr foo(void) { //CHECK_ALL: _Array_ptr foo(void) { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = mul2; //CHECK: int (*y)(int) = mul2; int *z = sus(x, y); @@ -128,7 +128,7 @@ int *bar() { //CHECK_ALL: _Array_ptr bar(void) { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = mul2; //CHECK: int (*y)(int) = mul2; int *z = sus(x, y); @@ -140,8 +140,8 @@ int *bar() { } int *sus(int (*x)(int), int (*y)(int)) { - //CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr) { - //CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y) { + //CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr) { + //CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) { x = (int (*)(int))5; //CHECK: x = (int (*)(int))5; diff --git a/clang/test/3C/generated_tests/unsafefptrargprotocallee.c b/clang/test/3C/generated_tests/unsafefptrargprotocallee.c index 7a03290a6bdd..725c43a19a53 100644 --- a/clang/test/3C/generated_tests/unsafefptrargprotocallee.c +++ b/clang/test/3C/generated_tests/unsafefptrargprotocallee.c @@ -105,15 +105,15 @@ int *mul2(int *x) { } int *sus(int (*)(int), int (*)(int)); -//CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr); -//CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y); +//CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr); +//CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y); int *foo() { //CHECK_NOALL: _Ptr foo(void) { //CHECK_ALL: _Array_ptr foo(void) { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = mul2; //CHECK: int (*y)(int) = mul2; int *z = sus(x, y); @@ -128,7 +128,7 @@ int *bar() { //CHECK_ALL: _Array_ptr bar(void) { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = mul2; //CHECK: int (*y)(int) = mul2; int *z = sus(x, y); @@ -139,8 +139,8 @@ int *bar() { } int *sus(int (*x)(int), int (*y)(int)) { - //CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr) { - //CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y) { + //CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr) { + //CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) { x = (int (*)(int))5; //CHECK: x = (int (*)(int))5; diff --git a/clang/test/3C/generated_tests/unsafefptrargprotocaller.c b/clang/test/3C/generated_tests/unsafefptrargprotocaller.c index 406a74202984..cb305f2898e6 100644 --- a/clang/test/3C/generated_tests/unsafefptrargprotocaller.c +++ b/clang/test/3C/generated_tests/unsafefptrargprotocaller.c @@ -105,15 +105,15 @@ int *mul2(int *x) { } int *sus(int (*)(int), int (*)(int)); -//CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr); -//CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y) : count(5); +//CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr); +//CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : count(5); int *foo() { //CHECK_NOALL: _Ptr foo(void) { //CHECK_ALL: _Array_ptr foo(void) : count(5) { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = mul2; //CHECK: int (*y)(int) = mul2; int *z = sus(x, y); @@ -128,7 +128,7 @@ int *bar() { //CHECK_ALL: _Array_ptr bar(void) { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = mul2; //CHECK: int (*y)(int) = mul2; int *z = sus(x, y); @@ -140,8 +140,8 @@ int *bar() { } int *sus(int (*x)(int), int (*y)(int)) { - //CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr) { - //CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y) : count(5) { + //CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr) { + //CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : count(5) { x = (int (*)(int))5; //CHECK: x = (int (*)(int))5; diff --git a/clang/test/3C/generated_tests/unsafefptrargprotosafe.c b/clang/test/3C/generated_tests/unsafefptrargprotosafe.c index 4d892e2c7d18..e01e0edb409f 100644 --- a/clang/test/3C/generated_tests/unsafefptrargprotosafe.c +++ b/clang/test/3C/generated_tests/unsafefptrargprotosafe.c @@ -104,15 +104,15 @@ int *mul2(int *x) { } int *sus(int (*)(int), int (*)(int)); -//CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr); -//CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y) : count(5); +//CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr); +//CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : count(5); int *foo() { //CHECK_NOALL: _Ptr foo(void) { //CHECK_ALL: _Array_ptr foo(void) : count(5) { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = mul2; //CHECK: int (*y)(int) = mul2; int *z = sus(x, y); @@ -127,7 +127,7 @@ int *bar() { //CHECK_ALL: _Array_ptr bar(void) : count(5) { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = mul2; //CHECK: int (*y)(int) = mul2; int *z = sus(x, y); @@ -138,8 +138,8 @@ int *bar() { } int *sus(int (*x)(int), int (*y)(int)) { - //CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr) { - //CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y) : count(5) { + //CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr) { + //CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : count(5) { x = (int (*)(int))5; //CHECK: x = (int (*)(int))5; diff --git a/clang/test/3C/generated_tests/unsafefptrargsafe.c b/clang/test/3C/generated_tests/unsafefptrargsafe.c index ad7ef51b9a42..2db8745bc8c7 100644 --- a/clang/test/3C/generated_tests/unsafefptrargsafe.c +++ b/clang/test/3C/generated_tests/unsafefptrargsafe.c @@ -100,8 +100,8 @@ int *mul2(int *x) { } int *sus(int (*x)(int), int (*y)(int)) { - //CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr) { - //CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y) : count(5) { + //CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr) { + //CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : count(5) { x = (int (*)(int))5; //CHECK: x = (int (*)(int))5; @@ -123,7 +123,7 @@ int *foo() { //CHECK_ALL: _Array_ptr foo(void) : count(5) { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = mul2; //CHECK: int (*y)(int) = mul2; int *z = sus(x, y); @@ -138,7 +138,7 @@ int *bar() { //CHECK_ALL: _Array_ptr bar(void) : count(5) { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = mul2; //CHECK: int (*y)(int) = mul2; int *z = sus(x, y); diff --git a/clang/test/3C/generated_tests/unsafefptrargsafemulti1.c b/clang/test/3C/generated_tests/unsafefptrargsafemulti1.c index da3f7c97c732..ddaddf1e9142 100644 --- a/clang/test/3C/generated_tests/unsafefptrargsafemulti1.c +++ b/clang/test/3C/generated_tests/unsafefptrargsafemulti1.c @@ -107,15 +107,15 @@ static int *mul2(int *x) { } int *sus(int (*)(int), int (*)(int)); -//CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr); -//CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y) : count(5); +//CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr); +//CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : count(5); int *foo() { //CHECK_NOALL: _Ptr foo(void) { //CHECK_ALL: _Array_ptr foo(void) : count(5) { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = mul2; //CHECK: int (*y)(int) = mul2; int *z = sus(x, y); @@ -130,7 +130,7 @@ int *bar() { //CHECK_ALL: _Array_ptr bar(void) : count(5) { int (*x)(int) = add1; - //CHECK: int (*x)(int) = add1; + //CHECK: _Ptr x = add1; int (*y)(int) = mul2; //CHECK: int (*y)(int) = mul2; int *z = sus(x, y); diff --git a/clang/test/3C/generated_tests/unsafefptrargsafemulti2.c b/clang/test/3C/generated_tests/unsafefptrargsafemulti2.c index f11ae9ddb52f..bacc7d11b6fc 100644 --- a/clang/test/3C/generated_tests/unsafefptrargsafemulti2.c +++ b/clang/test/3C/generated_tests/unsafefptrargsafemulti2.c @@ -107,8 +107,8 @@ static int *mul2(int *x) { } int *sus(int (*x)(int), int (*y)(int)) { - //CHECK_NOALL: int *sus(int (*x)(int), _Ptr y) : itype(_Ptr) { - //CHECK_ALL: _Array_ptr sus(int (*x)(int), _Ptr y) : count(5) { + //CHECK_NOALL: int *sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : itype(_Ptr) { + //CHECK_ALL: _Array_ptr sus(int ((*x)(int)) : itype(_Ptr), _Ptr y) : count(5) { x = (int (*)(int))5; //CHECK: x = (int (*)(int))5; diff --git a/clang/test/3C/liberal_itypes_fp.c b/clang/test/3C/liberal_itypes_fp.c index 677823f40095..0b6f93088148 100644 --- a/clang/test/3C/liberal_itypes_fp.c +++ b/clang/test/3C/liberal_itypes_fp.c @@ -75,7 +75,7 @@ void fp_unsafe_return() { } void f_ptr_arg(int (*f)()) { - // CHECK: void f_ptr_arg(int (*f)()) { + // CHECK: void f_ptr_arg(int ((*f)(void)) : itype(_Ptr)) { f = 1; } @@ -86,7 +86,7 @@ void fpnc1(void *p1) {} void fpnc2() { fpnc0(fpnc1); } // CHECK: void fpnc2() { fpnc0(fpnc1); } void fpnc3(void (*fptr)(void *)) { fptr = 1; } -// CHECK: void fpnc3(void (*fptr)(void *)) { fptr = 1; } +// CHECK: void fpnc3(void ((*fptr)(void *)) : itype(_Ptr)) { fptr = 1; } void fpnc4(void *p1) {} // CHECK: void fpnc4(void *p1) {} void fpnc5() { fpnc3(fpnc4); } diff --git a/clang/test/3C/mergebodies1.c b/clang/test/3C/mergebodies1.c index 8343e4944af5..81e729fa8816 100644 --- a/clang/test/3C/mergebodies1.c +++ b/clang/test/3C/mergebodies1.c @@ -30,7 +30,7 @@ int *wildBody(int x) { }; int *fnPtrParamUnsafe(int (*)(int,int), int(*)(int*)); -// CHECK: int *fnPtrParamUnsafe(int (*fn)(int, int), _Ptr)>) : itype(_Ptr); +// CHECK: int *fnPtrParamUnsafe(int ((*fn)(int, int)) : itype(_Ptr), _Ptr)>) : itype(_Ptr); int *fnPtrParamSafe(int (*)(int,int)); // CHECK: int *fnPtrParamSafe(_Ptr fn) : itype(_Ptr); diff --git a/clang/test/3C/mergebodies2.c b/clang/test/3C/mergebodies2.c index d48513a386c0..c620754b7b17 100644 --- a/clang/test/3C/mergebodies2.c +++ b/clang/test/3C/mergebodies2.c @@ -24,7 +24,7 @@ int *wildBody(int x, int *y, int **z); // CHECK: int *wildBody(int x, _Ptr y, _Ptr<_Ptr> z) : itype(_Ptr); int *fnPtrParamUnsafe(int (*fn)(int,int)) { -// CHECK: int *fnPtrParamUnsafe(int (*fn)(int,int)) : itype(_Ptr) { +// CHECK: int *fnPtrParamUnsafe(int ((*fn)(int, int)) : itype(_Ptr)) : itype(_Ptr) { fn = (int (*)(int,int))5; return fn(1,3); } From 556eaf378ac4ef46dd49a733432af7a305f75fbf Mon Sep 17 00:00:00 2001 From: John Kastner Date: Fri, 2 Jul 2021 17:23:23 -0400 Subject: [PATCH 04/38] Add a flag to always add itypes on function prototypes (#637) This is the implementation of the -itypes-for-externs flag used in the vsftpd tutorial. The flag enables a new mode where 3C will emit itypes for all checked external functions. This should let the headers files containing prototypes for these function be used indecently from the converted function bodies, which is intended to enable conversion of individual files in a program without committing to partial conversion of every file. This change also adds code to support emitting itypes of structure fields. For now, this is only done when the flag is supplied, but we might like to support solving these itypes in the future. Similarly, it adds support for itypes on parameter's with typedef'ed types. The typedef is left unchecked, but they are given a checked itype. Again, this is only done when the new flag is passed, but we might consider using itypes like this in normal operation of 3C. --- clang/include/clang/3C/3C.h | 2 + clang/include/clang/3C/3CGlobalOptions.h | 1 + clang/include/clang/3C/DeclRewriter.h | 7 +- clang/lib/3C/3C.cpp | 2 + clang/lib/3C/DeclRewriter.cpp | 127 +++++++++++++++++------ clang/test/3C/itypes_for_extern.c | 94 +++++++++++++++++ clang/tools/3c/3CStandalone.cpp | 9 ++ 7 files changed, 207 insertions(+), 35 deletions(-) create mode 100644 clang/test/3C/itypes_for_extern.c diff --git a/clang/include/clang/3C/3C.h b/clang/include/clang/3C/3C.h index 5352188ddbdb..543fb97dbfd4 100644 --- a/clang/include/clang/3C/3C.h +++ b/clang/include/clang/3C/3C.h @@ -72,6 +72,8 @@ struct _3COptions { bool AllowUnwritableChanges; bool AllowRewriteFailures; + + bool ItypesForExtern; }; // The main interface exposed by the 3C to interact with the tool. diff --git a/clang/include/clang/3C/3CGlobalOptions.h b/clang/include/clang/3C/3CGlobalOptions.h index ae5b38505e89..08a038f97f70 100644 --- a/clang/include/clang/3C/3CGlobalOptions.h +++ b/clang/include/clang/3C/3CGlobalOptions.h @@ -33,6 +33,7 @@ extern bool WarnAllRootCause; extern bool DumpUnwritableChanges; extern bool AllowUnwritableChanges; extern bool AllowRewriteFailures; +extern bool ItypesForExtern; #ifdef FIVE_C extern bool RemoveItypes; diff --git a/clang/include/clang/3C/DeclRewriter.h b/clang/include/clang/3C/DeclRewriter.h index cee885835b24..7b69a2077227 100644 --- a/clang/include/clang/3C/DeclRewriter.h +++ b/clang/include/clang/3C/DeclRewriter.h @@ -35,6 +35,11 @@ class DeclRewriter { // Info parameter are rewritten. static void rewriteDecls(ASTContext &Context, ProgramInfo &Info, Rewriter &R); + static void + buildItypeDecl(PVConstraint *Defn, DeclaratorDecl *Decl, std::string &Type, + std::string &IType, ProgramInfo &Info, + ArrayBoundsRewriter &ABR); + private: static RecordDecl *LastRecordDecl; static std::map VDToRDMap; @@ -104,7 +109,7 @@ class FunctionDeclBuilder : public RecursiveASTVisitor { virtual void buildDeclVar(const FVComponentVariable *CV, DeclaratorDecl *Decl, std::string &Type, std::string &IType, std::string UseName, bool &RewriteParm, - bool &RewriteRet); + bool &RewriteRet, bool StaticFunc); void buildCheckedDecl(PVConstraint *Defn, DeclaratorDecl *Decl, std::string &Type, std::string &IType, std::string UseName, bool &RewriteParm, diff --git a/clang/lib/3C/3C.cpp b/clang/lib/3C/3C.cpp index bb7948bc48d3..73096652eb98 100644 --- a/clang/lib/3C/3C.cpp +++ b/clang/lib/3C/3C.cpp @@ -59,6 +59,7 @@ std::set FilePaths; bool DumpUnwritableChanges; bool AllowUnwritableChanges; bool AllowRewriteFailures; +bool ItypesForExtern; #ifdef FIVE_C bool RemoveItypes; @@ -354,6 +355,7 @@ _3CInterface::_3CInterface(const struct _3COptions &CCopt, DumpUnwritableChanges = CCopt.DumpUnwritableChanges; AllowUnwritableChanges = CCopt.AllowUnwritableChanges; AllowRewriteFailures = CCopt.AllowRewriteFailures; + ItypesForExtern = CCopt.ItypesForExtern; #ifdef FIVE_C RemoveItypes = CCopt.RemoveItypes; diff --git a/clang/lib/3C/DeclRewriter.cpp b/clang/lib/3C/DeclRewriter.cpp index 359ead632f61..193d4cbdc19e 100644 --- a/clang/lib/3C/DeclRewriter.cpp +++ b/clang/lib/3C/DeclRewriter.cpp @@ -27,6 +27,54 @@ using namespace llvm; using namespace clang; +// Generate a new declaration for the PVConstraint using an itype where the +// unchecked portion of the type is the original type, and the checked portion +// is the taken from the constraint graph solution. The unchecked portion is +// assigned to string reference Type and the checked (itype) portion is assigned +// to the string reference Itype. +void DeclRewriter::buildItypeDecl(PVConstraint *Defn, DeclaratorDecl *Decl, + std::string &Type, std::string &IType, + ProgramInfo &Info, ArrayBoundsRewriter &ABR) { + if (Defn->getFV()) { + // This declaration is for a function pointer. Writing itypes on function + // pointers is a little bit harder since the original type string will not + // work for the unchecked portion of the itype. We need to generate the + // unchecked type from the PVConstraint. The last argument of this call + // tells mkString to generate a string using unchecked types instead of + // checked types. + Type = Defn->mkString(Info.getConstraints(), true, false, false, false, "", + true); + } else { + Type = Defn->getRewritableOriginalTy(); + if (isa_and_nonnull(Decl)) { + if (Decl->getName().empty()) + Type += Defn->getName(); + else + Type += Decl->getNameAsString(); + } else { + std::string Name = Defn->getName(); + if (Name != RETVAR) + Type += Name; + } + } + + IType = " : itype("; + if (ItypesForExtern && Defn->isTypedef()) { + // In -itypes-for-extern mode we do not rewrite typedefs to checked types. + // They are given a checked itype instead. The unchecked portion of the + // itype continues to use the original typedef, but the typedef in the + // checked portion is expanded and rewritten to use a checked type. This + // lets the typedef be used in unchecked code while still giving a checked + // type to the declaration so that it can be used in checked code. + // TODO: This could potentially be applied to typedef types even when the + // flag is not passed to limit spread of wildness through typedefs. + IType += Defn->mkString(Info.getConstraints(), false, true, false, true); + } else { + IType += Defn->mkString(Info.getConstraints(), false, true); + } + IType += ")" + ABR.getBoundsString(Defn, Decl, true); +} + // This function is the public entry point for declaration rewriting. void DeclRewriter::rewriteDecls(ASTContext &Context, ProgramInfo &Info, Rewriter &R) { @@ -50,7 +98,11 @@ void DeclRewriter::rewriteDecls(ASTContext &Context, ProgramInfo &Info, for (const auto &D : Context.getTranslationUnitDecl()->decls()) { TRV->TraverseDecl(D); SVI.TraverseDecl(D); - if (const auto &TD = dyn_cast(D)) { + const auto &TD = dyn_cast(D); + // Don't convert typedefs when -itype-for-extern is passed. Typedefs will + // keep their unchecked type but function using the typedef will be given a + // checked itype. + if (!ItypesForExtern && TD) { auto PSL = PersistentSourceLoc::mkPSL(TD, Context); // Don't rewrite base types like int if (!TD->getUnderlyingType()->isBuiltinType()) { @@ -114,9 +166,30 @@ void DeclRewriter::rewriteDecls(ASTContext &Context, ProgramInfo &Info, if (VDLToStmtMap.find(D) != VDLToStmtMap.end()) DS = VDLToStmtMap[D]; - std::string NewTy = getStorageQualifierString(D) + - PV->mkString(Info.getConstraints()) + - ABRewriter.getBoundsString(PV, D); + std::string NewTy = getStorageQualifierString(D); + bool IsExternGlobalVar = + isa(D) && + cast(D)->getFormalLinkage() == Linkage::ExternalLinkage; + if (ItypesForExtern && (isa(D) || IsExternGlobalVar)) { + // Give record fields and global variables itypes when using + // -itypes-for-extern. Note that we haven't properly implemented + // itypes for structures and globals. This just rewrites to an itype + // instead of a fully checked type when a checked type could have been + // used. This does provide most of the rewriting infrastructure that + // would be required to support these itypes if constraint generation + // is updated to handle structure/global itypes. + std::string Type, IType; + // VarDecl and FieldDecl subclass DeclaratorDecl, so the cast will + // always succeed. In fact, ParmVarDecl is also a subclass of + // DeclaratorDecl, so it should be possible to make the values in + // PSLMap DeclaratorDecls and avoid this cast altogether. + DeclRewriter::buildItypeDecl(PV, cast(D), Type, IType, + Info, ABRewriter); + NewTy += Type + IType; + } else { + NewTy += PV->mkString(Info.getConstraints()) + + ABRewriter.getBoundsString(PV, D); + } if (auto *VD = dyn_cast(D)) RewriteThese.insert(new VarDeclReplacement(VD, DS, NewTy)); else if (auto *FD = dyn_cast(D)) @@ -569,7 +642,7 @@ bool FunctionDeclBuilder::VisitFunctionDecl(FunctionDecl *FD) { std::string Type, IType; this->buildDeclVar(CV, PVDecl, Type, IType, PVDecl->getQualifiedNameAsString(), RewriteParams, - RewriteReturn); + RewriteReturn, FD->isStatic()); ParmStrs.push_back(Type + IType); } } else if (FDConstraint->numParams() != 0) { @@ -579,7 +652,7 @@ bool FunctionDeclBuilder::VisitFunctionDecl(FunctionDecl *FD) { const FVComponentVariable *CV = FDConstraint->getCombineParam(I); std::string Type, IType; this->buildDeclVar(CV, PVDecl, Type, IType, "", RewriteParams, - RewriteReturn); + RewriteReturn, FD->isStatic()); ParmStrs.push_back(Type + IType); // FIXME: when the above FIXME is changed this condition will always // be true. This is correct, always rewrite if there were no params @@ -601,7 +674,7 @@ bool FunctionDeclBuilder::VisitFunctionDecl(FunctionDecl *FD) { // For now we still need to check if this needs rewriting, see FIXME below // if (!DeclIsTypedef) this->buildDeclVar(FDConstraint->getCombineReturn(), FD, ReturnVar, ItypeStr, - "", RewriteParams, RewriteReturn); + "", RewriteParams, RewriteReturn, FD->isStatic()); // If the return is a function pointer, we need to rewrite the whole // declaration even if no actual changes were made to the parameters because @@ -672,31 +745,13 @@ void FunctionDeclBuilder::buildCheckedDecl( RewriteRet |= isa_and_nonnull(Decl); } + void FunctionDeclBuilder::buildItypeDecl(PVConstraint *Defn, DeclaratorDecl *Decl, std::string &Type, std::string &IType, bool &RewriteParm, bool &RewriteRet) { Info.getPerfStats().incrementNumITypes(); - if (Defn->getFV()) { - // The result of getFV() is not nullptr, so the declaration is for a - // function pointer. This makes rewriting with an itype a bit harder. - Type = Defn->mkString(Info.getConstraints(), true, false, false, false, "", - true); - } else { - Type = Defn->getRewritableOriginalTy(); - if (isa_and_nonnull(Decl)) { - if (Decl->getName().empty()) - Type += Defn->getName(); - else - Type += Decl->getQualifiedNameAsString(); - } else { - std::string Name = Defn->getName(); - if (Name != RETVAR) - Type += Name; - } - } - IType = " : itype(" + Defn->mkString(Info.getConstraints(), false, true) + - ")" + ABRewriter.getBoundsString(Defn, Decl, true); + DeclRewriter::buildItypeDecl(Defn, Decl, Type, IType, Info, ABRewriter); RewriteParm = true; RewriteRet |= isa_and_nonnull(Decl); } @@ -708,17 +763,21 @@ void FunctionDeclBuilder::buildItypeDecl(PVConstraint *Defn, void FunctionDeclBuilder::buildDeclVar(const FVComponentVariable *CV, DeclaratorDecl *Decl, std::string &Type, std::string &IType, std::string UseName, - bool &RewriteParm, bool &RewriteRet) { - if (CV->hasCheckedSolution(Info.getConstraints())) { - buildCheckedDecl(CV->getExternal(), Decl, Type, IType, UseName, RewriteParm, - RewriteRet); - return; - } - if (CV->hasItypeSolution(Info.getConstraints())) { + bool &RewriteParm, bool &RewriteRet, + bool StaticFunc) { + + bool CheckedSolution = CV->hasCheckedSolution(Info.getConstraints()); + bool ItypeSolution = CV->hasItypeSolution(Info.getConstraints()); + if (ItypeSolution || (CheckedSolution && ItypesForExtern && !StaticFunc)) { buildItypeDecl(CV->getExternal(), Decl, Type, IType, RewriteParm, RewriteRet); return; } + if (CheckedSolution) { + buildCheckedDecl(CV->getExternal(), Decl, Type, IType, UseName, RewriteParm, + RewriteRet); + return; + } // If the type of the pointer hasn't changed, then neither of the above // branches will be taken, but it's still possible for the bounds of an array diff --git a/clang/test/3C/itypes_for_extern.c b/clang/test/3C/itypes_for_extern.c new file mode 100644 index 000000000000..ee5dede2c5f9 --- /dev/null +++ b/clang/test/3C/itypes_for_extern.c @@ -0,0 +1,94 @@ +// RUN: rm -rf %t* +// RUN: 3c -base-dir=%S -itypes-for-extern -alltypes -addcr %s -- | FileCheck -match-full-lines -check-prefixes="CHECK_ALL","CHECK" %s +// RUN: 3c -base-dir=%S -itypes-for-extern -addcr %s -- | FileCheck -match-full-lines -check-prefixes="CHECK_NOALL","CHECK" %s +// RUN: 3c -base-dir=%S -itypes-for-extern -alltypes -addcr %s -- | %clang -c -fcheckedc-extension -x c -o /dev/null - +// RUN: 3c -base-dir=%S -itypes-for-extern -alltypes -output-dir=%t.checked %s -- +// RUN: 3c -base-dir=%t.checked -itypes-for-extern -alltypes %t.checked/itypes_for_extern.c -- | diff %t.checked/itypes_for_extern.c - + +// Simplest test case: a would normally get a checked type, but is given an +// itype because of the flag. +void foo(int *a) {} +//CHECK: void foo(int *a : itype(_Ptr)) _Checked {} + +// Since static function can't be included in other translation units, these +// can stay fully checked. +static void static_foo(int *a) {} +//CHECK: static void static_foo(_Ptr a) _Checked {} + +// Don't give a function an itype if it wouldn't normally be checked +void undef_foo(int *a); +//CHECK: void undef_foo(int *a); + +int *bar() { return 0; } +//CHECK: int *bar(void) : itype(_Ptr) _Checked { return 0; } + +int *baz(int *a, int len, int *b) { +//CHECK_ALL: int *baz(int *a : itype(_Array_ptr) count(len), int len, int *b : itype(_Ptr)) : itype(_Array_ptr) count(len) _Checked { +//CHECK_NOALL: int *baz(int *a : itype(_Ptr), int len, int *b : itype(_Ptr)) : itype(_Ptr) { + for (int i = 0; i < len; i++) + a[i]; + return a; +} + +// FIXME: The space between after the first star is needed for the idempotence +// test to pass. If there isn't a space there, 3c will add one when +// re-run on its original output. This should be fixed ideally in two +// ways. First, the space shouldn't be added when not present in the +// original source, and second, the second conversion should not rewrite +// the declaration. +void buz(int * (*f)(int *, int *)) {} +//CHECK: void buz(int * ((*f)(int *, int *)) : itype(_Ptr<_Ptr (_Ptr, _Ptr)>)) _Checked {} + +typedef int * int_star; +void typedef_test(int_star p) {} +//CHECK: typedef int * int_star; +//CHECK: void typedef_test(int_star p : itype(_Ptr)) _Checked {} + +typedef void (*fn)(int *); +void fn_typedef_test(fn f) {} +//CHECK: typedef void (*fn)(int *); +//CHECK: void fn_typedef_test(fn f : itype(_Ptr)>)) _Checked {} + +struct foo { + int *a; + void (*fn)(int *); +}; +//CHECK: int *a : itype(_Ptr); +//CHECK: void ((*fn)(int *)) : itype(_Ptr)>); + +int *glob = 0; +extern int *extern_glob = 0; +static int *static_glob = 0; +//CHECK: int *glob : itype(_Ptr) = 0; +//CHECK: extern int *extern_glob : itype(_Ptr) = 0; +//CHECK: static _Ptr static_glob = 0; + +void (*glob_fn)(int *) = 0; +extern void (*extern_glob_fn)(int *) = 0; +static void (*static_glob_fn)(int *) = 0; +//CHECK: void ((*glob_fn)(int *)) : itype(_Ptr)>) = 0; +//CHECK: extern void ((*extern_glob_fn)(int *)) : itype(_Ptr)>) = 0; +//CHECK: static _Ptr)> static_glob_fn = 0; + +int_star typedef_glob = 0; +fn typedef_fn_glob = 0; +//CHECK: int_star typedef_glob : itype(_Ptr) = 0; +//CHECK: fn typedef_fn_glob : itype(_Ptr)>) = 0; + +struct typedef_struct { + int_star a; + fn f; +}; +//CHECK: int_star a : itype(_Ptr); +//CHECK: fn f : itype(_Ptr)>); + +// Testing some cases where the itypes already exist. Earlier we made a change +// that lets itypes re-solve to checked types. That shouldn't happen with this +// flag. This is also covered by the idempotence check, but I wanted to make it +// explicit. + +void has_itype0(int *a : itype(_Ptr)) { a = 1; } +//CHECK: void has_itype0(int *a : itype(_Ptr)) { a = 1; } + +void has_itype1(int *a : itype(_Ptr)) { a = 0; } +//CHECK: void has_itype1(int *a : itype(_Ptr)) _Checked { a = 0; } diff --git a/clang/tools/3c/3CStandalone.cpp b/clang/tools/3c/3CStandalone.cpp index 120bccdf3192..67befeab9dd1 100644 --- a/clang/tools/3c/3CStandalone.cpp +++ b/clang/tools/3c/3CStandalone.cpp @@ -227,6 +227,14 @@ static cl::opt OptAllowRewriteFailures( "affect common use cases."), cl::init(false), cl::cat(_3CCategory)); +static cl::opt OptItypesForExtern( + "itypes-for-extern", + cl::desc("All functions with external linkage will be rewritten to use itypes" + "instead checked types. This does not apply to static functions which" + "continue to have itypes only when the function is internally" + "unsafe."), + cl::init(false), cl::cat(_3CCategory)); + #ifdef FIVE_C static cl::opt OptRemoveItypes( "remove-itypes", @@ -292,6 +300,7 @@ int main(int argc, const char **argv) { CcOptions.DumpUnwritableChanges = OptDumpUnwritableChanges; CcOptions.AllowUnwritableChanges = OptAllowUnwritableChanges; CcOptions.AllowRewriteFailures = OptAllowRewriteFailures; + CcOptions.ItypesForExtern = OptItypesForExtern; #ifdef FIVE_C CcOptions.RemoveItypes = OptRemoveItypes; From d2452931dfca47afffaa3cc039242d08cc1661ed Mon Sep 17 00:00:00 2001 From: John Kastner Date: Tue, 6 Jul 2021 12:30:14 -0400 Subject: [PATCH 05/38] Remove implication constraints and replace with GEQ constraints (#513) The constraints we created with implications can easily be encoded with Geq constraints. Instead of generating an implication constraint `(p_0 >= WILD) ==> (p_1 >= WILD)` a single constraint `p_1 >= p_0` is generated. This has the same outcome of forcing p_1 to be unchecked whenever p_0 is unchecked. Doing this lets us simplify the solving algorithm by removing the outer loop for firing implication constraints. This should also makes the constraint graph more easily interpreted because the relationship between the atoms of a pointer is shown explicitly by checked type edges between those atoms. --- clang/include/clang/3C/Constraints.h | 63 +----------- clang/lib/3C/ConstraintVariables.cpp | 49 ++++----- clang/lib/3C/Constraints.cpp | 146 +++++++-------------------- clang/lib/3C/ProgramInfo.cpp | 18 ---- 4 files changed, 53 insertions(+), 223 deletions(-) diff --git a/clang/include/clang/3C/Constraints.h b/clang/include/clang/3C/Constraints.h index 18a5b1025701..278878736ac1 100644 --- a/clang/include/clang/3C/Constraints.h +++ b/clang/include/clang/3C/Constraints.h @@ -8,7 +8,6 @@ // // This implements a simple constraint solver for expressions of the form: // a >= b -// a implies b // // The 3C tool performs type inference to identify locations // where a C type might be replaced with a Checked C type. This interface @@ -251,10 +250,9 @@ class WildAtom : public ConstAtom { // Represents constraints of the form: // - a >= b -// - a ==> b class Constraint { public: - enum ConstraintKind { C_Geq, C_Imp }; + enum ConstraintKind { C_Geq }; private: const ConstraintKind Kind; @@ -382,61 +380,6 @@ class Geq : public Constraint { bool IsSoft; }; -// a ==> b -class Implies : public Constraint { -public: - Implies(Geq *Premise, Geq *Conclusion) - : Constraint(C_Imp), Premise(Premise), Conclusion(Conclusion) {} - - static bool classof(const Constraint *C) { return C->getKind() == C_Imp; } - - Geq *getPremise() { return Premise; } - Geq *getConclusion() { return Conclusion; } - - void print(llvm::raw_ostream &O) const { - Premise->print(O); - O << " ==> "; - Conclusion->print(O); - } - - void dump(void) const { print(llvm::errs()); } - - void dumpJson(llvm::raw_ostream &O) const { - O << "{\"Implies\":{\"Premise\":"; - Premise->dumpJson(O); - O << ", \"Conclusion\":"; - Conclusion->dumpJson(O); - O << "}}"; - } - - bool operator==(const Constraint &Other) const { - if (const Implies *I = llvm::dyn_cast(&Other)) - return *Premise == *I->Premise && *Conclusion == *I->Conclusion; - return false; - } - - bool operator!=(const Constraint &Other) const { return !(*this == Other); } - - bool operator<(const Constraint &Other) const { - ConstraintKind K = Other.getKind(); - if (K == C_Imp) { - const Implies *I = llvm::dyn_cast(&Other); - assert(I != nullptr); - - if (*Premise == *I->Premise && *Conclusion == *I->Conclusion) - return false; - if (*Premise == *I->Premise && *Conclusion != *I->Conclusion) - return *Conclusion < *I->Conclusion; - return *Premise < *I->Premise; - } - return C_Imp < K; - } - -private: - Geq *Premise; - Geq *Conclusion; -}; - // This is the solution, the first item is Checked Solution and the second // is Ptr solution. typedef std::pair VarSolTy; @@ -513,7 +456,6 @@ class Constraints { bool IsCheckedConstraint = true); Geq *createGeq(Atom *Lhs, Atom *Rhs, const std::string &Rsn, PersistentSourceLoc *PL, bool IsCheckedConstraint = true); - Implies *createImplies(Geq *Premise, Geq *Conclusion); VarAtom *createFreshGEQ(std::string Name, VarAtom::VarKind VK, ConstAtom *Con, std::string Rsn = DEFAULT_REASON, @@ -547,9 +489,6 @@ class Constraints { std::map ConstraintsByReason; ConstraintsEnv Environment; - // Confirm a constraint is well-formed - bool check(Constraint *C); - // Managing constraints based on the underlying reason. // add constraint to the map. bool addReasonBasedConstraint(Constraint *C); diff --git a/clang/lib/3C/ConstraintVariables.cpp b/clang/lib/3C/ConstraintVariables.cpp index 34e83ac82c2e..ab814ebbf6db 100644 --- a/clang/lib/3C/ConstraintVariables.cpp +++ b/clang/lib/3C/ConstraintVariables.cpp @@ -89,15 +89,14 @@ PointerVariableConstraint *PointerVariableConstraint::addAtomPVConstraint( CS.addConstraint(CS.createGeq(NewA, PtrTyp, false)); std::vector Vars = PVC->Vars; std::vector SrcVars = PVC->SrcVars; - if (!Vars.empty()) { - if (auto *VA = dyn_cast(*Vars.begin())) { - // If PVC is already a pointer, add implication forcing outermost one to - // be wild if this added one is. - auto *Prem = CS.createGeq(NewA, CS.getWild()); - auto *Conc = CS.createGeq(VA, CS.getWild()); - CS.addConstraint(CS.createImplies(Prem, Conc)); - } - } + + // Add a constraint between the new atom and any existing atom for this + // pointer. This is the same constraint that is added between atoms of a + // pointer in the PointerVariableConstraint constructor. It forces all inner + // atoms to be wild if an outer atom in wild. + if (!Vars.empty()) + if (auto *VA = dyn_cast(*Vars.begin())) + CS.addConstraint(new Geq(VA, NewA)); Vars.insert(Vars.begin(), NewA); SrcVars.insert(SrcVars.begin(), PtrTyp); @@ -532,28 +531,13 @@ PointerVariableConstraint::PointerVariableConstraint( getQualString(TypeIdx, QualStr); BaseType = QualStr.str() + BaseType; - // Here lets add implication that if outer pointer is WILD - // then make the inner pointers WILD too. + // If an outer pointer is wild, then the inner pointer must also be wild. if (Vars.size() > 1) { - bool UsedPrGeq = false; - for (auto VI = Vars.begin(), VE = Vars.end(); VI != VE; VI++) { - if (VarAtom *VIVar = dyn_cast(*VI)) { - // Premise. - Geq *PrGeq = new Geq(VIVar, CS.getWild()); - UsedPrGeq = false; - for (auto VJ = (VI + 1); VJ != VE; VJ++) { - if (VarAtom *VJVar = dyn_cast(*VJ)) { - // Conclusion. - Geq *CoGeq = new Geq(VJVar, CS.getWild()); - CS.addConstraint(CS.createImplies(PrGeq, CoGeq)); - UsedPrGeq = true; - } - } - // Delete unused constraint. - if (!UsedPrGeq) { - delete (PrGeq); - } - } + for (unsigned VarIdx = 0; VarIdx < Vars.size() - 1; VarIdx++) { + VarAtom *VI = dyn_cast(Vars[VarIdx]); + VarAtom *VJ = dyn_cast(Vars[VarIdx + 1]); + if (VI && VJ) + CS.addConstraint(new Geq(VJ, VI)); } } } @@ -1236,8 +1220,9 @@ void PointerVariableConstraint::constrainToWild(Constraints &CS, break; } - // Constrains the outer VarAtom to WILD. Inner pointer levels are - // implicitly WILD because of implication constraints. + // Constrains the outer VarAtom to WILD. All inner pointer levels will also be + // implicitly constrained to WILD because of GEQ constraints that exist + // between levels of a pointer. if (FirstVA) CS.addConstraint(CS.createGeq(FirstVA, CS.getWild(), Rsn, PL, true)); diff --git a/clang/lib/3C/Constraints.cpp b/clang/lib/3C/Constraints.cpp index c9e6c36da121..9fdda11c83d1 100644 --- a/clang/lib/3C/Constraints.cpp +++ b/clang/lib/3C/Constraints.cpp @@ -94,12 +94,6 @@ void Constraints::editConstraintHook(Constraint *C) { // Add a constraint to the set of constraints. If the constraint is already // present (by syntactic equality) return false. bool Constraints::addConstraint(Constraint *C) { - // Validate the constraint to be added. - if (!check(C)) { - C->dump(); - assert(false); - } - editConstraintHook(C); // Check if C is already in the set of constraints. @@ -122,10 +116,6 @@ bool Constraints::addConstraint(Constraint *C) { else if (VarAtom *VRhs = dyn_cast(E->getRHS())) { VRhs->Constraints.insert(C); } - } else if (Implies *I = dyn_cast(C)) { - Geq *PEQ = I->getPremise(); - if (VarAtom *VLhs = dyn_cast(PEQ->getLHS())) - VLhs->Constraints.insert(C); } else llvm_unreachable("unsupported constraint"); return true; @@ -153,27 +143,6 @@ bool Constraints::removeReasonBasedConstraint(Constraint *C) { return false; } -// Checks to see if the constraint is of a form that we expect. -// The expected forms are the following: -// EQ : (q_i = q_k) -// GEQ : (q_i >= A) for A constant -// IMPLIES : (q_i >= A) => (q_k >= B) for A,B constant. -bool Constraints::check(Constraint *C) { - - if (Implies *I = dyn_cast(C)) { - Geq *P = I->getPremise(); - Geq *CO = I->getConclusion(); - if (!isa(P->getLHS()) || isa(P->getRHS()) || - !isa(CO->getLHS()) || isa(CO->getRHS())) - return false; - } else if (dyn_cast(C) != nullptr) { - // all good! - } else - return false; // Not Eq, Geq, or Implies; what is it?! - - return true; -} - // Make a graph G: //- with nodes for each variable k and each qualifier constant q. //- with edges Q --> Q’ for each constraint Q <: Q’ @@ -195,73 +164,45 @@ bool Constraints::check(Constraint *C) { static bool doSolve(ConstraintsGraph &CG, - std::set SavedImplies, // TODO: Can this be a ref? ConstraintsEnv &Env, Constraints *CS, bool DoLeastSolution, std::set *InitVs, std::set &Conflicts) { std::vector WorkList; - std::set FiredImplies; // Initialize with seeded VarAtom set (pre-solved). if (InitVs != nullptr) WorkList.insert(WorkList.begin(), InitVs->begin(), InitVs->end()); - do { - // Initialize work list with ConstAtoms. - auto &InitC = CG.getAllConstAtoms(); - WorkList.insert(WorkList.begin(), InitC.begin(), InitC.end()); - - while (!WorkList.empty()) { - auto *Curr = *(WorkList.begin()); - // Remove the first element, get its solution. - WorkList.erase(WorkList.begin()); - ConstAtom *CurrSol = Env.getAssignment(Curr); - - // get its neighbors. - std::set Neighbors; - CG.getNeighbors(Curr, Neighbors, DoLeastSolution); - // update each successor's solution. - for (auto *NeighborA : Neighbors) { - bool Changed = false; - if (VarAtom *Neighbor = dyn_cast(NeighborA)) { - ConstAtom *NghSol = Env.getAssignment(Neighbor); - // update solution if doing so would change it - // checked? --- if sol(Neighbor) <> (sol(Neighbor) JOIN Cur) - // else --- if sol(Neighbor) <> (sol(Neighbor) MEET Cur) - if ((DoLeastSolution && *NghSol < *CurrSol) || - (!DoLeastSolution && *CurrSol < *NghSol)) { - // ---- set sol(k) := (sol(k) JOIN/MEET Q) - Changed = Env.assign(Neighbor, CurrSol); - assert(Changed); - WorkList.push_back(Neighbor); - } - } // ignore ConstAtoms for now; will confirm solution below - } - } - FiredImplies.clear(); - - // If there are some implications that we saved? Propagate them. - if (!SavedImplies.empty()) { - // Check if Premise holds. If yes then fire the conclusion. - for (auto *Imp : SavedImplies) { - Geq *Pre = Imp->getPremise(); - Geq *Con = Imp->getConclusion(); - ConstAtom *Cca = Env.getAssignment(Pre->getRHS()); - ConstAtom *Cva = Env.getAssignment(Pre->getLHS()); - // Premise is true, so fire the conclusion. - if (*Cca < *Cva || *Cca == *Cva) { - CG.addConstraint(Con, *CS); - // Keep track of fired constraints, so that we can delete them. - FiredImplies.insert(Imp); + // Initialize work list with ConstAtoms. + auto &InitC = CG.getAllConstAtoms(); + WorkList.insert(WorkList.begin(), InitC.begin(), InitC.end()); + + while (!WorkList.empty()) { + auto *Curr = *(WorkList.begin()); + // Remove the first element, get its solution. + WorkList.erase(WorkList.begin()); + ConstAtom *CurrSol = Env.getAssignment(Curr); + + // get its neighbors. + std::set Neighbors; + CG.getNeighbors(Curr, Neighbors, DoLeastSolution); + // update each successor's solution. + for (auto *NeighborA : Neighbors) { + if (VarAtom *Neighbor = dyn_cast(NeighborA)) { + ConstAtom *NghSol = Env.getAssignment(Neighbor); + // update solution if doing so would change it + // checked? --- if sol(Neighbor) <> (sol(Neighbor) JOIN Cur) + // else --- if sol(Neighbor) <> (sol(Neighbor) MEET Cur) + if ((DoLeastSolution && *NghSol < *CurrSol) || + (!DoLeastSolution && *CurrSol < *NghSol)) { + // ---- set sol(k) := (sol(k) JOIN/MEET Q) + bool Changed = Env.assign(Neighbor, CurrSol); + assert(Changed); + WorkList.push_back(Neighbor); } - } - // Erase all the fired implies. - for (auto *ToDel : FiredImplies) { - SavedImplies.erase(ToDel); - } + } // ignore ConstAtoms for now; will confirm solution below } - // Lets repeat if there are some fired constraints. - } while (!FiredImplies.empty()); + } // Check Upper/lower bounds hold; collect failures in conflicts set. std::set Neighbors; @@ -372,8 +313,6 @@ bool Constraints::graphBasedSolve() { std::set Conflicts; ConstraintsGraph SolChkCG; ConstraintsGraph SolPtrTypCG; - std::set SavedImplies; - std::set Empty; ConstraintsEnv &Env = Environment; // Checked well-formedness. @@ -388,12 +327,6 @@ bool Constraints::graphBasedSolve() { // Need to copy whether or not this constraint into the new graph SolPtrTypCG.addConstraint(G, *this); } - // Save the implies to solve them later. - else if (Implies *Imp = dyn_cast(C)) { - assert(Imp->getConclusion()->constraintIsChecked() && - Imp->getPremise()->constraintIsChecked()); - SavedImplies.insert(Imp); - } } if (DebugSolver) @@ -403,8 +336,7 @@ bool Constraints::graphBasedSolve() { // Solve Checked/unchecked constraints first. Env.doCheckedSolve(true); - bool Res = - doSolve(SolChkCG, SavedImplies, Env, this, true, nullptr, Conflicts); + bool Res = doSolve(SolChkCG, Env, this, true, nullptr, Conflicts); // Now solve PtrType constraints if (Res && AllTypes) { @@ -420,14 +352,14 @@ bool Constraints::graphBasedSolve() { return true; }, getNTArr()); - Res = doSolve(SolPtrTypCG, Empty, Env, this, true, nullptr, Conflicts); + Res = doSolve(SolPtrTypCG, Env, this, true, nullptr, Conflicts); } else if (OnlyGreatestSol) { // Do only greatest solution - Res = doSolve(SolPtrTypCG, Empty, Env, this, false, nullptr, Conflicts); + Res = doSolve(SolPtrTypCG, Env, this, false, nullptr, Conflicts); } else { // Regular solve // Step 1: Greatest solution - Res = doSolve(SolPtrTypCG, Empty, Env, this, false, nullptr, Conflicts); + Res = doSolve(SolPtrTypCG, Env, this, false, nullptr, Conflicts); } // Step 2: Reset all solutions but for function params, @@ -473,7 +405,7 @@ bool Constraints::graphBasedSolve() { // a lower bound will be resolved in the final greatest solution. std::set LowerBounded = findBounded(SolPtrTypCG, &Rest, true); - Res = doSolve(SolPtrTypCG, Empty, Env, this, true, &Rest, Conflicts); + Res = doSolve(SolPtrTypCG, Env, this, true, &Rest, Conflicts); // Step 3: Reset local variable solutions, compute greatest if (Res) { @@ -486,7 +418,7 @@ bool Constraints::graphBasedSolve() { }, getPtr()); - Res = doSolve(SolPtrTypCG, Empty, Env, this, false, &Rest, Conflicts); + Res = doSolve(SolPtrTypCG, Env, this, false, &Rest, Conflicts); } } // If PtrType solving (partly) failed, make the affected VarAtoms wild. @@ -503,16 +435,12 @@ bool Constraints::graphBasedSolve() { } Conflicts.clear(); /* FIXME: Should we propagate the old res? */ - Res = doSolve(SolChkCG, SavedImplies, Env, this, true, &Rest, Conflicts); + Res = doSolve(SolChkCG, Env, this, true, &Rest, Conflicts); } // Final Step: Merge ptyp solution with checked solution. Env.mergePtrTypes(); } - if (DebugSolver) - GraphVizOutputGraph::dumpConstraintGraphs( - "implication_constraints_graph.dot", SolChkCG, SolPtrTypCG); - return Res; } @@ -646,10 +574,6 @@ Geq *Constraints::createGeq(Atom *Lhs, Atom *Rhs, const std::string &Rsn, return new Geq(Lhs, Rhs, Rsn, PL, IsCheckedConstraint); } -Implies *Constraints::createImplies(Geq *Premise, Geq *Conclusion) { - return new Implies(Premise, Conclusion); -} - void Constraints::resetEnvironment() { Environment.resetFullSolution(getDefaultSolution()); } diff --git a/clang/lib/3C/ProgramInfo.cpp b/clang/lib/3C/ProgramInfo.cpp index 3d148f9c6ad2..90c4c692f4fc 100644 --- a/clang/lib/3C/ProgramInfo.cpp +++ b/clang/lib/3C/ProgramInfo.cpp @@ -1000,20 +1000,6 @@ bool ProgramInfo::computeInterimConstraintState( std::set DirectWildVarAtoms; CS.getChkCG().getSuccessors(CS.getWild(), DirectWildVarAtoms); - // Maps each atom to the set of atoms which depend on it through an - // implication constraint. These atoms would not be associated with the - // correct root cause through a BFS because an explicit edge does not exist - // between the cause and these atoms. Implication firing adds an edge from - // WILD to the LHS conclusion ptr. The logical flow of WILDness, however, is - // from the premise LHS to conclusion LHS. - std::map> ImpMap; - for (auto *C : getConstraints().getConstraints()) - if (auto *Imp = dyn_cast(C)) { - auto *Pre = Imp->getPremise(); - auto *Con = Imp->getConclusion(); - ImpMap[Pre->getLHS()].insert(Con->getLHS()); - } - CVars TmpCGrp; CVars OnlyIndirect; for (auto *A : DirectWildVarAtoms) { @@ -1037,10 +1023,6 @@ bool ProgramInfo::computeInterimConstraintState( } }; CS.getChkCG().visitBreadthFirst(VA, BFSVisitor); - if (ImpMap.find(A) != ImpMap.end()) - for (Atom *ImpA : ImpMap[A]) - if (isa(ImpA)) - CS.getChkCG().visitBreadthFirst(ImpA, BFSVisitor); CState.TotalNonDirectWildAtoms.insert(OnlyIndirect.begin(), OnlyIndirect.end()); From 02860bf0005ea7d7557315ce28bdf591f7047a53 Mon Sep 17 00:00:00 2001 From: Kyle Headley Date: Tue, 13 Jul 2021 12:48:18 -0400 Subject: [PATCH 06/38] fix bug (#649) --- clang/include/clang/3C/CastPlacement.h | 5 ++++- clang/lib/3C/CastPlacement.cpp | 29 +++++++++++++++++++------- clang/test/3C/cast.c | 10 +++++++++ 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/clang/include/clang/3C/CastPlacement.h b/clang/include/clang/3C/CastPlacement.h index a822d2d172c5..994f700223c5 100644 --- a/clang/include/clang/3C/CastPlacement.h +++ b/clang/include/clang/3C/CastPlacement.h @@ -62,9 +62,12 @@ class CastPlacementVisitor : public RecursiveASTVisitor { ConstraintVariable *DstExt); std::pair getCastString(ConstraintVariable *Dst, + ConstraintVariable *TypeVar, CastNeeded CastKind); - void surroundByCast(ConstraintVariable *Dst, CastNeeded CastKind, Expr *E); + void surroundByCast(ConstraintVariable *Dst, + ConstraintVariable *TypeVar, + CastNeeded CastKind, Expr *E); void reportCastInsertionFailure(Expr *E, const std::string &CastStr); void updateRewriteStats(CastNeeded CastKind); }; diff --git a/clang/lib/3C/CastPlacement.cpp b/clang/lib/3C/CastPlacement.cpp index cbadd8133cbe..a1d6103ffdfd 100644 --- a/clang/lib/3C/CastPlacement.cpp +++ b/clang/lib/3C/CastPlacement.cpp @@ -53,20 +53,23 @@ bool CastPlacementVisitor::VisitCallExpr(CallExpr *CE) { // Avoid adding incorrect casts to generic function arguments by // removing implicit casts when on arguments with a consistently // used generic type. + ConstraintVariable *TypeVar = nullptr; Expr *ArgExpr = A; if (FD && PIdx < FD->getNumParams()) { const int TyVarIdx = FV->getExternalParam(PIdx)->getGenericIndex(); if (TypeVars.find(TyVarIdx) != TypeVars.end() && TypeVars[TyVarIdx] != nullptr) - ArgExpr = ArgExpr->IgnoreImpCasts(); + TypeVar = TypeVars[TyVarIdx]; } + if (TypeVar != nullptr) + ArgExpr = ArgExpr->IgnoreImpCasts(); CVarSet ArgConstraints = CR.getExprConstraintVarsSet(ArgExpr); for (auto *ArgC : ArgConstraints) { CastNeeded CastKind = needCasting( ArgC, ArgC, FV->getInternalParam(PIdx), FV->getExternalParam(PIdx)); if (CastKind != NO_CAST) { - surroundByCast(FV->getExternalParam(PIdx), CastKind, A); + surroundByCast(FV->getExternalParam(PIdx), TypeVar, CastKind, A); ExprsWithCast.insert(ignoreCheckedCImplicit(A)); break; } @@ -91,7 +94,7 @@ bool CastPlacementVisitor::VisitCallExpr(CallExpr *CE) { FV->getExternalReturn(), DstC, DstC); if (ExprsWithCast.find(CE) == ExprsWithCast.end() && CastKind != NO_CAST) { - surroundByCast(DstC, CastKind, CE); + surroundByCast(DstC, nullptr, CastKind, CE); ExprsWithCast.insert(ignoreCheckedCImplicit(CE)); break; } @@ -151,6 +154,7 @@ CastPlacementVisitor::CastNeeded CastPlacementVisitor::needCasting( // when placed around the expression being cast. std::pair CastPlacementVisitor::getCastString(ConstraintVariable *Dst, + ConstraintVariable *TypeVar, CastNeeded CastKind) { switch (CastKind) { case CAST_NT_ARRAY: @@ -181,10 +185,18 @@ CastPlacementVisitor::getCastString(ConstraintVariable *Dst, Suffix = ", " + Bounds + ")"; } } - return std::make_pair("_Assume_bounds_cast<" + - Dst->mkString(Info.getConstraints(), false) + - ">(", - Suffix); + // The destination's type may be generic, which would have an out-of-scope + // type var, so use the already analysed local type var instead + std::string Type; + if (TypeVar != nullptr) { + Type = "_Ptr<" + + TypeVar->mkString(Info.getConstraints(), + false, false, true) + + ">"; + } else { + Type = Dst->mkString(Info.getConstraints(), false); + } + return std::make_pair("_Assume_bounds_cast<" + Type + ">(", Suffix); } default: llvm_unreachable("No casting needed"); @@ -192,6 +204,7 @@ CastPlacementVisitor::getCastString(ConstraintVariable *Dst, } void CastPlacementVisitor::surroundByCast(ConstraintVariable *Dst, + ConstraintVariable *TypeVar, CastNeeded CastKind, Expr *E) { PersistentSourceLoc PSL = PersistentSourceLoc::mkPSL(E, *Context); if (!canWrite(PSL.getFileName())) { @@ -208,7 +221,7 @@ void CastPlacementVisitor::surroundByCast(ConstraintVariable *Dst, return; } - auto CastStrs = getCastString(Dst, CastKind); + auto CastStrs = getCastString(Dst, TypeVar, CastKind); // If E is already a cast expression, we will try to rewrite the cast instead // of adding a new expression. diff --git a/clang/test/3C/cast.c b/clang/test/3C/cast.c index 301dad320d08..c94459f1abc6 100644 --- a/clang/test/3C/cast.c +++ b/clang/test/3C/cast.c @@ -22,3 +22,13 @@ void bar(void) { //CHECK: int *d = (int *)5; /*int *e = (int *)(a+5);*/ } + + +// Check that casts to generics use the local type variables +int *mk_ptr(void); +_For_any(T) void free_ptr(_Ptr p_ptr) {} +void work (void) { + int *node = mk_ptr(); // wild because extern unavailable + free_ptr(node); + // CHECK: free_ptr(_Assume_bounds_cast<_Ptr>(node)); +} From a359f10ee2a928669590fd4e8b30f958ac114c0d Mon Sep 17 00:00:00 2001 From: "Matt McCutchen (Correct Computation)" Date: Tue, 13 Jul 2021 15:58:14 -0400 Subject: [PATCH 07/38] Introduce a code pattern for functions with named optional parameters. (#638) And migrate ConstraintVariable::mkString to use it. Fixes #621. --- clang/include/clang/3C/ConstraintVariables.h | 32 +-- clang/include/clang/3C/OptionalParams.h | 213 +++++++++++++++++++ clang/lib/3C/CastPlacement.cpp | 11 +- clang/lib/3C/ConstraintVariables.cpp | 38 ++-- clang/lib/3C/DeclRewriter.cpp | 16 +- clang/lib/3C/RewriteUtils.cpp | 8 +- 6 files changed, 273 insertions(+), 45 deletions(-) create mode 100644 clang/include/clang/3C/OptionalParams.h diff --git a/clang/include/clang/3C/ConstraintVariables.h b/clang/include/clang/3C/ConstraintVariables.h index d6492f82b206..d0ae7f678925 100644 --- a/clang/include/clang/3C/ConstraintVariables.h +++ b/clang/include/clang/3C/ConstraintVariables.h @@ -26,6 +26,7 @@ #define LLVM_CLANG_3C_CONSTRAINTVARIABLES_H #include "clang/3C/Constraints.h" +#include "clang/3C/OptionalParams.h" #include "clang/3C/ProgramVar.h" #include "clang/AST/ASTContext.h" #include "clang/Lex/Lexer.h" @@ -41,6 +42,16 @@ typedef std::set CVars; // Holds Atoms, one for each of the pointer (*) declared in the program. typedef std::vector CAtoms; +struct MkStringOpts { + bool EmitName = true; + bool ForItype = false; + bool EmitPointee = false; + bool UnmaskTypedef = false; + std::string UseName = ""; + bool ForItypeBase = false; +}; +#define MKSTRING_OPTS(...) PACK_OPTS(MkStringOpts, __VA_ARGS__) + // Base class for ConstraintVariables. A ConstraintVariable can either be a // PointerVariableConstraint or a FunctionVariableConstraint. The difference // is that FunctionVariableConstraints have constraints on the return value @@ -84,11 +95,8 @@ class ConstraintVariable { // the name of the variable, false for just the type. // The 'forIType' parameter is true when the generated string is expected // to be used inside an itype. - virtual std::string mkString(Constraints &CS, bool EmitName = true, - bool ForItype = false, bool EmitPointee = false, - bool UnmaskTypedef = false, - std::string UseName = "", - bool ForItypeBase = false) const = 0; + virtual std::string mkString(Constraints &CS, + const MkStringOpts &Opts = {}) const = 0; // Debug printing of the constraint variable. virtual void print(llvm::raw_ostream &O) const = 0; @@ -442,11 +450,8 @@ class PointerVariableConstraint : public ConstraintVariable { std::string gatherQualStrings(void) const; - std::string mkString(Constraints &CS, bool EmitName = true, - bool ForItype = false, bool EmitPointee = false, - bool UnmaskTypedef = false, - std::string UseName = "", - bool ForItypeBase = false) const override; + std::string mkString(Constraints &CS, + const MkStringOpts &Opts = {}) const override; FunctionVariableConstraint *getFV() const { return FV; } @@ -635,11 +640,8 @@ class FunctionVariableConstraint : public ConstraintVariable { bool solutionEqualTo(Constraints &CS, const ConstraintVariable *CV, bool ComparePtyp = true) const override; - std::string mkString(Constraints &CS, bool EmitName = true, - bool ForItype = false, bool EmitPointee = false, - bool UnmaskTypedef = false, - std::string UseName = "", - bool ForItypeBase = false) const override; + std::string mkString(Constraints &CS, + const MkStringOpts &Opts = {}) const override; void print(llvm::raw_ostream &O) const override; void dump() const override { print(llvm::errs()); } void dumpJson(llvm::raw_ostream &O) const override; diff --git a/clang/include/clang/3C/OptionalParams.h b/clang/include/clang/3C/OptionalParams.h new file mode 100644 index 000000000000..8d2c81738ac9 --- /dev/null +++ b/clang/include/clang/3C/OptionalParams.h @@ -0,0 +1,213 @@ +//=--3C.h---------------------------------------------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Definitions supporting a code pattern for functions that take _named_ +// optional parameters. For functions with multiple optional parameters, this +// pattern tends to be better than C++'s default arguments, which are positional +// and have the limitation that if a call site specifies a value for one +// argument, it must also specify values for all previous arguments. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_3C_OPTIONALPARAMS_H +#define LLVM_CLANG_3C_OPTIONALPARAMS_H + +// The basic ideas of the code pattern, for a function `foo`: +// +// 1. Define a struct `FooOpts` to hold the optional "parameters", and use +// default member initializers to specify the desired default values. +// 2. Declare `foo` to take a final parameter `const FooOpts &Opts = {}`. (The +// default argument `{}` corresponds to a `FooOpts` with all the default +// values.) +// 3. In the body of `foo`, use the `UNPACK_OPTS` macro to copy the fields of +// `Opts` to local variables for convenient access. +// 4. At call sites, use a macro to generate an immediately invoked lambda that +// generates a `FooOpts` instance with the desired values. We could +// alternatively use designated initializers if they were supported in our +// target C++ standard. +// +// In two places in the current pattern, optional parameters are simply copied +// by value, which gives rise to several limitations: we have to use pointers +// (`T *`) instead of references (`T &`), and there may be a performance cost to +// the copying. We may be able to overcome these limitations in the future with +// additional tricks such as using wrapper objects with an overloaded `=` +// operator for the members of `FooOpts`, but it doesn't seem worth it yet (as +// of 2021-07-09). +// +// Further design discussion: +// https://github.com/correctcomputation/checkedc-clang/issues/621 +// https://github.com/correctcomputation/checkedc-clang/pull/638 +// +// A simple example is included at the bottom of this file, after the +// implementation. That way, you can explore it in your IDE by changing the `#if +// 0` to `#if 1`, at the cost of having to scroll down to reach it. For an +// example of an actual use, see ConstraintVariable::mkString. + +// IMPLEMENTATION: + +// This currently supports up to 6 optional parameters for a given function. It +// should be clear how to extend the pattern of macros to increase the limit. + +// Our internal macro names are prefixed with `_OP_` (for "optional parameters") +// in an attempt to avoid accidental collisions with anything else in the LLVM +// monorepo. + +// Helpers: + +// Workaround for an MSVC bug where if a macro body passes __VA_ARGS__ to +// another macro (called M in this discussion), M receives a single argument +// containing commas instead of a variable number of arguments. For reasons we +// haven't researched, placing this wrapper around the entire macro body seems +// to avoid the problem. +// +// _OP_MSVC_VA_ARGS_WORKAROUND is needed only when M declares individual +// parameters and we need the arguments to be matched up with those parameters. +// If M just declares `...` and passes it on to another macro with __VA_ARGS__, +// it's generally harmless to let M receive a single argument containing commas; +// the argument will be correctly split up by _OP_MSVC_VA_ARGS_WORKAROUND later. +// +// We should be able to remove this workaround when the 3C Windows build moves +// to the "new" MSVC preprocessor. See +// https://developercommunity.visualstudio.com/t/-va-args-seems-to-be-trated-as-a-single-parameter/460154. +#define _OP_MSVC_VA_ARGS_WORKAROUND(_x) _x + +#define _OP_GET_ARG7(_1, _2, _3, _4, _5, _6, _7, ...) _7 +// Precondition: The number of arguments is between 1 and 6 inclusive. +// +// The `_dummy` is because according to the C++ standard, at least one argument +// must be passed to a `...` parameter +// (https://en.cppreference.com/w/cpp/preprocessor/replace). +#define _OP_COUNT_ARGS(...) \ + _OP_MSVC_VA_ARGS_WORKAROUND( \ + _OP_GET_ARG7(__VA_ARGS__, 6, 5, 4, 3, 2, 1, _dummy)) + +// Each _i argument is expected to be of the form `field = expr`, so we generate +// a statement of the form `_s.field = expr;` to set the struct field. +#define _OP_ASSIGN_FIELDS_1(_i1) _s._i1; +#define _OP_ASSIGN_FIELDS_2(_i1, _i2) \ + _s._i1; \ + _s._i2; +#define _OP_ASSIGN_FIELDS_3(_i1, _i2, _i3) \ + _s._i1; \ + _s._i2; \ + _s._i3; +#define _OP_ASSIGN_FIELDS_4(_i1, _i2, _i3, _i4) \ + _s._i1; \ + _s._i2; \ + _s._i3; \ + _s._i4; +#define _OP_ASSIGN_FIELDS_5(_i1, _i2, _i3, _i4, _i5) \ + _s._i1; \ + _s._i2; \ + _s._i3; \ + _s._i4; \ + _s._i5; +#define _OP_ASSIGN_FIELDS_6(_i1, _i2, _i3, _i4, _i5, _i6) \ + _s._i1; \ + _s._i2; \ + _s._i3; \ + _s._i4; \ + _s._i5; \ + _s._i6; +#define _OP_ASSIGN_FIELDS(_count, ...) \ + _OP_MSVC_VA_ARGS_WORKAROUND(_OP_ASSIGN_FIELDS_##_count(__VA_ARGS__)) +// In _OP_ASSIGN_FIELDS, ## takes precedence over the expansion of the _count +// argument, so if we let PACK_OPTS call `_OP_ASSIGN_FIELDS(_OP_COUNT_ARGS(foo), +// bar)` directly, we'd end up with `_OP_ASSIGN_FIELDS__COUNT_ARGS(foo)(bar)`. +// _OP_ASSIGN_FIELDS_WRAP expands its _count argument before calling +// _OP_ASSIGN_FIELDS, working around the problem. See the second example on +// https://gcc.gnu.org/onlinedocs/cpp/Argument-Prescan.html. +#define _OP_ASSIGN_FIELDS_WRAP(_count, ...) \ + _OP_ASSIGN_FIELDS(_count, __VA_ARGS__) + +// PACK_OPTS currently doesn't support passing an empty list of fields to the +// `...`, because in the current usage for optional parameters, we expect the +// whole struct to be omitted if no optional parameters need to be passed. +#define PACK_OPTS(_type, ...) \ + ([&] { \ + _type _s; \ + _OP_ASSIGN_FIELDS_WRAP(_OP_COUNT_ARGS(__VA_ARGS__), __VA_ARGS__) \ + return _s; \ + })() + +// `(void)_f` suppresses "unused variable" warnings: see the comment on +// LLVM_ATTRIBUTE_UNUSED in llvm/include/llvm/Support/Compiler.h. +#define _OP_UNPACK_OPT(_f) \ + auto _f = Opts._f; \ + (void)_f; +#define _OP_UNPACK_OPTS_1(_f1) _OP_UNPACK_OPT(_f1) +#define _OP_UNPACK_OPTS_2(_f1, _f2) _OP_UNPACK_OPT(_f1) _OP_UNPACK_OPT(_f2) +#define _OP_UNPACK_OPTS_3(_f1, _f2, _f3) \ + _OP_UNPACK_OPT(_f1) _OP_UNPACK_OPT(_f2) _OP_UNPACK_OPT(_f3) +#define _OP_UNPACK_OPTS_4(_f1, _f2, _f3, _f4) \ + _OP_UNPACK_OPT(_f1) \ + _OP_UNPACK_OPT(_f2) _OP_UNPACK_OPT(_f3) _OP_UNPACK_OPT(_f4) +#define _OP_UNPACK_OPTS_5(_f1, _f2, _f3, _f4, _f5) \ + _OP_UNPACK_OPT(_f1) \ + _OP_UNPACK_OPT(_f2) \ + _OP_UNPACK_OPT(_f3) _OP_UNPACK_OPT(_f4) _OP_UNPACK_OPT(_f5) +#define _OP_UNPACK_OPTS_6(_f1, _f2, _f3, _f4, _f5, _f6) \ + _OP_UNPACK_OPT(_f1) \ + _OP_UNPACK_OPT(_f2) \ + _OP_UNPACK_OPT(_f3) \ + _OP_UNPACK_OPT(_f4) _OP_UNPACK_OPT(_f5) _OP_UNPACK_OPT(_f6) +#define _OP_UNPACK_OPTS_IMPL(_count, ...) \ + _OP_MSVC_VA_ARGS_WORKAROUND(_OP_UNPACK_OPTS_##_count(__VA_ARGS__)) +// Ditto the comment on _OP_ASSIGN_FIELDS_WRAP. +#define _OP_UNPACK_OPTS_WRAP(_count, ...) \ + _OP_UNPACK_OPTS_IMPL(_count, __VA_ARGS__) +// UNPACK_OPTS doesn't support an empty list of parameters: the UNPACK_OPTS call +// should just be omitted in that case. +#define UNPACK_OPTS(...) \ + _OP_UNPACK_OPTS_WRAP(_OP_COUNT_ARGS(__VA_ARGS__), __VA_ARGS__) + +// EXAMPLE (change `#if 0` to `#if 1` to explore it in your IDE): + +#if 0 + +struct FooOpts { + std::string B = "hello"; + bool C = false; +}; + +#define FOO_OPTS(...) PACK_OPTS(FooOpts, __VA_ARGS__) + +void foo(int A, const FooOpts &Opts = {}) { + UNPACK_OPTS(B, C); +#if 0 // Avoid "duplicate local variable" errors in this illustration. + // Expansion: + auto B = Opts.B; + // Suppresses an "unused variable" warning if FooOpts is shared by several + // functions that take the same optional parameters (e.g., virtual overrides) + // and some of those functions don't actually read all the parameters. + (void)B; + auto C = Opts.C; + (void)C; +#endif + + llvm::errs() << "A is " << A << ", B is " << B << ", C is " << C << "\n"; +} + +void test() { + foo(1); + foo(2, FOO_OPTS(B = "goodbye")); + // Expansion (notice how we conveniently concatenated `_s.` and the argument + // `B = "goodbye"`): + foo(2, ([&] { + FooOpts _s; + _s.B = "goodbye"; + return _s; + })()); + + foo(3, FOO_OPTS(C = true)); + foo(4, FOO_OPTS(B = "goodbye", C = true)); +} + +#endif // example + +#endif // LLVM_CLANG_3C_OPTIONALPARAMS_H diff --git a/clang/lib/3C/CastPlacement.cpp b/clang/lib/3C/CastPlacement.cpp index a1d6103ffdfd..d88db50272b1 100644 --- a/clang/lib/3C/CastPlacement.cpp +++ b/clang/lib/3C/CastPlacement.cpp @@ -158,8 +158,11 @@ CastPlacementVisitor::getCastString(ConstraintVariable *Dst, CastNeeded CastKind) { switch (CastKind) { case CAST_NT_ARRAY: - return std::make_pair( - "((" + Dst->mkString(Info.getConstraints(), false) + ")", ")"); + return std::make_pair("((" + + Dst->mkString(Info.getConstraints(), + MKSTRING_OPTS(EmitName = false)) + + ")", + ")"); case CAST_TO_WILD: return std::make_pair("((" + Dst->getRewritableOriginalTy() + ")", ")"); case CAST_TO_CHECKED: { @@ -191,10 +194,10 @@ CastPlacementVisitor::getCastString(ConstraintVariable *Dst, if (TypeVar != nullptr) { Type = "_Ptr<" + TypeVar->mkString(Info.getConstraints(), - false, false, true) + + MKSTRING_OPTS(EmitName = false, EmitPointee = true)) + ">"; } else { - Type = Dst->mkString(Info.getConstraints(), false); + Type = Dst->mkString(Info.getConstraints(), MKSTRING_OPTS(EmitName = false)); } return std::make_pair("_Assume_bounds_cast<" + Type + ">(", Suffix); } diff --git a/clang/lib/3C/ConstraintVariables.cpp b/clang/lib/3C/ConstraintVariables.cpp index ab814ebbf6db..8b6e0254514b 100644 --- a/clang/lib/3C/ConstraintVariables.cpp +++ b/clang/lib/3C/ConstraintVariables.cpp @@ -721,11 +721,11 @@ std::string PointerVariableConstraint::gatherQualStrings(void) const { return S.str(); } -std::string PointerVariableConstraint::mkString(Constraints &CS, bool EmitName, - bool ForItype, bool EmitPointee, - bool UnmaskTypedef, - std::string UseName, - bool ForItypeBase) const { +std::string +PointerVariableConstraint::mkString(Constraints &CS, + const MkStringOpts &Opts) const { + UNPACK_OPTS(EmitName, ForItype, EmitPointee, UnmaskTypedef, UseName, + ForItypeBase); // The name field encodes if this variable is the return type for a function. // TODO: store this information in a separate field. @@ -905,10 +905,11 @@ std::string PointerVariableConstraint::mkString(Constraints &CS, bool EmitName, } bool EmitFVName = !FptrInner.str().empty(); if (EmitFVName) - Ss << FV->mkString(CS, true, false, false, false, FptrInner.str(), - ForItypeBase); + Ss << FV->mkString(CS, MKSTRING_OPTS(UseName = FptrInner.str(), + ForItypeBase = ForItypeBase)); else - Ss << FV->mkString(CS, false, false, false, false, "", ForItypeBase); + Ss << FV->mkString( + CS, MKSTRING_OPTS(EmitName = false, ForItypeBase = ForItypeBase)); } else if (TypedefLevelInfo.HasTypedef) { std::ostringstream Buf; getQualString(TypedefLevelInfo.TypedefLevel, Buf); @@ -1532,12 +1533,11 @@ bool FunctionVariableConstraint::solutionEqualTo(Constraints &CS, return Ret; } -std::string FunctionVariableConstraint::mkString(Constraints &CS, bool EmitName, - bool ForItype, - bool EmitPointee, - bool UnmaskTypedef, - std::string UseName, - bool ForItypeBase) const { +std::string +FunctionVariableConstraint::mkString(Constraints &CS, + const MkStringOpts &Opts) const { + UNPACK_OPTS(EmitName, ForItype, EmitPointee, UnmaskTypedef, UseName, + ForItypeBase); if (UseName.empty()) UseName = Name; std::string Ret = ReturnVar.mkTypeStr(CS, false, "", ForItypeBase); @@ -2058,8 +2058,9 @@ std::string FVComponentVariable::mkTypeStr(Constraints &CS, bool EmitName, // variable. Because the call is passed ForItypeBase, it will emit an // unchecked type instead of the solved type. if (ForItypeBase || hasCheckedSolution(CS) || (EmitName && !UseName.empty())) { - Ret = ExternalConstraint->mkString(CS, EmitName, false, false, false, - UseName, ForItypeBase); + Ret = ExternalConstraint->mkString( + CS, MKSTRING_OPTS(EmitName = EmitName, UseName = UseName, + ForItypeBase = ForItypeBase)); } else { // if no need to generate type, try to use source if (!SourceDeclaration.empty()) @@ -2085,7 +2086,10 @@ std::string FVComponentVariable::mkTypeStr(Constraints &CS, bool EmitName, std::string FVComponentVariable::mkItypeStr(Constraints &CS, bool ForItypeBase) const { if (!ForItypeBase && hasItypeSolution(CS)) - return " : itype(" + ExternalConstraint->mkString(CS, false, true) + ")"; + return " : itype(" + + ExternalConstraint->mkString( + CS, MKSTRING_OPTS(EmitName = false, ForItype = true)) + + ")"; return ""; } diff --git a/clang/lib/3C/DeclRewriter.cpp b/clang/lib/3C/DeclRewriter.cpp index 193d4cbdc19e..23033f54f4d5 100644 --- a/clang/lib/3C/DeclRewriter.cpp +++ b/clang/lib/3C/DeclRewriter.cpp @@ -42,8 +42,8 @@ void DeclRewriter::buildItypeDecl(PVConstraint *Defn, DeclaratorDecl *Decl, // unchecked type from the PVConstraint. The last argument of this call // tells mkString to generate a string using unchecked types instead of // checked types. - Type = Defn->mkString(Info.getConstraints(), true, false, false, false, "", - true); + Type = Defn->mkString(Info.getConstraints(), + MKSTRING_OPTS(ForItypeBase = true)); } else { Type = Defn->getRewritableOriginalTy(); if (isa_and_nonnull(Decl)) { @@ -68,9 +68,12 @@ void DeclRewriter::buildItypeDecl(PVConstraint *Defn, DeclaratorDecl *Decl, // type to the declaration so that it can be used in checked code. // TODO: This could potentially be applied to typedef types even when the // flag is not passed to limit spread of wildness through typedefs. - IType += Defn->mkString(Info.getConstraints(), false, true, false, true); + IType += Defn->mkString(Info.getConstraints(), + MKSTRING_OPTS(EmitName = false, ForItype = true, + UnmaskTypedef = true)); } else { - IType += Defn->mkString(Info.getConstraints(), false, true); + IType += Defn->mkString(Info.getConstraints(), + MKSTRING_OPTS(EmitName = false, ForItype = true)); } IType += ")" + ABR.getBoundsString(Defn, Decl, true); } @@ -113,7 +116,8 @@ void DeclRewriter::rewriteDecls(ASTContext &Context, ProgramInfo &Info, if (Var.anyChanges(Env)) { std::string NewTy = getStorageQualifierString(D) + - Var.mkString(Info.getConstraints(), true, false, false, true); + Var.mkString(Info.getConstraints(), + MKSTRING_OPTS(UnmaskTypedef = true)); RewriteThese.insert(new TypedefDeclReplacement(TD, nullptr, NewTy)); } } @@ -737,7 +741,7 @@ void FunctionDeclBuilder::buildCheckedDecl( std::string &IType, std::string UseName, bool &RewriteParm, bool &RewriteRet) { Type = - Defn->mkString(Info.getConstraints(), true, false, false, false, UseName); + Defn->mkString(Info.getConstraints(), MKSTRING_OPTS(UseName = UseName)); //IType = getExistingIType(Defn); IType = ABRewriter.getBoundsString(Defn, Decl, !IType.empty()); RewriteParm |= getExistingIType(Defn).empty() != IType.empty() || diff --git a/clang/lib/3C/RewriteUtils.cpp b/clang/lib/3C/RewriteUtils.cpp index 9ac5d00a974a..539f1696a8cf 100644 --- a/clang/lib/3C/RewriteUtils.cpp +++ b/clang/lib/3C/RewriteUtils.cpp @@ -422,7 +422,8 @@ class TypeExprRewriter : public clang::RecursiveASTVisitor { // Replace the original type with this new one if the type has changed. if (CV->anyChanges(Vars)) { rewriteSourceRange(Writer, Range, - CV->mkString(Info.getConstraints(), false)); + CV->mkString(Info.getConstraints(), + MKSTRING_OPTS(EmitName = false))); PState.incrementNumFixedCasts(); } } @@ -451,8 +452,9 @@ class TypeArgumentAdder : public clang::RecursiveASTVisitor { for (auto Entry : Info.getTypeParamBindings(CE, Context)) if (Entry.second != nullptr) { AllInconsistent = false; - std::string TyStr = Entry.second->mkString(Info.getConstraints(), - false, false, true); + std::string TyStr = Entry.second->mkString( + Info.getConstraints(), + MKSTRING_OPTS(EmitName = false, EmitPointee = true)); if (TyStr.back() == ' ') TyStr.pop_back(); TypeParamString += TyStr + ","; From cf35b121de3211cbcac3e107edacaaf8c89e45e0 Mon Sep 17 00:00:00 2001 From: "Matt McCutchen (Correct Computation)" Date: Tue, 3 Aug 2021 16:13:20 -0400 Subject: [PATCH 08/38] Remove some unneeded rewriting code, including DComp. (#662) * Remove the DComp custom comparator for the rewrite set (RSet). 3C may have relied on this custom order at one time, but it no longer seems to; at least, the regression tests pass after this change. Rewriting of non-overlapping declarations should be independent, and the code already ensures that all rewrites to a given multi-decl are performed together in the appropriate way. RSet is now a std::map so that rewriteMultiDecl can look up the rewrite (if any) for a given Decl in the multi-decl rather than scanning the set in the original custom order. The use of a map adds some boilerplate, but I can't think of a better option. Also, VisitedMultiDeclMembers can just be a std::set because that's all we need in order to test whether a Decl has already been rewritten. * The ParmVarDeclReplacement code is dead; remove it. It seems that isPartOfFunctionPrototype is always true for a ParmVarDecl and rewriting is handled as part of the containing FunctionDecl instead. I haven't looked into when the old code path to rewrite a single ParmVar became dead or whether it still works. * hasFunctionBody in Utils.{h,cpp} is also dead; remove it. I noticed this when searching for other code related to ParmVarDecl that might be dead. --- clang/include/clang/3C/DeclRewriter.h | 8 +- clang/include/clang/3C/RewriteUtils.h | 38 +------- clang/include/clang/3C/Utils.h | 2 - clang/lib/3C/DeclRewriter.cpp | 124 +++++++------------------- clang/lib/3C/RewriteUtils.cpp | 67 -------------- clang/lib/3C/StructInit.cpp | 3 +- clang/lib/3C/Utils.cpp | 18 ---- 7 files changed, 39 insertions(+), 221 deletions(-) diff --git a/clang/include/clang/3C/DeclRewriter.h b/clang/include/clang/3C/DeclRewriter.h index 7b69a2077227..0dd3de375fdf 100644 --- a/clang/include/clang/3C/DeclRewriter.h +++ b/clang/include/clang/3C/DeclRewriter.h @@ -27,8 +27,7 @@ using namespace clang; class DeclRewriter { public: DeclRewriter(Rewriter &R, ASTContext &A, GlobalVariableGroups &GP) - : R(R), A(A), GP(GP), - VisitedMultiDeclMembers(DComp(A.getSourceManager())) {} + : R(R), A(A), GP(GP) {} // The publicly accessible interface for performing declaration rewriting. // All declarations for variables with checked types in the variable map of @@ -54,7 +53,7 @@ class DeclRewriter { // It is not used with individual declarations outside of multi-declarations // because these declarations are seen exactly once, rather than every time a // declaration in the containing multi-decl is visited. - RSet VisitedMultiDeclMembers; + std::set VisitedMultiDeclMembers; // Visit each Decl in ToRewrite and apply the appropriate pointer type // to that Decl. ToRewrite is the set of all declarations to rewrite. @@ -63,8 +62,6 @@ class DeclRewriter { // Rewrite a specific variable declaration using the replacement string in the // DAndReplace structure. Each of these functions is specialized to handling // one subclass of declarations. - void rewriteParmVarDecl(ParmVarDeclReplacement *N); - template void rewriteFieldOrVarDecl(DRType *N, RSet &ToRewrite); void rewriteMultiDecl(DeclReplacement *N, RSet &ToRewrite, @@ -76,7 +73,6 @@ class DeclRewriter { void rewriteTypedefDecl(TypedefDeclReplacement *TDT, RSet &ToRewrite); void getDeclsOnSameLine(DeclReplacement *N, std::vector &Decls); bool isSingleDeclaration(DeclReplacement *N); - bool areDeclarationsOnSameLine(DeclReplacement *N1, DeclReplacement *N2); SourceRange getNextCommaOrSemicolon(SourceLocation L); static void detectInlineStruct(Decl *D, SourceManager &SM); }; diff --git a/clang/include/clang/3C/RewriteUtils.h b/clang/include/clang/3C/RewriteUtils.h index 8de42a0d50fb..480596ed85fe 100644 --- a/clang/include/clang/3C/RewriteUtils.h +++ b/clang/include/clang/3C/RewriteUtils.h @@ -35,7 +35,6 @@ class DeclReplacement { // Discriminator for LLVM-style RTTI (dyn_cast<> et al.). enum DRKind { DRK_VarDecl, - DRK_ParmVarDecl, DRK_FunctionDecl, DRK_FieldDecl, DRK_TypedefDecl @@ -75,8 +74,6 @@ class DeclReplacementTempl : public DeclReplacement { typedef DeclReplacementTempl VarDeclReplacement; -typedef DeclReplacementTempl - ParmVarDeclReplacement; typedef DeclReplacementTempl FieldDeclReplacement; typedef DeclReplacementTempl @@ -107,40 +104,7 @@ class FunctionDeclReplacement SourceLocation getDeclEnd(SourceManager &SM) const; }; -// Compare two DeclReplacement values. The algorithm for comparing them relates -// their source positions. If two DeclReplacement values refer to overlapping -// source positions, then they are the same. Otherwise, they are ordered -// by their placement in the input file. -// -// There are two special cases: Function declarations, and DeclStmts. In turn: -// -// - Function declarations might either be a DeclReplacement describing the -// entire declaration, i.e. replacing "int *foo(void)" -// with "int *foo(void) : itype(_Ptr)". Or, it might describe just -// replacing only the return type, i.e. "_Ptr foo(void)". This is -// discriminated against with the 'fullDecl' field of the DeclReplacement -// type and the comparison function first checks if the operands are -// FunctionDecls and if the 'fullDecl' field is set. -// - A DeclStmt of mupltiple Decls, i.e. 'int *a = 0, *b = 0'. In this case, -// we want the DeclReplacement to refer only to the specific sub-region that -// would be replaced, i.e. '*a = 0' and '*b = 0'. To do that, we traverse -// the Decls contained in a DeclStmt and figure out what the appropriate -// source locations are to describe the positions of the independent -// declarations. -class DComp { -public: - DComp(SourceManager &S) : SM(S) {} - - bool operator()(DeclReplacement *Lhs, DeclReplacement *Rhs) const; - -private: - SourceManager &SM; - - SourceRange getReplacementSourceRange(DeclReplacement *D) const; - SourceLocation getDeclBegin(DeclReplacement *D) const; -}; - -typedef std::set RSet; +typedef std::map RSet; // This class is used to figure out which global variables are part of // multi-variable declarations. For local variables, all variables in a single diff --git a/clang/include/clang/3C/Utils.h b/clang/include/clang/3C/Utils.h index 8e64eec057ca..4c2fb0f752bc 100644 --- a/clang/include/clang/3C/Utils.h +++ b/clang/include/clang/3C/Utils.h @@ -100,8 +100,6 @@ clang::FunctionDecl *getDefinition(clang::FunctionDecl *FD); clang::CheckedPointerKind getCheckedPointerKind(clang::InteropTypeExpr *ItypeExpr); -bool hasFunctionBody(clang::Decl *D); - std::string getStorageQualifierString(clang::Decl *D); std::error_code tryGetCanonicalFilePath(const std::string &FileName, diff --git a/clang/lib/3C/DeclRewriter.cpp b/clang/lib/3C/DeclRewriter.cpp index 23033f54f4d5..c2c4a085a9c2 100644 --- a/clang/lib/3C/DeclRewriter.cpp +++ b/clang/lib/3C/DeclRewriter.cpp @@ -86,7 +86,7 @@ void DeclRewriter::rewriteDecls(ASTContext &Context, ProgramInfo &Info, // Collect function and record declarations that need to be rewritten in a set // as well as their rewriten types in a map. - RSet RewriteThese(DComp(Context.getSourceManager())); + RSet RewriteThese; FunctionDeclBuilder *TRV = nullptr; #ifdef FIVE_C @@ -118,7 +118,8 @@ void DeclRewriter::rewriteDecls(ASTContext &Context, ProgramInfo &Info, getStorageQualifierString(D) + Var.mkString(Info.getConstraints(), MKSTRING_OPTS(UnmaskTypedef = true)); - RewriteThese.insert(new TypedefDeclReplacement(TD, nullptr, NewTy)); + RewriteThese.insert(std::make_pair( + TD, new TypedefDeclReplacement(TD, nullptr, NewTy))); } } } @@ -166,6 +167,9 @@ void DeclRewriter::rewriteDecls(ASTContext &Context, ProgramInfo &Info, ABRewriter.hasNewBoundsString(PV, D)); if (PVChanged && !PV->isPartOfFunctionPrototype()) { // Rewrite a declaration, only if it is not part of function prototype. + assert(!isa(D) && + "Got a PVConstraint for a ParmVarDecl where " + "isPartOfFunctionPrototype returns false?"); DeclStmt *DS = nullptr; if (VDLToStmtMap.find(D) != VDLToStmtMap.end()) DS = VDLToStmtMap[D]; @@ -184,9 +188,7 @@ void DeclRewriter::rewriteDecls(ASTContext &Context, ProgramInfo &Info, // is updated to handle structure/global itypes. std::string Type, IType; // VarDecl and FieldDecl subclass DeclaratorDecl, so the cast will - // always succeed. In fact, ParmVarDecl is also a subclass of - // DeclaratorDecl, so it should be possible to make the values in - // PSLMap DeclaratorDecls and avoid this cast altogether. + // always succeed. DeclRewriter::buildItypeDecl(PV, cast(D), Type, IType, Info, ABRewriter); NewTy += Type + IType; @@ -195,11 +197,11 @@ void DeclRewriter::rewriteDecls(ASTContext &Context, ProgramInfo &Info, ABRewriter.getBoundsString(PV, D); } if (auto *VD = dyn_cast(D)) - RewriteThese.insert(new VarDeclReplacement(VD, DS, NewTy)); + RewriteThese.insert( + std::make_pair(VD, new VarDeclReplacement(VD, DS, NewTy))); else if (auto *FD = dyn_cast(D)) - RewriteThese.insert(new FieldDeclReplacement(FD, DS, NewTy)); - else if (auto *PD = dyn_cast(D)) - RewriteThese.insert(new ParmVarDeclReplacement(PD, DS, NewTy)); + RewriteThese.insert( + std::make_pair(FD, new FieldDeclReplacement(FD, DS, NewTy))); else llvm_unreachable("Unrecognized declaration type."); } @@ -219,12 +221,13 @@ void DeclRewriter::rewriteDecls(ASTContext &Context, ProgramInfo &Info, DeclRewriter DeclR(R, Context, GVG); DeclR.rewrite(RewriteThese); - for (const auto *R : RewriteThese) - delete R; + for (auto Pair : RewriteThese) + delete Pair.second; } void DeclRewriter::rewrite(RSet &ToRewrite) { - for (auto *const N : ToRewrite) { + for (auto Pair : ToRewrite) { + DeclReplacement *N = Pair.second; assert(N->getDecl() != nullptr); if (Verbose) { @@ -234,10 +237,7 @@ void DeclRewriter::rewrite(RSet &ToRewrite) { } // Exact rewriting procedure depends on declaration type - if (auto *PVR = dyn_cast(N)) { - assert(N->getStatement() == nullptr); - rewriteParmVarDecl(PVR); - } else if (auto *VR = dyn_cast(N)) { + if (auto *VR = dyn_cast(N)) { rewriteFieldOrVarDecl(VR, ToRewrite); } else if (auto *FR = dyn_cast(N)) { rewriteFunctionDecl(FR); @@ -251,26 +251,6 @@ void DeclRewriter::rewrite(RSet &ToRewrite) { } } -void DeclRewriter::rewriteParmVarDecl(ParmVarDeclReplacement *N) { - // First, find all the declarations of the containing function. - DeclContext *DF = N->getDecl()->getParentFunctionOrMethod(); - assert(DF != nullptr && "no parent function or method for decl"); - FunctionDecl *FD = cast(DF); - - // For each function, determine which parameter in the declaration - // matches PV, then, get the type location of that parameter - // declaration and re-write. - unsigned int PIdx = getParameterIndex(N->getDecl(), FD); - - for (auto *CurFD = FD; CurFD != nullptr; CurFD = CurFD->getPreviousDecl()) - if (PIdx < CurFD->getNumParams()) { - ParmVarDecl *Rewrite = CurFD->getParamDecl(PIdx); - assert(Rewrite != nullptr); - SourceRange TR = Rewrite->getSourceRange(); - rewriteSourceRange(R, TR, N->getReplacement()); - } -} - void DeclRewriter::rewriteTypedefDecl(TypedefDeclReplacement *TDR, RSet &ToRewrite) { rewriteSingleDecl(TDR, ToRewrite); @@ -294,8 +274,10 @@ void DeclRewriter::rewriteFieldOrVarDecl(DRType *N, RSet &ToRewrite) { std::is_same::value, "Method expects variable or field declaration replacement."); + bool IsVisitedMultiDeclMember = (VisitedMultiDeclMembers.find(N->getDecl()) != + VisitedMultiDeclMembers.end()); if (InlineVarDecls.find(N->getDecl()) != InlineVarDecls.end() && - VisitedMultiDeclMembers.find(N) == VisitedMultiDeclMembers.end()) { + !IsVisitedMultiDeclMember) { std::vector SameLineDecls; getDeclsOnSameLine(N, SameLineDecls); if (std::find(SameLineDecls.begin(), SameLineDecls.end(), @@ -304,7 +286,7 @@ void DeclRewriter::rewriteFieldOrVarDecl(DRType *N, RSet &ToRewrite) { rewriteMultiDecl(N, ToRewrite, SameLineDecls, true); } else if (isSingleDeclaration(N)) { rewriteSingleDecl(N, ToRewrite); - } else if (VisitedMultiDeclMembers.find(N) == VisitedMultiDeclMembers.end()) { + } else if (!IsVisitedMultiDeclMember) { std::vector SameLineDecls; getDeclsOnSameLine(N, SameLineDecls); if (isInlineStruct(SameLineDecls)) @@ -314,8 +296,7 @@ void DeclRewriter::rewriteFieldOrVarDecl(DRType *N, RSet &ToRewrite) { // Anything that reaches this case should be a multi-declaration that has // already been rewritten. assert("Declaration should have been rewritten." && - !isSingleDeclaration(N) && - VisitedMultiDeclMembers.find(N) != VisitedMultiDeclMembers.end()); + !isSingleDeclaration(N) && IsVisitedMultiDeclMember); } } @@ -339,22 +320,10 @@ void DeclRewriter::rewriteMultiDecl(DeclReplacement *N, RSet &ToRewrite, // need to avoid rewriting any of these declarations twice by updating the // Skip set to include the processed declarations. - // Step 1: get declaration replacement in the same statement - RSet RewritesForThisDecl(DComp(R.getSourceMgr())); - auto I = ToRewrite.find(N); - while (I != ToRewrite.end()) { - if (areDeclarationsOnSameLine(N, *I)) { - assert("Unexpected DeclReplacement kind." && - (*I)->getKind() == N->getKind()); - RewritesForThisDecl.insert(*I); - } - ++I; - } - - // Step 2: For each decl in the original, build up a new string. If the - // original decl was re-written, write that out instead. Existing - // initializers are preserved, any declarations that an initializer to - // be valid checked-c are given one. + // For each decl in the original, build up a new string. If the + // original decl was re-written, write that out instead. Existing + // initializers are preserved, any declarations that an initializer to + // be valid checked-c are given one. bool IsFirst = true; SourceLocation PrevEnd; @@ -363,12 +332,12 @@ void DeclRewriter::rewriteMultiDecl(DeclReplacement *N, RSet &ToRewrite, // Find the declaration replacement object for the current declaration. DeclReplacement *SameLineReplacement; bool Found = false; - for (const auto &NLT : RewritesForThisDecl) - if (NLT->getDecl() == DL) { - SameLineReplacement = NLT; - Found = true; - break; - } + auto It = ToRewrite.find(DL); + if (It != ToRewrite.end()) { + SameLineReplacement = It->second; + Found = true; + VisitedMultiDeclMembers.insert(DL); + } if (IsFirst && ContainsInlineStruct) { // If it is an inline struct, the first thing we have to do @@ -444,11 +413,6 @@ void DeclRewriter::rewriteMultiDecl(DeclReplacement *N, RSet &ToRewrite, // Offset by one to skip past what we've just added so it isn't overwritten. PrevEnd = End.getEnd().getLocWithOffset(1); } - - // Step 3: Be sure and skip all of the declarations that we just dealt with by - // adding them to the skip set. - for (const auto &TN : RewritesForThisDecl) - VisitedMultiDeclMembers.insert(TN); } // Common rewriting logic used to replace a single decl either on its own or as @@ -537,27 +501,6 @@ SourceRange DeclRewriter::getNextCommaOrSemicolon(SourceLocation L) { llvm_unreachable("Unable to find comma or semicolon at source location."); } -bool DeclRewriter::areDeclarationsOnSameLine(DeclReplacement *N1, - DeclReplacement *N2) { - Decl *D1 = N1->getDecl(); - Decl *D2 = N2->getDecl(); - if (D1 && D2) { - // In the event that this is a FieldDecl, - // these statements will always be null - DeclStmt *Stmt1 = N1->getStatement(); - DeclStmt *Stmt2 = N2->getStatement(); - if (Stmt1 == nullptr && Stmt2 == nullptr) { - auto &DGroup = GP.getVarsOnSameLine(D1); - return llvm::is_contained(DGroup, D2); - } - if (Stmt1 == nullptr || Stmt2 == nullptr) { - return false; - } - return Stmt1 == Stmt2; - } - return false; -} - bool DeclRewriter::isSingleDeclaration(DeclReplacement *N) { DeclStmt *Stmt = N->getStatement(); if (Stmt == nullptr) { @@ -729,8 +672,9 @@ bool FunctionDeclBuilder::VisitFunctionDecl(FunctionDecl *FD) { // Add new declarations to RewriteThese if it has changed if (RewriteReturn || RewriteParams) { - RewriteThese.insert( - new FunctionDeclReplacement(FD, NewSig, RewriteReturn, RewriteParams)); + RewriteThese.insert(std::make_pair( + FD, + new FunctionDeclReplacement(FD, NewSig, RewriteReturn, RewriteParams))); } return true; diff --git a/clang/lib/3C/RewriteUtils.cpp b/clang/lib/3C/RewriteUtils.cpp index f276fd74856e..b9eb8169b8e2 100644 --- a/clang/lib/3C/RewriteUtils.cpp +++ b/clang/lib/3C/RewriteUtils.cpp @@ -21,73 +21,6 @@ using namespace llvm; using namespace clang; -SourceLocation DComp::getDeclBegin(DeclReplacement *D) const { - SourceLocation Begin = - (*D->getStatement()->decls().begin())->getSourceRange().getBegin(); - for (const auto &DT : D->getStatement()->decls()) { - if (DT == D->getDecl()) - return Begin; - Begin = DT->getSourceRange().getEnd(); - } - llvm_unreachable("Declaration not found in DeclStmt."); -} - -SourceRange DComp::getReplacementSourceRange(DeclReplacement *D) const { - SourceRange Range = D->getSourceRange(SM); - - // Also take into account whether or not there is a multi-statement - // decl, because the generated ranges will overlap. - DeclStmt *LhStmt = D->getStatement(); - if (LhStmt && !LhStmt->isSingleDecl()) { - SourceLocation NewBegin = getDeclBegin(D); - Range.setBegin(NewBegin); - // This is needed to make the subsequent test inclusive. - Range.setEnd(Range.getEnd().getLocWithOffset(-1)); - } - - return Range; -} - -bool DComp::operator()(DeclReplacement *Lhs, DeclReplacement *Rhs) const { - // Does the source location of the Decl in lhs overlap at all with - // the source location of rhs? - SourceRange SrLhs = getReplacementSourceRange(Lhs); - SourceRange SrRhs = getReplacementSourceRange(Rhs); - - SourceLocation X1 = SrLhs.getBegin(); - SourceLocation X2 = SrLhs.getEnd(); - SourceLocation Y1 = SrRhs.getBegin(); - SourceLocation Y2 = SrRhs.getEnd(); - - if (Lhs->getStatement() == nullptr && Rhs->getStatement() == nullptr) { - // These are global declarations. Get the source location - // and compare them lexicographically. - PresumedLoc LHsPLocB = SM.getPresumedLoc(X2); - PresumedLoc RHsPLocE = SM.getPresumedLoc(Y2); - - // Are both the source location valid? - if (LHsPLocB.isValid() && RHsPLocE.isValid()) { - // They are in same fine? - if (!strcmp(LHsPLocB.getFilename(), RHsPLocE.getFilename())) { - // Are they in same line? - if (LHsPLocB.getLine() == RHsPLocE.getLine()) - return LHsPLocB.getColumn() < RHsPLocE.getColumn(); - - return LHsPLocB.getLine() < RHsPLocE.getLine(); - } - return strcmp(LHsPLocB.getFilename(), RHsPLocE.getFilename()) > 0; - } - return LHsPLocB.isValid(); - } - - bool Contained = SM.isBeforeInTranslationUnit(X1, Y2) && - SM.isBeforeInTranslationUnit(Y1, X2); - - if (Contained) - return false; - return SM.isBeforeInTranslationUnit(X2, Y1); -} - void GlobalVariableGroups::addGlobalDecl(Decl *VD, std::vector *VDVec) { if (VD && GlobVarGroups.find(VD) == GlobVarGroups.end()) { if (VDVec == nullptr) diff --git a/clang/lib/3C/StructInit.cpp b/clang/lib/3C/StructInit.cpp index 38cce593844e..886b584d26d4 100644 --- a/clang/lib/3C/StructInit.cpp +++ b/clang/lib/3C/StructInit.cpp @@ -64,7 +64,8 @@ void StructVariableInitializer::insertVarDecl(VarDecl *VD, DeclStmt *S) { TQ += " "; std::string ToReplace = getStorageQualifierString(VD) + TQ + tyToStr(Ty) + " " + VD->getName().str() + " = {}"; - RewriteThese.insert(new VarDeclReplacement(VD, S, ToReplace)); + RewriteThese.insert( + std::make_pair(VD, new VarDeclReplacement(VD, S, ToReplace))); } } diff --git a/clang/lib/3C/Utils.cpp b/clang/lib/3C/Utils.cpp index 4eb7a65776ab..30ee345f8607 100644 --- a/clang/lib/3C/Utils.cpp +++ b/clang/lib/3C/Utils.cpp @@ -105,24 +105,6 @@ clang::CheckedPointerKind getCheckedPointerKind(InteropTypeExpr *ItypeExpr) { return CheckedPointerKind::Unchecked; } -// Check if function body exists for the -// provided declaration. -bool hasFunctionBody(clang::Decl *D) { - // If this a parameter? - if (ParmVarDecl *PD = dyn_cast(D)) { - if (DeclContext *DC = PD->getParentFunctionOrMethod()) { - FunctionDecl *FD = dyn_cast(DC); - if (getDefinition(FD) != nullptr) { - return true; - } - } - return false; - } - // Else this should be within body and - // the function body should exist. - return true; -} - static std::string storageClassToString(StorageClass SC) { switch (SC) { case StorageClass::SC_Static: From 39756615d8b5c1bbf2e29432b533a39dc0f80e2d Mon Sep 17 00:00:00 2001 From: Aaron Eline Date: Fri, 6 Aug 2021 15:53:48 -0400 Subject: [PATCH 09/38] Don't insert generic instantiations in non-writable files (try 2) (#670) * Test cases * First draft, needs cleaning * Cleanup * Removed test * Testing * Formatting and organization in test --- clang/include/clang/3C/ConstraintVariables.h | 1 + clang/include/clang/3C/TypeVariableAnalysis.h | 7 +++++-- clang/lib/3C/TypeVariableAnalysis.cpp | 19 ++++++++++--------- clang/test/3C/canwrite_constraints.h | 6 ++++++ 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/clang/include/clang/3C/ConstraintVariables.h b/clang/include/clang/3C/ConstraintVariables.h index d0ae7f678925..58c086272ae3 100644 --- a/clang/include/clang/3C/ConstraintVariables.h +++ b/clang/include/clang/3C/ConstraintVariables.h @@ -325,6 +325,7 @@ class PointerVariableConstraint : public ConstraintVariable { // String representing declared bounds expression. std::string BoundsAnnotationStr; + // TODO can we move this to an optional instead of the -1? // Does this variable represent a generic type? Which one (or -1 for none)? // Generic types can be used with fewer restrictions, so this field is used // stop assignments with generic variables from forcing constraint variables diff --git a/clang/include/clang/3C/TypeVariableAnalysis.h b/clang/include/clang/3C/TypeVariableAnalysis.h index 31799a6d6923..093f3e0b96f5 100644 --- a/clang/include/clang/3C/TypeVariableAnalysis.h +++ b/clang/include/clang/3C/TypeVariableAnalysis.h @@ -33,7 +33,9 @@ class TypeVariableEntry { } bool getIsConsistent() const; + // Note: undefined behaviour if `getIsConsistent` is false QualType getType(); + // Note: undefined behaviour if `getIsConsistent` is false std::set &getConstraintVariables(); ConstraintVariable *getTypeParamConsVar(); @@ -53,6 +55,7 @@ class TypeVariableEntry { // Collection of constraint variables generated for all uses of the type // variable. Also should not be used when IsConsistent is false. + // TODO: accessor methods don't enforce this? std::set ArgConsVars; // A single constraint variable for solving the checked type of the type @@ -65,6 +68,7 @@ class TypeVariableEntry { // typed parameter. The values in the map are another maps from type variable // index in the called function's parameter list to the type the type variable // becomes (or null if it is not used consistently). +// TODO: use a better map implementation? typedef std::map> TypeVariableMapT; @@ -95,8 +99,7 @@ class TypeVarVisitor : public RecursiveASTVisitor, ConstraintResolver CR; TypeVariableMapT TVMap; - void insertBinding(CallExpr *CE, const int TyIdx, QualType Ty, CVarSet &CVs, - bool ForceInconsistent = false); + void insertBinding(CallExpr *CE, const int TyIdx, QualType Ty, CVarSet &CVs); }; bool typeArgsProvided(CallExpr *Call); diff --git a/clang/lib/3C/TypeVariableAnalysis.cpp b/clang/lib/3C/TypeVariableAnalysis.cpp index cd01625f526c..eaf3e3da99f4 100644 --- a/clang/lib/3C/TypeVariableAnalysis.cpp +++ b/clang/lib/3C/TypeVariableAnalysis.cpp @@ -72,7 +72,7 @@ bool TypeVarVisitor::VisitCastExpr(CastExpr *CE) { if (CHKCBindTemporaryExpr *TempE = dyn_cast(SubExpr)) SubExpr = TempE->getSubExpr(); - if (auto *Call = dyn_cast(SubExpr)) + if (auto *Call = dyn_cast(SubExpr)) { if (auto *FD = dyn_cast_or_null(Call->getCalleeDecl())) { FunctionDecl *FDef = getDefinition(FD); if (FDef == nullptr) @@ -87,6 +87,7 @@ bool TypeVarVisitor::VisitCastExpr(CastExpr *CE) { } } } + } return true; } @@ -96,11 +97,6 @@ bool TypeVarVisitor::VisitCallExpr(CallExpr *CE) { if (FDef == nullptr) FDef = FD; if (auto *FVCon = Info.getFuncConstraint(FDef, Context)) { - // if we need to rewrite it but can't (macro, etc), it isn't safe - bool ForcedInconsistent = - !typeArgsProvided(CE) && - (!Rewriter::isRewritable(CE->getExprLoc()) || - !canWrite(PersistentSourceLoc::mkPSL(CE, *Context).getFileName())); // Visit each function argument, and if it use a type variable, insert it // into the type variable binding map. unsigned int I = 0; @@ -113,7 +109,7 @@ bool TypeVarVisitor::VisitCallExpr(CallExpr *CE) { Expr *Uncast = A->IgnoreImpCasts(); std::set CVs = CR.getExprConstraintVarsSet(Uncast); - insertBinding(CE, TyIdx, Uncast->getType(), CVs, ForcedInconsistent); + insertBinding(CE, TyIdx, Uncast->getType(), CVs); } ++I; } @@ -150,8 +146,13 @@ bool TypeVarVisitor::VisitCallExpr(CallExpr *CE) { // the exact type variable is identified by the call expression where it is // used and the index of the type variable type in the function declaration. void TypeVarVisitor::insertBinding(CallExpr *CE, const int TyIdx, - clang::QualType Ty, CVarSet &CVs, - bool ForceInconsistent) { + clang::QualType Ty, CVarSet &CVs) { + // if we need to rewrite it but can't (macro, etc), it isn't safe + bool ForceInconsistent = + !typeArgsProvided(CE) && + (!Rewriter::isRewritable(CE->getExprLoc()) || + !canWrite(PersistentSourceLoc::mkPSL(CE, *Context).getFileName())); + assert(TyIdx >= 0 && "Creating a type variable binding without a type variable."); auto &CallTypeVarMap = TVMap[CE]; diff --git a/clang/test/3C/canwrite_constraints.h b/clang/test/3C/canwrite_constraints.h index cbcbd303c3dc..c6492a1b486f 100644 --- a/clang/test/3C/canwrite_constraints.h +++ b/clang/test/3C/canwrite_constraints.h @@ -71,11 +71,17 @@ void unwritable_func_with_itype_and_bounds(int *p // expected-warning@+1 {{Declaration in non-writable file}} _Itype_for_any(T) void my_generic_function(void *p : itype(_Ptr)); +// expected-warning@+1 {{Declaration in non-writable file}} +_Itype_for_any(T) void *my_generic_return(void) : itype(_Ptr); + void unwritable_type_argument() { int i; + int *b; // expected-warning {{Declaration in non-writable file}} // This warning relates to the atom representing the temporary pointer of // `&i`. https://github.com/correctcomputation/checkedc-clang/issues/618 would // make 3C smarter to avoid the need to constrain the temporary pointer. // expected-warning@+1 {{Expression in non-writable file}} my_generic_function(&i); + + b = my_generic_return(); // expected-warning {{Expression in non-writable file}} } From c3d3e5df613cfdf1eb1c10b5abb969d34b8857f0 Mon Sep 17 00:00:00 2001 From: Kyle Headley Date: Mon, 9 Aug 2021 12:40:32 -0400 Subject: [PATCH 10/38] No longer generate extra atoms to compare generic types (#655) * switch max atoms to min atoms to avoid excess * make type param info more available * Provide typevars to constraint collection * reset cache when typevar constraint is added * trivial test changes * Use constraint from TypeVarInfo in for malloc in possible * Fix root cause output for constraint variables deleted from persistent constraints * Fix failing array bounds tests * special case for realloc * include now-passing test in main test file * Tweak change from 2a5db6f to also handle unsage allocator calls * This fixes the 0 atoms affected root cause in test root_cause.c * better comment * Update clang/test/3C/root_cause.c Co-authored-by: Aaron Eline * whitespace in test checked line * Update clang/test/3C/hash.c Co-authored-by: Matt McCutchen (Correct Computation) Co-authored-by: John Kastner Co-authored-by: Aaron Eline Co-authored-by: Matt McCutchen (Correct Computation) --- clang/include/clang/3C/3CInteractiveData.h | 2 +- clang/include/clang/3C/ProgramInfo.h | 6 ++-- clang/lib/3C/ConstraintBuilder.cpp | 30 ++++++++-------- clang/lib/3C/ConstraintResolver.cpp | 40 ++++++++++++++++++---- clang/lib/3C/ConstraintVariables.cpp | 25 ++++---------- clang/lib/3C/ProgramInfo.cpp | 34 ++++++++++++++---- clang/lib/3C/TypeVariableAnalysis.cpp | 19 ++++++++-- clang/test/3C/hash.c | 5 ++- clang/test/3C/type_params.c | 11 +++++- clang/test/3C/type_params_xfail1.c | 28 --------------- 10 files changed, 118 insertions(+), 82 deletions(-) delete mode 100644 clang/test/3C/type_params_xfail1.c diff --git a/clang/include/clang/3C/3CInteractiveData.h b/clang/include/clang/3C/3CInteractiveData.h index 2c72045fc575..95012f45af11 100644 --- a/clang/include/clang/3C/3CInteractiveData.h +++ b/clang/include/clang/3C/3CInteractiveData.h @@ -50,7 +50,7 @@ class ConstraintsInfo { CVars TotalNonDirectWildAtoms; CVars InSrcNonDirectWildAtoms; std::set ValidSourceFiles; - std::map AtomSourceMap; + std::map AtomSourceMap; private: // Root cause map: This is the map of a Constraint var and a set of diff --git a/clang/include/clang/3C/ProgramInfo.h b/clang/include/clang/3C/ProgramInfo.h index 5e691c08bc77..10df434a59e1 100644 --- a/clang/include/clang/3C/ProgramInfo.h +++ b/clang/include/clang/3C/ProgramInfo.h @@ -90,6 +90,7 @@ class ProgramInfo : public ProgramVariableAdder { // Store CVarSet with an empty set of BoundsKey into persistent contents. void storePersistentConstraints(clang::Expr *E, const CVarSet &Vars, ASTContext *C); + void removePersistentConstraints(Expr *E, ASTContext *C); // Get constraint variable for the provided Decl CVarOption getVariable(clang::Decl *D, clang::ASTContext *C); @@ -169,6 +170,8 @@ class ProgramInfo : public ProgramVariableAdder { // expected that multiple entries will map to the same source location. std::map ExprLocations; + std::map DeletedAtomLocations; + //Performance stats PerformanceStats PerfS; @@ -210,8 +213,7 @@ class ProgramInfo : public ProgramVariableAdder { // Retrieves a FVConstraint* from a Decl (which could be static, or global) FVConstraint *getFuncFVConstraint(FunctionDecl *FD, ASTContext *C); - void insertIntoPtrSourceMap(const PersistentSourceLoc *PSL, - ConstraintVariable *CV); + void insertIntoPtrSourceMap(PersistentSourceLoc PSL, ConstraintVariable *CV); void computePtrLevelStats(); diff --git a/clang/lib/3C/ConstraintBuilder.cpp b/clang/lib/3C/ConstraintBuilder.cpp index 89db58b06cc2..d2425f8c1dae 100644 --- a/clang/lib/3C/ConstraintBuilder.cpp +++ b/clang/lib/3C/ConstraintBuilder.cpp @@ -131,9 +131,8 @@ class InlineStructDetector { // and imposing constraints on variables it uses class FunctionVisitor : public RecursiveASTVisitor { public: - explicit FunctionVisitor(ASTContext *C, ProgramInfo &I, FunctionDecl *FD, - TypeVarInfo &TVI) - : Context(C), Info(I), Function(FD), CB(Info, Context), TVInfo(TVI), + explicit FunctionVisitor(ASTContext *C, ProgramInfo &I, FunctionDecl *FD) + : Context(C), Info(I), Function(FD), CB(Info, Context), ISD() {} // T x = e @@ -217,9 +216,10 @@ class FunctionVisitor : public RecursiveASTVisitor { // Collect type parameters for this function call that are // consistently instantiated as single type in this function call. - std::set ConsistentTypeParams; - if (TFD != nullptr) - TVInfo.getConsistentTypeParams(E, ConsistentTypeParams); + auto ConsistentTypeParams = + Info.hasTypeParamBindings(E,Context) ? + Info.getTypeParamBindings(E,Context) : + ProgramInfo::CallTypeParamBindingsT(); // Now do the call: Constrain arguments to parameters (but ignore returns) if (FVCons.empty()) { @@ -436,7 +436,6 @@ class FunctionVisitor : public RecursiveASTVisitor { ProgramInfo &Info; FunctionDecl *Function; ConstraintResolver CB; - TypeVarInfo &TVInfo; InlineStructDetector ISD; }; @@ -485,9 +484,8 @@ class PtrToStructDef : public RecursiveASTVisitor { // for functions, variables, types, etc. that are visited. class ConstraintGenVisitor : public RecursiveASTVisitor { public: - explicit ConstraintGenVisitor(ASTContext *Context, ProgramInfo &I, - TypeVarInfo &TVI) - : Context(Context), Info(I), CB(Info, Context), TVInfo(TVI), ISD() {} + explicit ConstraintGenVisitor(ASTContext *Context, ProgramInfo &I) + : Context(Context), Info(I), CB(Info, Context), ISD() {} bool VisitVarDecl(VarDecl *G) { @@ -526,7 +524,7 @@ class ConstraintGenVisitor : public RecursiveASTVisitor { if (FL.isValid()) { // TODO: When would this ever be false? if (D->hasBody() && D->isThisDeclarationADefinition()) { Stmt *Body = D->getBody(); - FunctionVisitor FV = FunctionVisitor(Context, Info, D, TVInfo); + FunctionVisitor FV = FunctionVisitor(Context, Info, D); FV.TraverseStmt(Body); if (AllTypes) { // Only do this, if all types is enabled. @@ -551,7 +549,6 @@ class ConstraintGenVisitor : public RecursiveASTVisitor { ASTContext *Context; ProgramInfo &Info; ConstraintResolver CB; - TypeVarInfo &TVInfo; InlineStructDetector ISD; }; @@ -669,7 +666,7 @@ void ConstraintBuilderConsumer::HandleTranslationUnit(ASTContext &C) { ConstraintResolver CSResolver(Info, &C); ContextSensitiveBoundsKeyVisitor CSBV = ContextSensitiveBoundsKeyVisitor(&C, Info, &CSResolver); - ConstraintGenVisitor GV = ConstraintGenVisitor(&C, Info, TV); + ConstraintGenVisitor GV = ConstraintGenVisitor(&C, Info); TranslationUnitDecl *TUD = C.getTranslationUnitDecl(); StatsRecorder SR(&C, &Info); @@ -681,13 +678,16 @@ void ConstraintBuilderConsumer::HandleTranslationUnit(ASTContext &C) { CSBV.TraverseDecl(D); TV.TraverseDecl(D); - GV.TraverseDecl(D); - SR.TraverseDecl(D); } // Store type variable information for use in rewriting TV.setProgramInfoTypeVars(); + for (const auto &D : TUD->decls()) { + GV.TraverseDecl(D); + SR.TraverseDecl(D); + } + if (Verbose) errs() << "Done analyzing\n"; diff --git a/clang/lib/3C/ConstraintResolver.cpp b/clang/lib/3C/ConstraintResolver.cpp index 602644a09b6e..724b37bca8c8 100644 --- a/clang/lib/3C/ConstraintResolver.cpp +++ b/clang/lib/3C/ConstraintResolver.cpp @@ -151,6 +151,26 @@ inline CSetBkeyPair pairWithEmptyBkey(const CVarSet &Vars) { return std::make_pair(Vars, EmptyBSet); } +// Get the return type of the function from the TypeVars, that is, from +// the local instantiation of a generic function. Or the regular return +// constraint if it's not generic +ConstraintVariable *localReturnConstraint( + FVConstraint *FV, + ProgramInfo::CallTypeParamBindingsT TypeVars, + Constraints &CS) { + int TyVarIdx = FV->getExternalReturn()->getGenericIndex(); + // Check if local type vars are available + if (TypeVars.find(TyVarIdx) != TypeVars.end() && + TypeVars[TyVarIdx] != nullptr) { + ConstraintVariable *CV = TypeVars[TyVarIdx]; + if (FV->getExternalReturn()->hasBoundsKey()) + CV->setBoundsKey(FV->getExternalReturn()->getBoundsKey()); + return CV; + } else { + return FV->getExternalReturn(); + } +} + // Returns a pair of set of ConstraintVariables and set of BoundsKey // after evaluating the expression E. Will explore E recursively, but will // ignore parts of it that do not contribute to the final result. @@ -403,6 +423,10 @@ CSetBkeyPair ConstraintResolver::getExprConstraintVars(Expr *E) { CVarSet ReturnCVs; BKeySet ReturnBSet = EmptyBSet; + ProgramInfo::CallTypeParamBindingsT TypeVars; + if (Info.hasTypeParamBindings(CE, Context)) + TypeVars = Info.getTypeParamBindings(CE, Context); + // Here, we need to look up the target of the call and return the // constraints for the return value of that function. QualType ExprType = E->getType(); @@ -418,10 +442,10 @@ CSetBkeyPair ConstraintResolver::getExprConstraintVars(Expr *E) { for (ConstraintVariable *C : Tmp.first) { if (FVConstraint *FV = dyn_cast(C)) { - ReturnCVs.insert(FV->getExternalReturn()); + ReturnCVs.insert(localReturnConstraint(FV,TypeVars,CS)); } else if (PVConstraint *PV = dyn_cast(C)) { if (FVConstraint *FV = PV->getFV()) - ReturnCVs.insert(FV->getExternalReturn()); + ReturnCVs.insert(localReturnConstraint(FV,TypeVars,CS)); } } } else if (DeclaratorDecl *FD = dyn_cast(D)) { @@ -429,11 +453,13 @@ CSetBkeyPair ConstraintResolver::getExprConstraintVars(Expr *E) { if (isFunctionAllocator(std::string(FD->getName()))) { bool DidInsert = false; IsAllocator = true; - if (CE->getNumArgs() > 0) { + if (TypeVars.find(0) != TypeVars.end() && TypeVars[0] != nullptr) { + ReturnCVs.insert(TypeVars[0]); + DidInsert = true; + } else if (CE->getNumArgs() > 0) { QualType ArgTy; std::string FuncName = FD->getNameAsString(); - ConstAtom *A; - A = analyzeAllocExpr(CE, CS, ArgTy, FuncName, Context); + ConstAtom *A = analyzeAllocExpr(CE, CS, ArgTy, FuncName, Context); if (A) { std::string N(FD->getName()); N = "&" + N; @@ -465,13 +491,13 @@ CSetBkeyPair ConstraintResolver::getExprConstraintVars(Expr *E) { assert(CV.hasValue() && "Function without constraint variable."); /* Direct function call */ if (FVConstraint *FVC = dyn_cast(&CV.getValue())) - ReturnCVs.insert(FVC->getExternalReturn()); + ReturnCVs.insert(localReturnConstraint(FVC,TypeVars,CS)); /* Call via function pointer */ else { PVConstraint *Tmp = dyn_cast(&CV.getValue()); assert(Tmp != nullptr); if (FVConstraint *FVC = Tmp->getFV()) - ReturnCVs.insert(FVC->getExternalReturn()); + ReturnCVs.insert(localReturnConstraint(FVC,TypeVars,CS)); else { // No FVConstraint -- make WILD. auto *TmpFV = new FVConstraint(); diff --git a/clang/lib/3C/ConstraintVariables.cpp b/clang/lib/3C/ConstraintVariables.cpp index 8b6e0254514b..0506df850010 100644 --- a/clang/lib/3C/ConstraintVariables.cpp +++ b/clang/lib/3C/ConstraintVariables.cpp @@ -1786,10 +1786,11 @@ void constrainConsVarGeq(ConstraintVariable *LHS, ConstraintVariable *RHS, // Only generate constraint if LHS is not a base type. if (CLHS.size() != 0) { - if (CLHS.size() == CRHS.size() || PCLHS->getIsGeneric() || - PCRHS->getIsGeneric()) { - unsigned Max = std::max(CLHS.size(), CRHS.size()); - for (unsigned N = 0; N < Max; N++) { + if (CLHS.size() == CRHS.size() || + (CLHS.size() < CRHS.size() && PCLHS->getIsGeneric()) || + (CLHS.size() > CRHS.size() && PCRHS->getIsGeneric())) { + unsigned Min = std::min(CLHS.size(), CRHS.size()); + for (unsigned N = 0; N < Min; N++) { Atom *IAtom = PCLHS->getAtom(N, CS); Atom *JAtom = PCRHS->getAtom(N, CS); if (IAtom == nullptr || JAtom == nullptr) @@ -1931,20 +1932,8 @@ void PointerVariableConstraint::mergeDeclaration(ConstraintVariable *FromCV, } Atom *PointerVariableConstraint::getAtom(unsigned AtomIdx, Constraints &CS) { - if (AtomIdx < Vars.size()) { - // If index is in bounds, just return the atom. - return Vars[AtomIdx]; - } - if (getIsGeneric() && AtomIdx == Vars.size()) { - // Polymorphic types don't know how "deep" their pointers are beforehand so, - // we need to create new atoms for new pointer levels on the fly. - std::string Stars(Vars.size(), '*'); - Atom *A = CS.getFreshVar(Name + Stars, VarAtom::V_Other); - Vars.push_back(A); - SrcVars.push_back(CS.getWild()); - return A; - } - return nullptr; + assert(AtomIdx < Vars.size()); + return Vars[AtomIdx]; } void PointerVariableConstraint::equateWithItype( diff --git a/clang/lib/3C/ProgramInfo.cpp b/clang/lib/3C/ProgramInfo.cpp index 90c4c692f4fc..34421bfe664d 100644 --- a/clang/lib/3C/ProgramInfo.cpp +++ b/clang/lib/3C/ProgramInfo.cpp @@ -829,6 +829,24 @@ void ProgramInfo::storePersistentConstraints(Expr *E, const CSetBkeyPair &Vars, ExprLocations[Key] = PSL; } +void ProgramInfo::removePersistentConstraints(Expr *E, ASTContext *C) { + assert(hasPersistentConstraints(E, C) && + "Persistent constraints not present."); + + IDAndTranslationUnit Key = getExprKey(E, C); + + // Save VarAtom locations so they can be used to assign source locations to + // root causes. + for (auto *CV : ExprConstraintVars[Key].first) + if (auto *PVC = dyn_cast(CV)) + for (Atom *A : PVC->getCvars()) + if (auto *VA = dyn_cast(A)) + DeletedAtomLocations[VA->getLoc()] = ExprLocations[Key]; + + ExprConstraintVars.erase(Key); + ExprLocations.erase(Key); +} + // The Rewriter won't let us re-write things that are in macros. So, we // should check to see if what we just added was defined within a macro. // If it was, we should constrain it to top. This is sad. Hopefully, @@ -1043,12 +1061,14 @@ bool ProgramInfo::computeInterimConstraintState( // Variables before ExprConstraintVars and making insertIntoPtrSourceMap not // overwrite a PSL already recorded for a given atom. for (const auto &I : Variables) - insertIntoPtrSourceMap(&(I.first), I.second); + insertIntoPtrSourceMap(I.first, I.second); for (const auto &I : ExprConstraintVars) { - PersistentSourceLoc *PSL = &ExprLocations[I.first]; + PersistentSourceLoc PSL = ExprLocations[I.first]; for (auto *J : I.second.first) insertIntoPtrSourceMap(PSL, J); } + for (auto E : DeletedAtomLocations) + CState.AtomSourceMap.insert(std::make_pair(E.first, E.second)); auto &WildPtrsReason = CState.RootWildAtomsWithReason; for (auto *CurrC : CS.getConstraints()) { @@ -1056,9 +1076,9 @@ bool ProgramInfo::computeInterimConstraintState( VarAtom *VLhs = dyn_cast(EC->getLHS()); if (EC->constraintIsChecked() && dyn_cast(EC->getRHS())) { PersistentSourceLoc PSL = EC->getLocation(); - const PersistentSourceLoc *APSL = CState.AtomSourceMap[VLhs->getLoc()]; - if (!PSL.valid() && APSL && APSL->valid()) - PSL = *APSL; + PersistentSourceLoc APSL = CState.AtomSourceMap[VLhs->getLoc()]; + if (!PSL.valid() && APSL.valid()) + PSL = APSL; WildPointerInferenceInfo Info(EC->getReason(), PSL); WildPtrsReason.insert(std::make_pair(VLhs->getLoc(), Info)); } @@ -1069,9 +1089,9 @@ bool ProgramInfo::computeInterimConstraintState( return true; } -void ProgramInfo::insertIntoPtrSourceMap(const PersistentSourceLoc *PSL, +void ProgramInfo::insertIntoPtrSourceMap(PersistentSourceLoc PSL, ConstraintVariable *CV) { - std::string FilePath = PSL->getFileName(); + std::string FilePath = PSL.getFileName(); if (canWrite(FilePath)) CState.ValidSourceFiles.insert(FilePath); diff --git a/clang/lib/3C/TypeVariableAnalysis.cpp b/clang/lib/3C/TypeVariableAnalysis.cpp index eaf3e3da99f4..63c2d5ce11c8 100644 --- a/clang/lib/3C/TypeVariableAnalysis.cpp +++ b/clang/lib/3C/TypeVariableAnalysis.cpp @@ -127,12 +127,27 @@ bool TypeVarVisitor::VisitCallExpr(CallExpr *CE) { // Constrain this variable GEQ the function arguments using the type // variable so if any of them are wild, the type argument will also be - // an unchecked pointer. - constrainConsVarGeq(P, TVEntry.second.getConstraintVariables(), + // an unchecked pointer. Except for realloc, which has special casing + // elsewhere, especially `ConstraintResolver::getExprConstraintVars` + // using variable `ReallocFlow`. Because `realloc` can take a wild + // pointer and return a safe one. + if (FD->getNameAsString() == "realloc") { + constrainConsVarGeq(P, TVEntry.second.getConstraintVariables(), + Info.getConstraints(), nullptr, Wild_to_Safe, false, + &Info); + + } else { + constrainConsVarGeq(P, TVEntry.second.getConstraintVariables(), Info.getConstraints(), nullptr, Safe_to_Wild, false, &Info); + } TVEntry.second.setTypeParamConsVar(P); + // Since we've changed the constraint variable for this context, we + // need to remove the cache from the old one. Our new info will be + // used next request. + if (Info.hasPersistentConstraints(CE,Context)) + Info.removePersistentConstraints(CE,Context); } else { // TODO: This might be too cautious. CR.constraintAllCVarsToWild(TVEntry.second.getConstraintVariables(), diff --git a/clang/test/3C/hash.c b/clang/test/3C/hash.c index 4bec73c58213..dc0d0135b9bb 100644 --- a/clang/test/3C/hash.c +++ b/clang/test/3C/hash.c @@ -13,7 +13,10 @@ _Itype_for_any(T) void vsf_sysutil_memclr(void *p_dest : itype(_Array_ptr) byte_count(size), unsigned int size) -// CHECK_ALL: _Itype_for_any(T) void vsf_sysutil_memclr(void *p_dest : itype(_Array_ptr) byte_count(size), unsigned int size) +// CHECK_ALL: _Itype_for_any(T) void vsf_sysutil_memclr(void *p_dest +// CHECK_ALL-NEXT: : itype(_Array_ptr) +// CHECK_ALL-NEXT: byte_count(size), +// CHECK_ALL-NEXT: unsigned int size) { /* Safety */ if (size == 0) { diff --git a/clang/test/3C/type_params.c b/clang/test/3C/type_params.c index e265885ca47d..edeee992e8a2 100644 --- a/clang/test/3C/type_params.c +++ b/clang/test/3C/type_params.c @@ -175,7 +175,7 @@ void *example1(void *ptr, unsigned int size) { // Issue #349. Check that the parameter doesn't inherit the double pointer // argument within do_doubleptr _Itype_for_any(T) void incoming_doubleptr(void *ptr : itype(_Array_ptr)) { - // CHECK_ALL: _Itype_for_any(T) void incoming_doubleptr(void *ptr : itype(_Array_ptr)) { + // CHECK_ALL: _Itype_for_any(T) void incoming_doubleptr(void *ptr : itype(_Array_ptr) count(5)) { return; } @@ -185,3 +185,12 @@ void do_doubleptr(int count) { incoming_doubleptr(arr); // CHECK_ALL: incoming_doubleptr<_Ptr>(arr); } + +// make sure adding this function doesn't infer +// with the type of the previous one +// Though it does currently add the `count(5)` +// to the param of incomming_doubleptr +void interfere_doubleptr(void) { + float fl _Checked[5][5] = {}; + incoming_doubleptr(fl); +} diff --git a/clang/test/3C/type_params_xfail1.c b/clang/test/3C/type_params_xfail1.c deleted file mode 100644 index b8eb5604fb5d..000000000000 --- a/clang/test/3C/type_params_xfail1.c +++ /dev/null @@ -1,28 +0,0 @@ -// RUN: 3c -base-dir=%S -addcr -alltypes %s -- | FileCheck -match-full-lines %s - -// XFAIL: * - -// This fails because the type variable is used to constrain both calls to -// `incoming_doubleptr`. To be correct, the usage of a type variable should be -// independent at each call site. - -// adapted from type_params.c -#include - -_Itype_for_any(T) void incoming_doubleptr(_Array_ptr ptr - : itype(_Array_ptr)) { - return; -} - -void do_doubleptr(int count) { - int **arr = malloc(sizeof(int *) * count); - // CHECK: _Array_ptr<_Ptr> arr : count(count) = malloc<_Ptr>(sizeof(int*) * count); - incoming_doubleptr(arr); -} - -// adding this function changes the infered type of the previous one -// unnecessarily -void interfere_doubleptr(void) { - float fl _Checked[5][5] = {}; - incoming_doubleptr(fl); -} From d01fc6603f4b0734192e8657e1fbf93010cb12f1 Mon Sep 17 00:00:00 2001 From: John Kastner Date: Mon, 9 Aug 2021 14:04:40 -0400 Subject: [PATCH 11/38] Fix for when a single file is the main file of multiple translation units (#666) This is accomplished by identify the current translation unit using the index of the translation unit in the vector of all translation units being processed instead of name of the main file of the translation unit. This is done for the primary expression map which previously used a (file name, node id) pair as the key, and for the type variable map which previously used the (file name, line, column) triple as the key. Both maps now use (TU index, node id). In debugging the issue fixed by this the change, a few uninitialized fields were discovered in PointerVaraibleConstraint. These did not cause the issue (or any other concrete issue I have been able to find), but they have been initialized anyways. The constructor used for copying ConstraintVariables has been cleaned up and refactored to use member initialization lists. The constructor is now used a few places where a ConstraintVariable was previously constructed by manually extracting fields from an existing ConstraintVariable into a new one. --- clang/include/clang/3C/ConstraintResolver.h | 2 - clang/include/clang/3C/ConstraintVariables.h | 52 +++++-- clang/include/clang/3C/ProgramInfo.h | 34 +++- clang/lib/3C/3C.cpp | 2 + clang/lib/3C/ConstraintResolver.cpp | 5 +- clang/lib/3C/ConstraintVariables.cpp | 156 ++++++++++--------- clang/lib/3C/ProgramInfo.cpp | 38 +++-- clang/test/3C/multiple_tu.c | 22 +++ 8 files changed, 190 insertions(+), 121 deletions(-) create mode 100644 clang/test/3C/multiple_tu.c diff --git a/clang/include/clang/3C/ConstraintResolver.h b/clang/include/clang/3C/ConstraintResolver.h index 79f5225f53ff..8e36be30c001 100644 --- a/clang/include/clang/3C/ConstraintResolver.h +++ b/clang/include/clang/3C/ConstraintResolver.h @@ -69,8 +69,6 @@ class ConstraintResolver { CVarSet getInvalidCastPVCons(CastExpr *E); - // Update a PVConstraint with one additional level of indirection - PVConstraint *addAtom(PVConstraint *PVC, ConstAtom *NewA, Constraints &CS); CVarSet addAtomAll(CVarSet CVS, ConstAtom *PtrTyp, Constraints &CS); CVarSet pvConstraintFromType(QualType TypE); diff --git a/clang/include/clang/3C/ConstraintVariables.h b/clang/include/clang/3C/ConstraintVariables.h index 58c086272ae3..5bc7f4a7b92a 100644 --- a/clang/include/clang/3C/ConstraintVariables.h +++ b/clang/include/clang/3C/ConstraintVariables.h @@ -191,6 +191,9 @@ class ConstraintVariable { const std::string &ReasonUnchangeable, PersistentSourceLoc *PSL) = 0; + // Copy this variable and replace all VarAtoms with fresh VarAtoms. Using + // fresh atoms allows the new variable to solve to different types than the + // original. virtual ConstraintVariable *getCopy(Constraints &CS) = 0; virtual ~ConstraintVariable(){}; @@ -243,18 +246,35 @@ class PointerVariableConstraint : public ConstraintVariable { RestrictQualification }; + // Get a constraint variable representing a pointer which has been constrained + // to WILD for the specified reason. + // TODO: This method always returns a constraint variable containing one atom. + // This causes problems if the variable is later used as a deeper + // pointer type. See correctcomputation/checkedc-clang#673. static PointerVariableConstraint * getWildPVConstraint(Constraints &CS, const std::string &Rsn, PersistentSourceLoc *PSL = nullptr); - static PointerVariableConstraint *getPtrPVConstraint(Constraints &CS); + + // Get constraint variables representing values that are not pointers. If a + // meaningful name can be assigned to the value, the second method should be + // used to get higher quality root-cause and debugging output. static PointerVariableConstraint *getNonPtrPVConstraint(Constraints &CS); static PointerVariableConstraint *getNamedNonPtrPVConstraint(StringRef Name, Constraints &CS); + // Given a constraint variable, return a new constraint variable with the same + // atoms as the original, but with one fresh atoms added to the front of the + // Vars vector. This effectively takes the address of the pointer represented + // by the original constraint variable. static PointerVariableConstraint * addAtomPVConstraint(PointerVariableConstraint *PVC, ConstAtom *PtrTyp, Constraints &CS); + // Return a new constraint variable representing the result of dereferencing + // the input constraint variable. This is accomplished by first copying the + // parameter, and then removing the first element of the Vars vector in the + // copy. The remaining VarAtoms in the copy are the same as those in the + // original. static PointerVariableConstraint * derefPVConstraint(PointerVariableConstraint *PVC); @@ -320,7 +340,11 @@ class PointerVariableConstraint : public ConstraintVariable { // Get solution for the atom of a pointer. const ConstAtom *getSolution(const Atom *A, const EnvironmentMap &E) const; - PointerVariableConstraint(PointerVariableConstraint *Ot, Constraints &CS); + // Construct a copy of this variable, reusing all VarAtoms. To instead obtains + // a copy of the constraint variable which contains fresh VarAtoms (allowing + // the new variable to solve to different type than the original), instead use + // the method getCopy. + PointerVariableConstraint(PointerVariableConstraint *Ot); PointerVariableConstraint *Parent; // String representing declared bounds expression. std::string BoundsAnnotationStr; @@ -348,15 +372,14 @@ class PointerVariableConstraint : public ConstraintVariable { // Is this a pointer to void? Possibly with multiple levels of indirection. bool IsVoidPtr; - // Constructor for when we know a CVars and a type string. - PointerVariableConstraint(CAtoms V, std::vector SV, - std::string T, std::string Name, - FunctionVariableConstraint *F, std::string Is, - int Generic = -1) - : ConstraintVariable(PointerVariable, "" /*not used*/, Name), BaseType(T), - Vars(V), SrcVars(SV), FV(F), SrcHasItype(!Is.empty()), ItypeStr(Is), - PartOfFuncPrototype(false), Parent(nullptr), BoundsAnnotationStr(""), - GenericIndex(Generic), IsZeroWidthArray(false), IsVoidPtr(false) {} + // Construct and empty PointerVariableConstraint with only the name set. All + // other fields are initialized to default values. This is used to construct + // variables for non-pointer expressions. + PointerVariableConstraint(std::string Name) : + ConstraintVariable(PointerVariable, "", Name), FV(nullptr), + SrcHasItype(false), PartOfFuncPrototype(false), Parent(nullptr), + GenericIndex(-1), IsZeroWidthArray(false), IsTypedef(false), TDT(nullptr), + TypedefLevelInfo({}), IsVoidPtr(false) {} public: std::string getTy() const { return BaseType; } @@ -555,7 +578,7 @@ class FVComponentVariable { // when a re-write of a function pointer is needed. class FunctionVariableConstraint : public ConstraintVariable { private: - FunctionVariableConstraint(FunctionVariableConstraint *Ot, Constraints &CS); + FunctionVariableConstraint(FunctionVariableConstraint *Ot); // N constraints on the return value of the function. FVComponentVariable ReturnVar; @@ -578,11 +601,6 @@ class FunctionVariableConstraint : public ConstraintVariable { void equateFVConstraintVars(ConstraintVariable *CV, ProgramInfo &Info) const; public: - FunctionVariableConstraint() - : ConstraintVariable(FunctionVariable, "", ""), FileName(""), - Hasproto(false), Hasbody(false), IsStatic(false), Parent(nullptr), - IsFunctionPtr(false) {} - FunctionVariableConstraint(clang::DeclaratorDecl *D, ProgramInfo &I, const clang::ASTContext &C); FunctionVariableConstraint(clang::TypedefDecl *D, ProgramInfo &I, diff --git a/clang/include/clang/3C/ProgramInfo.h b/clang/include/clang/3C/ProgramInfo.h index 10df434a59e1..1ba753406adf 100644 --- a/clang/include/clang/3C/ProgramInfo.h +++ b/clang/include/clang/3C/ProgramInfo.h @@ -45,12 +45,7 @@ typedef std::pair CSetBkeyPair; class ProgramInfo : public ProgramVariableAdder { public: - // This map holds similar information as the type variable map in - // ConstraintBuilder.cpp, but it is stored in a form that is usable during - // rewriting. typedef std::map CallTypeParamBindingsT; - typedef std::map - TypeParamBindingsT; typedef std::map ExternalFunctionMapType; typedef std::map StaticFunctionMapType; @@ -145,6 +140,12 @@ class ProgramInfo : public ProgramVariableAdder { void addTypedef(PersistentSourceLoc PSL, bool CanRewriteDef, TypedefDecl *TD, ASTContext &C); + // Store mapping from ASTContexts to a unique index in the ASTs vector in + // the ProgramInfo object. This function must be called prior to any AST + // traversals so that the map is populated. + void registerTranslationUnits( + const std::vector> &ASTs); + private: // List of constraint variables for declarations, indexed by their location in // the source. This information persists across invocations of the constraint @@ -156,9 +157,11 @@ class ProgramInfo : public ProgramVariableAdder { // rewritten. std::map TypedefVars; - // A pair containing an AST node ID and the name of the main file in the - // translation unit. Used as a key to index expression in the following maps. - typedef std::pair IDAndTranslationUnit; + // A pair containing an AST node ID and an index that uniquely identifies the + // translation unit. Translation unit identifiers are drawn from the + // TranslationUnitIdxMap. Used as a key to index expression in the following + // maps. + typedef std::pair IDAndTranslationUnit; IDAndTranslationUnit getExprKey(clang::Expr *E, clang::ASTContext *C) const; // Map with the similar purpose as the Variables map. This stores a set of @@ -170,6 +173,12 @@ class ProgramInfo : public ProgramVariableAdder { // expected that multiple entries will map to the same source location. std::map ExprLocations; + // This map holds similar information as the type variable map in + // ConstraintBuilder.cpp, but it is stored in a form that is usable during + // rewriting. + typedef std::map + TypeParamBindingsT; + std::map DeletedAtomLocations; //Performance stats @@ -199,6 +208,15 @@ class ProgramInfo : public ProgramVariableAdder { // instantiated so they can be inserted during rewriting. TypeParamBindingsT TypeParamBindings; + // Maps each ASTContext to a unique index in the vector of ASTs being + // processed. This is used to uniquely determine the translation unit an AST + // belongs to given its corresponding ASTContext. By using this index instead + // of the name of the main file, this uniquely identifies the translation unit + // even when a file is the main file of multiple translation units. The values + // in this map are used as part of the IDAndTranslationUnit which is the type + // used as keys for maps from ASTNodes. + std::map TranslationUnitIdxMap; + // Special-case handling for decl introductions. For the moment this covers: // * void-typed variables // * va_list-typed variables diff --git a/clang/lib/3C/3C.cpp b/clang/lib/3C/3C.cpp index 73096652eb98..4b019965f661 100644 --- a/clang/lib/3C/3C.cpp +++ b/clang/lib/3C/3C.cpp @@ -511,6 +511,8 @@ bool _3CInterface::parseASTs() { int ToolExitStatus = Tool->run(&Action); HadNonDiagnosticError |= (ToolExitStatus != 0); + GlobalProgramInfo.registerTranslationUnits(ASTs); + return isSuccessfulSoFar(); } diff --git a/clang/lib/3C/ConstraintResolver.cpp b/clang/lib/3C/ConstraintResolver.cpp index 724b37bca8c8..88c77bf9806d 100644 --- a/clang/lib/3C/ConstraintResolver.cpp +++ b/clang/lib/3C/ConstraintResolver.cpp @@ -500,8 +500,9 @@ CSetBkeyPair ConstraintResolver::getExprConstraintVars(Expr *E) { ReturnCVs.insert(localReturnConstraint(FVC,TypeVars,CS)); else { // No FVConstraint -- make WILD. - auto *TmpFV = new FVConstraint(); - ReturnCVs.insert(TmpFV); + std::string Rsn = "Can't get return variable of function call."; + PersistentSourceLoc PL = PersistentSourceLoc::mkPSL(CE, *Context); + ReturnCVs.insert(PVConstraint::getWildPVConstraint(CS, Rsn, &PL)); } } } diff --git a/clang/lib/3C/ConstraintVariables.cpp b/clang/lib/3C/ConstraintVariables.cpp index 0506df850010..1e208ab034ee 100644 --- a/clang/lib/3C/ConstraintVariables.cpp +++ b/clang/lib/3C/ConstraintVariables.cpp @@ -48,47 +48,54 @@ std::string ConstraintVariable::getRewritableOriginalTy() const { PointerVariableConstraint *PointerVariableConstraint::getWildPVConstraint( Constraints &CS, const std::string &Rsn, PersistentSourceLoc *PSL) { - VarAtom *VA = - CS.createFreshGEQ("wildvar", VarAtom::V_Other, CS.getWild(), Rsn, PSL); - return new PointerVariableConstraint({VA}, {CS.getWild()}, "unsigned", - "wildvar", nullptr, ""); + auto *WildPVC = new PointerVariableConstraint("wildvar"); + VarAtom *VA = CS.createFreshGEQ("wildvar", VarAtom::V_Other, CS.getWild(), + Rsn, PSL); + WildPVC->Vars.push_back(VA); + WildPVC->SrcVars.push_back(CS.getWild()); + + return WildPVC; } PointerVariableConstraint * PointerVariableConstraint::getNonPtrPVConstraint(Constraints &CS) { static PointerVariableConstraint *GlobalNonPtrPV = nullptr; - if (GlobalNonPtrPV == nullptr) { - return new PointerVariableConstraint({}, {}, "unsigned", "basevar", nullptr, - ""); - } + if (GlobalNonPtrPV == nullptr) + GlobalNonPtrPV = new PointerVariableConstraint("basevar"); return GlobalNonPtrPV; } PointerVariableConstraint * PointerVariableConstraint::getNamedNonPtrPVConstraint(StringRef Name, Constraints &CS) { - return new PointerVariableConstraint({}, {}, "unsigned", std::string(Name), - nullptr, ""); + return new PointerVariableConstraint(std::string(Name)); } PointerVariableConstraint * PointerVariableConstraint::derefPVConstraint(PointerVariableConstraint *PVC) { - std::vector Vars = PVC->Vars; - std::vector SrcVars = PVC->SrcVars; - assert(!PVC->Vars.empty() && !SrcVars.empty()); - Vars.erase(Vars.begin()); - SrcVars.erase(SrcVars.begin()); - return new PointerVariableConstraint(Vars, SrcVars, PVC->getTy(), - PVC->getName(), PVC->getFV(), - PVC->getItype()); + // Make a copy of the PVConstraint using the same VarAtoms + auto *Copy = new PointerVariableConstraint(PVC); + + // Get rid of the outer atom to dereference the pointer. + assert(!Copy->Vars.empty() && !Copy->SrcVars.empty()); + Copy->Vars.erase(Copy->Vars.begin()); + Copy->SrcVars.erase(Copy->SrcVars.begin()); + + // This can't have bounds because bounds only apply to the top pointer level. + Copy->BKey = 0; + Copy->ValidBoundsKey = false; + + return Copy; } PointerVariableConstraint *PointerVariableConstraint::addAtomPVConstraint( PointerVariableConstraint *PVC, ConstAtom *PtrTyp, Constraints &CS) { - VarAtom *NewA = CS.getFreshVar("&" + PVC->Name, VarAtom::V_Other); + auto *Copy = new PointerVariableConstraint(PVC); + std::vector &Vars = Copy->Vars; + std::vector &SrcVars = Copy->SrcVars; + + VarAtom *NewA = CS.getFreshVar("&" + Copy->Name, VarAtom::V_Other); CS.addConstraint(CS.createGeq(NewA, PtrTyp, false)); - std::vector Vars = PVC->Vars; - std::vector SrcVars = PVC->SrcVars; // Add a constraint between the new atom and any existing atom for this // pointer. This is the same constraint that is added between atoms of a @@ -100,49 +107,26 @@ PointerVariableConstraint *PointerVariableConstraint::addAtomPVConstraint( Vars.insert(Vars.begin(), NewA); SrcVars.insert(SrcVars.begin(), PtrTyp); - return new PointerVariableConstraint(Vars, SrcVars, PVC->BaseType, PVC->Name, - PVC->FV, PVC->ItypeStr); + + return Copy; } PointerVariableConstraint::PointerVariableConstraint( - PointerVariableConstraint *Ot, Constraints &CS) - : ConstraintVariable(ConstraintVariable::PointerVariable, Ot->OriginalType, - Ot->Name), - FV(nullptr), PartOfFuncPrototype(Ot->PartOfFuncPrototype) { - this->ArrSizes = Ot->ArrSizes; - this->ArrSizeStrs = Ot->ArrSizeStrs; + PointerVariableConstraint *Ot) + : ConstraintVariable(ConstraintVariable::PointerVariable, Ot->OriginalType, + Ot->Name), BaseType(Ot->BaseType), Vars(Ot->Vars), + SrcVars(Ot->SrcVars), FV(Ot->FV), QualMap(Ot->QualMap), + ArrSizes(Ot->ArrSizes), ArrSizeStrs(Ot->ArrSizeStrs), + SrcHasItype(Ot->SrcHasItype), ItypeStr(Ot->ItypeStr), + PartOfFuncPrototype(Ot->PartOfFuncPrototype), Parent(Ot), + BoundsAnnotationStr(Ot->BoundsAnnotationStr), + GenericIndex(Ot->GenericIndex), IsZeroWidthArray(Ot->IsZeroWidthArray), + IsTypedef(Ot->IsTypedef), TDT(Ot->TDT), TypedefString(Ot->TypedefString), + TypedefLevelInfo(Ot->TypedefLevelInfo), IsVoidPtr(Ot->IsVoidPtr) { + // These are fields of the super class Constraint Variable this->HasEqArgumentConstraints = Ot->HasEqArgumentConstraints; this->ValidBoundsKey = Ot->ValidBoundsKey; this->BKey = Ot->BKey; - - assert(Ot->Vars.size() == Ot->SrcVars.size()); - auto VAIt = Ot->Vars.begin(); - auto CAIt = Ot->SrcVars.begin(); - while (VAIt != Ot->Vars.end() && CAIt != Ot->SrcVars.end()) { - if (ConstAtom *CA = dyn_cast(*VAIt)) { - this->Vars.push_back(CA); - this->SrcVars.push_back(CA); - } else if (VarAtom *VA = dyn_cast(*VAIt)) { - VarAtom *FreshVA = CS.getFreshVar(VA->getName(), VA->getVarKind()); - this->Vars.push_back(FreshVA); - this->SrcVars.push_back(*CAIt); - if (!isa(*CAIt)) - CS.addConstraint(CS.createGeq(*CAIt, FreshVA, false)); - } - ++VAIt; - ++CAIt; - } - - if (Ot->FV != nullptr) { - this->FV = dyn_cast(Ot->FV->getCopy(CS)); - } - this->Parent = Ot; - this->GenericIndex = Ot->GenericIndex; - this->IsZeroWidthArray = Ot->IsZeroWidthArray; - this->BaseType = Ot->BaseType; - this->SrcHasItype = Ot->SrcHasItype; - this->IsVoidPtr = Ot->IsVoidPtr; - this->TypedefLevelInfo = Ot->TypedefLevelInfo; } PointerVariableConstraint::PointerVariableConstraint(DeclaratorDecl *D, @@ -963,22 +947,13 @@ const CVarSet &PVConstraint::getArgumentConstraints() const { return ArgumentConstraints; } -FunctionVariableConstraint::FunctionVariableConstraint( - FunctionVariableConstraint *Ot, Constraints &CS) - : ConstraintVariable(ConstraintVariable::FunctionVariable, Ot->OriginalType, - Ot->getName()) { - this->IsStatic = Ot->IsStatic; - this->FileName = Ot->FileName; - this->Hasbody = Ot->Hasbody; - this->Hasproto = Ot->Hasproto; - this->HasEqArgumentConstraints = Ot->HasEqArgumentConstraints; - this->IsFunctionPtr = Ot->IsFunctionPtr; +FunctionVariableConstraint::FunctionVariableConstraint(FVConstraint *Ot) + : ConstraintVariable(ConstraintVariable::FunctionVariable, Ot->OriginalType, + Ot->getName()), ReturnVar(Ot->ReturnVar), + ParamVars(Ot->ParamVars), FileName(Ot->FileName), Hasproto(Ot->Hasproto), + Hasbody(Ot->Hasbody), IsStatic(Ot->IsStatic), Parent(Ot), + IsFunctionPtr(Ot->IsFunctionPtr), TypeParams(Ot->TypeParams) { this->HasEqArgumentConstraints = Ot->HasEqArgumentConstraints; - this->ReturnVar = FVComponentVariable(&Ot->ReturnVar, CS); - // Make copy of ParameterCVs too. - for (auto &ParmPv : Ot->ParamVars) - this->ParamVars.push_back(FVComponentVariable(&ParmPv, CS)); - this->Parent = Ot; } // This describes a function, either a function pointer or a function @@ -1151,7 +1126,14 @@ bool FunctionVariableConstraint::hasNtArr(const EnvironmentMap &E, } FVConstraint *FunctionVariableConstraint::getCopy(Constraints &CS) { - return new FVConstraint(this, CS); + FunctionVariableConstraint *Copy = new FunctionVariableConstraint(this); + Copy->ReturnVar = FVComponentVariable(&Copy->ReturnVar, CS); + // Make copy of ParameterCVs too. + std::vector FreshParams; + for (auto &ParmPv : Copy->ParamVars) + FreshParams.push_back(FVComponentVariable(&ParmPv, CS)); + Copy->ParamVars = FreshParams; + return Copy; } void PVConstraint::equateArgumentConstraints(ProgramInfo &Info) { @@ -1305,7 +1287,31 @@ bool PointerVariableConstraint::anyChanges(const EnvironmentMap &E) const { } PVConstraint *PointerVariableConstraint::getCopy(Constraints &CS) { - return new PointerVariableConstraint(this, CS); + auto *Copy = new PointerVariableConstraint(this); + + // After the copy construct, the copy Vars vector holds exactly the same + // atoms as this. For this method, we want it to contain fresh VarAtoms. + std::vector FreshVars; + auto VAIt = Copy->Vars.begin(); + auto CAIt = Copy->SrcVars.begin(); + while (VAIt != Copy->Vars.end() && CAIt != Copy->SrcVars.end()) { + if (auto *CA = dyn_cast(*VAIt)) { + FreshVars.push_back(CA); + } else if (auto *VA = dyn_cast(*VAIt)) { + VarAtom *FreshVA = CS.getFreshVar(VA->getName(), VA->getVarKind()); + FreshVars.push_back(FreshVA); + if (!isa(*CAIt)) + CS.addConstraint(CS.createGeq(*CAIt, FreshVA, false)); + } + ++VAIt; + ++CAIt; + } + Copy->Vars = FreshVars; + + if (Copy->FV != nullptr) + Copy->FV = Copy->FV->getCopy(CS); + + return Copy; } const ConstAtom * diff --git a/clang/lib/3C/ProgramInfo.cpp b/clang/lib/3C/ProgramInfo.cpp index 34421bfe664d..3e76b7b35011 100644 --- a/clang/lib/3C/ProgramInfo.cpp +++ b/clang/lib/3C/ProgramInfo.cpp @@ -774,14 +774,8 @@ void ProgramInfo::unifyIfTypedef(const Type *Ty, ASTContext &Context, ProgramInfo::IDAndTranslationUnit ProgramInfo::getExprKey(Expr *E, ASTContext *C) const { - // TODO: Main file name can be shared by multiple translation units if on file - // is compiled multiple times with different defines - std::string Name = - C->getSourceManager() - .getFileEntryForID(C->getSourceManager().getMainFileID()) - ->getName() - .str(); - return std::make_pair(getStmtIdWorkaround(E, *C), Name); + return std::make_pair(getStmtIdWorkaround(E, *C), + TranslationUnitIdxMap.at(C)); } bool ProgramInfo::hasPersistentConstraints(Expr *E, ASTContext *C) const { @@ -1161,28 +1155,28 @@ void ProgramInfo::computePtrLevelStats() { void ProgramInfo::setTypeParamBinding(CallExpr *CE, unsigned int TypeVarIdx, ConstraintVariable *CV, ASTContext *C) { - auto PSL = PersistentSourceLoc::mkPSL(CE, *C); - auto CallMap = TypeParamBindings[PSL]; + auto Key = getExprKey(CE, C); + auto CallMap = TypeParamBindings[Key]; if (CallMap.find(TypeVarIdx) == CallMap.end()) { - TypeParamBindings[PSL][TypeVarIdx] = CV; + TypeParamBindings[Key][TypeVarIdx] = CV; } else { // If this CE/idx is at the same location, it's in a macro, // so mark it as inconsistent. - TypeParamBindings[PSL][TypeVarIdx] = nullptr; + TypeParamBindings[Key][TypeVarIdx] = nullptr; } } bool ProgramInfo::hasTypeParamBindings(CallExpr *CE, ASTContext *C) const { - auto PSL = PersistentSourceLoc::mkPSL(CE, *C); - return TypeParamBindings.find(PSL) != TypeParamBindings.end(); + auto Key = getExprKey(CE, C); + return TypeParamBindings.find(Key) != TypeParamBindings.end(); } const ProgramInfo::CallTypeParamBindingsT & ProgramInfo::getTypeParamBindings(CallExpr *CE, ASTContext *C) const { - auto PSL = PersistentSourceLoc::mkPSL(CE, *C); + auto Key = getExprKey(CE, C); assert("Type parameter bindings could not be found." && - TypeParamBindings.find(PSL) != TypeParamBindings.end()); - return TypeParamBindings.at(PSL); + TypeParamBindings.find(Key) != TypeParamBindings.end()); + return TypeParamBindings.at(Key); } CVarOption ProgramInfo::lookupTypedef(PersistentSourceLoc PSL) { @@ -1210,3 +1204,13 @@ void ProgramInfo::addTypedef(PersistentSourceLoc PSL, bool CanRewriteDef, constrainWildIfMacro(V, TD->getLocation(), &PSL); this->TypedefVars[PSL] = {*V}; } + +void ProgramInfo::registerTranslationUnits( + const std::vector> &ASTs) { + assert(TranslationUnitIdxMap.empty()); + unsigned int Idx = 0; + for (const auto &AST : ASTs) { + TranslationUnitIdxMap[&(AST->getASTContext())] = Idx; + Idx++; + } +} diff --git a/clang/test/3C/multiple_tu.c b/clang/test/3C/multiple_tu.c new file mode 100644 index 000000000000..d7fc64f5145c --- /dev/null +++ b/clang/test/3C/multiple_tu.c @@ -0,0 +1,22 @@ +// RUN: rm -rf %t +// RUN: mkdir %t && cd %t +// RUN: python -c 'import sys, json; json.dump([{"arguments": ["clang", "-c", "%s"], "directory": "%S", "file": "%s"}]*2, sys.stdout)' > compile_commands.json +// RUN: 3c -dump-stats -p %t -base-dir=%S %s | FileCheck -match-full-lines %s +// RUN: python -c 'import sys, json; exit(any(e["Name"].startswith("ImplicitCastExpr") for e in json.load(sys.stdin)["RootCauseStats"]))' < PerWildPtrStats.json + +// The compilation database used for this test includes two entries for this +// file, causing 3C to process it twice. In issue #661, this caused type +// argument instantiation to fail. + +// This issue also made an erroneous entry appear in the root cause output. The +// root cause did not appear in the -warn-root-cause warnings. It was only +// present in the root cause statistics json output. The json is used to +// generate the output for 3c-wrap root_cause, so the error appeared there as well. + +_Itype_for_any(T) void my_free(void *pointer : itype(_Array_ptr) byte_count(0)); + +void foo() { + int *a; + my_free(a); + //CHECK: my_free(a); +} From 4f8511dbd8d4c64b4e1cfb0d9f2ed927bb8d3cbd Mon Sep 17 00:00:00 2001 From: "Matt McCutchen (Correct Computation)" Date: Mon, 9 Aug 2021 14:23:32 -0400 Subject: [PATCH 12/38] Add `override` keyword to overriding methods that lack it. (#672) * Add `override` keyword to overriding methods that lack it. Done via clang-tidy's modernize-use-override check. * Use git-clang-format to fix long lines introduced by the previous commit. --- clang/include/clang/3C/ABounds.h | 6 +- .../clang/3C/ArrayBoundsInferenceConsumer.h | 2 +- clang/include/clang/3C/ConstraintBuilder.h | 4 +- clang/include/clang/3C/Constraints.h | 84 +++++++++++-------- clang/include/clang/3C/IntermediateToolHook.h | 2 +- clang/include/clang/3C/ProgramInfo.h | 9 +- clang/include/clang/3C/ProgramVar.h | 80 ++++++++++-------- clang/include/clang/3C/RewriteUtils.h | 2 +- clang/include/clang/3C/TypeVariableAnalysis.h | 5 +- clang/lib/3C/3C.cpp | 2 +- 10 files changed, 111 insertions(+), 85 deletions(-) diff --git a/clang/include/clang/3C/ABounds.h b/clang/include/clang/3C/ABounds.h index d65874b49d8a..9b6d883b4c7d 100644 --- a/clang/include/clang/3C/ABounds.h +++ b/clang/include/clang/3C/ABounds.h @@ -71,7 +71,7 @@ class CountBound : public ABounds { addBoundsUsedKey(Var); } - virtual ~CountBound() {} + ~CountBound() override {} std::string mkString(AVarBoundsInfo *ABI, clang::Decl *D = nullptr) override; bool areSame(ABounds *O, AVarBoundsInfo *ABI) override; @@ -108,7 +108,7 @@ class ByteBound : public ABounds { addBoundsUsedKey(Var); } - virtual ~ByteBound() {} + ~ByteBound() override {} std::string mkString(AVarBoundsInfo *ABI, clang::Decl *D = nullptr) override; bool areSame(ABounds *O, AVarBoundsInfo *ABI) override; @@ -131,7 +131,7 @@ class RangeBound : public ABounds { addBoundsUsedKey(R); } - virtual ~RangeBound() {} + ~RangeBound() override {} std::string mkString(AVarBoundsInfo *ABI, clang::Decl *D = nullptr) override; bool areSame(ABounds *O, AVarBoundsInfo *ABI) override; diff --git a/clang/include/clang/3C/ArrayBoundsInferenceConsumer.h b/clang/include/clang/3C/ArrayBoundsInferenceConsumer.h index d6101f3f3f47..f5a857f71d20 100644 --- a/clang/include/clang/3C/ArrayBoundsInferenceConsumer.h +++ b/clang/include/clang/3C/ArrayBoundsInferenceConsumer.h @@ -25,7 +25,7 @@ class AllocBasedBoundsInference : public ASTConsumer { public: explicit AllocBasedBoundsInference(ProgramInfo &I, clang::ASTContext *C) : Info(I) {} - virtual void HandleTranslationUnit(ASTContext &Context); + void HandleTranslationUnit(ASTContext &Context) override; private: ProgramInfo &Info; diff --git a/clang/include/clang/3C/ConstraintBuilder.h b/clang/include/clang/3C/ConstraintBuilder.h index 924fc18e2e24..dc667824af89 100644 --- a/clang/include/clang/3C/ConstraintBuilder.h +++ b/clang/include/clang/3C/ConstraintBuilder.h @@ -23,7 +23,7 @@ class VariableAdderConsumer : public clang::ASTConsumer { explicit VariableAdderConsumer(ProgramInfo &I, clang::ASTContext *C) : Info(I) {} - virtual void HandleTranslationUnit(clang::ASTContext &); + void HandleTranslationUnit(clang::ASTContext &) override; private: ProgramInfo &Info; @@ -36,7 +36,7 @@ class ConstraintBuilderConsumer : public clang::ASTConsumer { explicit ConstraintBuilderConsumer(ProgramInfo &I, clang::ASTContext *C) : Info(I) {} - virtual void HandleTranslationUnit(clang::ASTContext &); + void HandleTranslationUnit(clang::ASTContext &) override; private: ProgramInfo &Info; diff --git a/clang/include/clang/3C/Constraints.h b/clang/include/clang/3C/Constraints.h index 278878736ac1..8c1dbc3550f9 100644 --- a/clang/include/clang/3C/Constraints.h +++ b/clang/include/clang/3C/Constraints.h @@ -105,25 +105,27 @@ class VarAtom : public Atom { return ""; } - void print(llvm::raw_ostream &O) const { + void print(llvm::raw_ostream &O) const override { O << varKindToStr(KindV) << Name << "_" << Loc; } - void dump(void) const { print(llvm::errs()); } + void dump(void) const override { print(llvm::errs()); } - void dumpJson(llvm::raw_ostream &O) const { + void dumpJson(llvm::raw_ostream &O) const override { O << "\"" << varKindToStr(KindV) << Name << "_" << Loc << "\""; } - bool operator==(const Atom &Other) const { + bool operator==(const Atom &Other) const override { if (const VarAtom *V = llvm::dyn_cast(&Other)) return V->Loc == Loc; return false; } - bool operator!=(const Atom &Other) const { return !(*this == Other); } + bool operator!=(const Atom &Other) const override { + return !(*this == Other); + } - bool operator<(const Atom &Other) const { + bool operator<(const Atom &Other) const override { if (const VarAtom *V = llvm::dyn_cast(&Other)) return Loc < V->Loc; return false; @@ -159,19 +161,21 @@ class NTArrAtom : public ConstAtom { static bool classof(const Atom *S) { return S->getKind() == A_NTArr; } - void print(llvm::raw_ostream &O) const { O << "NTARR"; } + void print(llvm::raw_ostream &O) const override { O << "NTARR"; } - void dump(void) const { print(llvm::errs()); } + void dump(void) const override { print(llvm::errs()); } - void dumpJson(llvm::raw_ostream &O) const { O << "\"NTARR\""; } + void dumpJson(llvm::raw_ostream &O) const override { O << "\"NTARR\""; } - bool operator==(const Atom &Other) const { + bool operator==(const Atom &Other) const override { return llvm::isa(&Other); } - bool operator!=(const Atom &Other) const { return !(*this == Other); } + bool operator!=(const Atom &Other) const override { + return !(*this == Other); + } - bool operator<(const Atom &Other) const { return !(*this == Other); } + bool operator<(const Atom &Other) const override { return !(*this == Other); } }; // This refers to the constant ARR. @@ -181,19 +185,21 @@ class ArrAtom : public ConstAtom { static bool classof(const Atom *S) { return S->getKind() == A_Arr; } - void print(llvm::raw_ostream &O) const { O << "ARR"; } + void print(llvm::raw_ostream &O) const override { O << "ARR"; } - void dump(void) const { print(llvm::errs()); } + void dump(void) const override { print(llvm::errs()); } - void dumpJson(llvm::raw_ostream &O) const { O << "\"ARR\""; } + void dumpJson(llvm::raw_ostream &O) const override { O << "\"ARR\""; } - bool operator==(const Atom &Other) const { + bool operator==(const Atom &Other) const override { return llvm::isa(&Other); } - bool operator!=(const Atom &Other) const { return !(*this == Other); } + bool operator!=(const Atom &Other) const override { + return !(*this == Other); + } - bool operator<(const Atom &Other) const { + bool operator<(const Atom &Other) const override { return !(llvm::isa(&Other) || *this == Other); } }; @@ -205,19 +211,21 @@ class PtrAtom : public ConstAtom { static bool classof(const Atom *S) { return S->getKind() == A_Ptr; } - void print(llvm::raw_ostream &O) const { O << "PTR"; } + void print(llvm::raw_ostream &O) const override { O << "PTR"; } - void dump(void) const { print(llvm::errs()); } + void dump(void) const override { print(llvm::errs()); } - void dumpJson(llvm::raw_ostream &O) const { O << "\"PTR\""; } + void dumpJson(llvm::raw_ostream &O) const override { O << "\"PTR\""; } - bool operator==(const Atom &Other) const { + bool operator==(const Atom &Other) const override { return llvm::isa(&Other); } - bool operator!=(const Atom &Other) const { return !(*this == Other); } + bool operator!=(const Atom &Other) const override { + return !(*this == Other); + } - bool operator<(const Atom &Other) const { + bool operator<(const Atom &Other) const override { return !(llvm::isa(&Other) || llvm::isa(&Other) || *this == Other); } @@ -230,19 +238,21 @@ class WildAtom : public ConstAtom { static bool classof(const Atom *S) { return S->getKind() == A_Wild; } - void print(llvm::raw_ostream &O) const { O << "WILD"; } + void print(llvm::raw_ostream &O) const override { O << "WILD"; } - void dump(void) const { print(llvm::errs()); } + void dump(void) const override { print(llvm::errs()); } - void dumpJson(llvm::raw_ostream &O) const { O << "\"WILD\""; } + void dumpJson(llvm::raw_ostream &O) const override { O << "\"WILD\""; } - bool operator==(const Atom &Other) const { + bool operator==(const Atom &Other) const override { return llvm::isa(&Other); } - bool operator!=(const Atom &Other) const { return !(*this == Other); } + bool operator!=(const Atom &Other) const override { + return !(*this == Other); + } - bool operator<(const Atom &Other) const { + bool operator<(const Atom &Other) const override { return !(llvm::isa(&Other) || llvm::isa(&Other) || llvm::isa(&Other) || *this == Other); } @@ -304,7 +314,7 @@ class Geq : public Constraint { static bool classof(const Constraint *C) { return C->getKind() == C_Geq; } - void print(llvm::raw_ostream &O) const { + void print(llvm::raw_ostream &O) const override { Lhs->print(O); std::string Kind = IsCheckedConstraint ? " (C)>= " : " (P)>= "; O << Kind; @@ -312,9 +322,9 @@ class Geq : public Constraint { O << ", Reason:" << REASON; } - void dump(void) const { print(llvm::errs()); } + void dump(void) const override { print(llvm::errs()); } - void dumpJson(llvm::raw_ostream &O) const { + void dumpJson(llvm::raw_ostream &O) const override { O << "{\"Geq\":{\"Atom1\":"; Lhs->dumpJson(O); O << ", \"Atom2\":"; @@ -344,16 +354,18 @@ class Geq : public Constraint { bool constraintIsChecked(void) const { return IsCheckedConstraint; } - bool operator==(const Constraint &Other) const { + bool operator==(const Constraint &Other) const override { if (const Geq *E = llvm::dyn_cast(&Other)) return *Lhs == *E->Lhs && *Rhs == *E->Rhs && IsCheckedConstraint == E->IsCheckedConstraint; return false; } - bool operator!=(const Constraint &Other) const { return !(*this == Other); } + bool operator!=(const Constraint &Other) const override { + return !(*this == Other); + } - bool operator<(const Constraint &Other) const { + bool operator<(const Constraint &Other) const override { ConstraintKind K = Other.getKind(); if (K == C_Geq) { const Geq *E = llvm::dyn_cast(&Other); diff --git a/clang/include/clang/3C/IntermediateToolHook.h b/clang/include/clang/3C/IntermediateToolHook.h index d413c0f7f201..44b317694494 100644 --- a/clang/include/clang/3C/IntermediateToolHook.h +++ b/clang/include/clang/3C/IntermediateToolHook.h @@ -25,7 +25,7 @@ class IntermediateToolHook : public ASTConsumer { public: explicit IntermediateToolHook(ProgramInfo &I, clang::ASTContext *C) : Info(I) {} - virtual void HandleTranslationUnit(ASTContext &Context); + void HandleTranslationUnit(ASTContext &Context) override; private: ProgramInfo &Info; diff --git a/clang/include/clang/3C/ProgramInfo.h b/clang/include/clang/3C/ProgramInfo.h index 1ba753406adf..7b240ff2cbe8 100644 --- a/clang/include/clang/3C/ProgramInfo.h +++ b/clang/include/clang/3C/ProgramInfo.h @@ -103,7 +103,7 @@ class ProgramInfo : public ProgramVariableAdder { const VariableMap &getVarMap() const { return Variables; } Constraints &getConstraints() { return CS; } - AVarBoundsInfo &getABoundsInfo() { return ArrBInfo; } + AVarBoundsInfo &getABoundsInfo() override { return ArrBInfo; } PerformanceStats &getPerfStats() { return PerfS; } @@ -135,10 +135,10 @@ class ProgramInfo : public ProgramVariableAdder { CVarOption lookupTypedef(PersistentSourceLoc PSL); - bool seenTypedef(PersistentSourceLoc PSL); + bool seenTypedef(PersistentSourceLoc PSL) override; void addTypedef(PersistentSourceLoc PSL, bool CanRewriteDef, TypedefDecl *TD, - ASTContext &C); + ASTContext &C) override; // Store mapping from ASTContexts to a unique index in the ASTs vector in // the ProgramInfo object. This function must be called prior to any AST @@ -240,7 +240,8 @@ class ProgramInfo : public ProgramVariableAdder { // For each pointer type in the declaration of D, add a variable to the // constraint system for that pointer type. - void addVariable(clang::DeclaratorDecl *D, clang::ASTContext *AstContext); + void addVariable(clang::DeclaratorDecl *D, + clang::ASTContext *AstContext) override; }; #endif diff --git a/clang/include/clang/3C/ProgramVar.h b/clang/include/clang/3C/ProgramVar.h index 0841809dcedd..009af2837d84 100644 --- a/clang/include/clang/3C/ProgramVar.h +++ b/clang/include/clang/3C/ProgramVar.h @@ -72,23 +72,25 @@ class PVSComp { class GlobalScope : public ProgramVarScope { public: GlobalScope() : ProgramVarScope(GlobalScopeKind) {} - virtual ~GlobalScope() {} + ~GlobalScope() override {} static bool classof(const ProgramVarScope *S) { return S->getKind() == GlobalScopeKind; } - bool operator==(const ProgramVarScope &O) const { + bool operator==(const ProgramVarScope &O) const override { return clang::isa(&O); } - bool operator!=(const ProgramVarScope &O) const { return !(*this == O); } + bool operator!=(const ProgramVarScope &O) const override { + return !(*this == O); + } - bool operator<(const ProgramVarScope &O) const { return false; } + bool operator<(const ProgramVarScope &O) const override { return false; } - bool isInInnerScope(const ProgramVarScope &O) const { return false; } + bool isInInnerScope(const ProgramVarScope &O) const override { return false; } - std::string getStr() const { return "Global"; } + std::string getStr() const override { return "Global"; } static GlobalScope *getGlobalScope(); @@ -100,22 +102,24 @@ class StructScope : public ProgramVarScope { public: StructScope(std::string SN) : ProgramVarScope(StructScopeKind), StName(SN) {} - virtual ~StructScope() {} + ~StructScope() override {} static bool classof(const ProgramVarScope *S) { return S->getKind() == StructScopeKind; } - bool operator==(const ProgramVarScope &O) const { + bool operator==(const ProgramVarScope &O) const override { if (auto *SS = clang::dyn_cast(&O)) { return SS->StName == StName; } return false; } - bool operator!=(const ProgramVarScope &O) const { return !(*this == O); } + bool operator!=(const ProgramVarScope &O) const override { + return !(*this == O); + } - bool operator<(const ProgramVarScope &O) const { + bool operator<(const ProgramVarScope &O) const override { if (clang::isa(&O)) { return true; } @@ -130,12 +134,12 @@ class StructScope : public ProgramVarScope { return false; } - bool isInInnerScope(const ProgramVarScope &O) const { + bool isInInnerScope(const ProgramVarScope &O) const override { // only global variables are visible here. return clang::isa(&O); } - std::string getStr() const { return "Struct_" + StName; } + std::string getStr() const override { return "Struct_" + StName; } std::string getSName() const { return this->StName; } @@ -155,22 +159,24 @@ class CtxStructScope : public StructScope { this->Kind = CtxStructScopeKind; } - virtual ~CtxStructScope() {} + ~CtxStructScope() override {} static bool classof(const ProgramVarScope *S) { return S->getKind() == CtxStructScopeKind; } - bool operator==(const ProgramVarScope &O) const { + bool operator==(const ProgramVarScope &O) const override { if (auto *CS = clang::dyn_cast(&O)) { return CS->StName == StName && CS->ASKey == ASKey && CS->IsG == IsG; } return false; } - bool operator!=(const ProgramVarScope &O) const { return !(*this == O); } + bool operator!=(const ProgramVarScope &O) const override { + return !(*this == O); + } - bool operator<(const ProgramVarScope &O) const { + bool operator<(const ProgramVarScope &O) const override { if (clang::isa(&O) || clang::isa(&O)) { return true; } @@ -191,7 +197,7 @@ class CtxStructScope : public StructScope { return false; } - std::string getStr() const { + std::string getStr() const override { return "CtxStruct_" + ASKey + "_" + StName + "_" + std::to_string(IsG); } @@ -210,22 +216,24 @@ class FunctionParamScope : public ProgramVarScope { FunctionParamScope(const std::string &FN, bool IsSt) : ProgramVarScope(FunctionParamScopeKind), FName(FN), IsStatic(IsSt) {} - virtual ~FunctionParamScope() {} + ~FunctionParamScope() override {} static bool classof(const ProgramVarScope *S) { return S->getKind() == FunctionParamScopeKind; } - bool operator==(const ProgramVarScope &O) const { + bool operator==(const ProgramVarScope &O) const override { if (auto *FPS = clang::dyn_cast(&O)) { return (FPS->FName == FName && FPS->IsStatic == IsStatic); } return false; } - bool operator!=(const ProgramVarScope &O) const { return !(*this == O); } + bool operator!=(const ProgramVarScope &O) const override { + return !(*this == O); + } - bool operator<(const ProgramVarScope &O) const { + bool operator<(const ProgramVarScope &O) const override { if (clang::isa(&O) || clang::isa(&O)) { return true; } @@ -243,12 +251,12 @@ class FunctionParamScope : public ProgramVarScope { return false; } - bool isInInnerScope(const ProgramVarScope &O) const { + bool isInInnerScope(const ProgramVarScope &O) const override { // only global variables are visible here. return clang::isa(&O); } - std::string getStr() const { return "FuncParm_" + FName; } + std::string getStr() const override { return "FuncParm_" + FName; } const llvm::StringRef getFName() const { return this->FName; } @@ -287,13 +295,13 @@ class CtxFunctionArgScope : public FunctionParamScope { this->Kind = CtxFunctionArgScopeKind; } - virtual ~CtxFunctionArgScope() {} + ~CtxFunctionArgScope() override {} static bool classof(const ProgramVarScope *S) { return S->getKind() == CtxFunctionArgScopeKind; } - bool operator==(const ProgramVarScope &O) const { + bool operator==(const ProgramVarScope &O) const override { if (auto *FPS = clang::dyn_cast(&O)) { return (FPS->FName == FName && FPS->IsStatic == IsStatic && !(FPS->PSL < PSL || PSL < FPS->PSL)); @@ -301,9 +309,11 @@ class CtxFunctionArgScope : public FunctionParamScope { return false; } - bool operator!=(const ProgramVarScope &O) const { return !(*this == O); } + bool operator!=(const ProgramVarScope &O) const override { + return !(*this == O); + } - bool operator<(const ProgramVarScope &O) const { + bool operator<(const ProgramVarScope &O) const override { if (clang::isa(&O) || clang::isa(&O) || clang::isa(&O)) { return true; @@ -325,7 +335,7 @@ class CtxFunctionArgScope : public FunctionParamScope { return false; } - std::string getStr() const { return FName + "_Ctx_" + CtxIDStr; } + std::string getStr() const override { return FName + "_Ctx_" + CtxIDStr; } static const CtxFunctionArgScope * getCtxFunctionParamScope(const FunctionParamScope *FPS, @@ -342,13 +352,13 @@ class FunctionScope : public ProgramVarScope { FunctionScope(std::string FN, bool IsSt) : ProgramVarScope(FunctionScopeKind), FName(FN), IsStatic(IsSt) {} - virtual ~FunctionScope() {} + ~FunctionScope() override {} static bool classof(const ProgramVarScope *S) { return S->getKind() == FunctionScopeKind; } - bool operator==(const ProgramVarScope &O) const { + bool operator==(const ProgramVarScope &O) const override { if (auto *FS = clang::dyn_cast(&O)) { return (FS->FName == FName && FS->IsStatic == IsStatic); } @@ -358,9 +368,11 @@ class FunctionScope : public ProgramVarScope { return false; } - bool operator!=(const ProgramVarScope &O) const { return !(*this == O); } + bool operator!=(const ProgramVarScope &O) const override { + return !(*this == O); + } - bool operator<(const ProgramVarScope &O) const { + bool operator<(const ProgramVarScope &O) const override { if (clang::isa(&O) || clang::isa(&O) || clang::isa(&O) || clang::isa(&O)) { return true; @@ -378,9 +390,9 @@ class FunctionScope : public ProgramVarScope { return false; } - bool isInInnerScope(const ProgramVarScope &O) const; + bool isInInnerScope(const ProgramVarScope &O) const override; - std::string getStr() const { return "InFunc_" + FName; } + std::string getStr() const override { return "InFunc_" + FName; } static const FunctionScope *getFunctionScope(std::string FnName, bool IsSt); diff --git a/clang/include/clang/3C/RewriteUtils.h b/clang/include/clang/3C/RewriteUtils.h index 480596ed85fe..5fd4dc8de7c6 100644 --- a/clang/include/clang/3C/RewriteUtils.h +++ b/clang/include/clang/3C/RewriteUtils.h @@ -147,7 +147,7 @@ class RewriteConsumer : public ASTConsumer { public: explicit RewriteConsumer(ProgramInfo &I) : Info(I) {} - virtual void HandleTranslationUnit(ASTContext &Context); + void HandleTranslationUnit(ASTContext &Context) override; private: ProgramInfo &Info; diff --git a/clang/include/clang/3C/TypeVariableAnalysis.h b/clang/include/clang/3C/TypeVariableAnalysis.h index 093f3e0b96f5..5810d249ace5 100644 --- a/clang/include/clang/3C/TypeVariableAnalysis.h +++ b/clang/include/clang/3C/TypeVariableAnalysis.h @@ -90,8 +90,9 @@ class TypeVarVisitor : public RecursiveASTVisitor, bool VisitCastExpr(CastExpr *CE); bool VisitCallExpr(CallExpr *CE); - void getConsistentTypeParams(CallExpr *CE, std::set &Types); - void setProgramInfoTypeVars(); + void getConsistentTypeParams(CallExpr *CE, + std::set &Types) override; + void setProgramInfoTypeVars() override; private: ASTContext *Context; diff --git a/clang/lib/3C/3C.cpp b/clang/lib/3C/3C.cpp index 4b019965f661..702accddee75 100644 --- a/clang/lib/3C/3C.cpp +++ b/clang/lib/3C/3C.cpp @@ -160,7 +160,7 @@ class _3CDiagnosticConsumer : public DiagnosticConsumer { return UnderlyingConsumer->getNumErrors() == 0; } - ~_3CDiagnosticConsumer() { + ~_3CDiagnosticConsumer() override { // We considered asserting that the state is S_Done here, but if // ASTUnit::LoadFromCompilerInvocation fails and returns null, the // _3CDiagnosticConsumer may be destructed without reaching S_Done. However, From 2f5bf2603934bbccfff0140136085d1ae52fda09 Mon Sep 17 00:00:00 2001 From: "Matt McCutchen (Correct Computation)" Date: Mon, 9 Aug 2021 16:21:17 -0400 Subject: [PATCH 13/38] Don't print the main file to stdout multiple times in different translation units. (#675) --- clang/include/clang/3C/RewriteUtils.h | 6 ++++++ clang/lib/3C/RewriteUtils.cpp | 20 +++++++++++++------- clang/test/3C/multiple_tu.c | 12 ++++++++++++ 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/clang/include/clang/3C/RewriteUtils.h b/clang/include/clang/3C/RewriteUtils.h index 5fd4dc8de7c6..0f312418bf5e 100644 --- a/clang/include/clang/3C/RewriteUtils.h +++ b/clang/include/clang/3C/RewriteUtils.h @@ -159,6 +159,12 @@ class RewriteConsumer : public ASTConsumer { static std::set EmittedDiagnostics; void emitRootCauseDiagnostics(ASTContext &Context); + + // Hack to avoid printing the main file to stdout multiple times in the edge + // case of a compilation database containing multiple translation units for + // the main file + // (https://github.com/correctcomputation/checkedc-clang/issues/374#issuecomment-893612654). + bool StdoutModeEmittedMainFile = false; }; bool canRewrite(Rewriter &R, const SourceRange &SR); diff --git a/clang/lib/3C/RewriteUtils.cpp b/clang/lib/3C/RewriteUtils.cpp index b9eb8169b8e2..31656e9c80da 100644 --- a/clang/lib/3C/RewriteUtils.cpp +++ b/clang/lib/3C/RewriteUtils.cpp @@ -120,12 +120,11 @@ void rewriteSourceRange(Rewriter &R, const CharSourceRange &Range, } } -static void emit(Rewriter &R, ASTContext &C) { +static void emit(Rewriter &R, ASTContext &C, bool &StdoutModeEmittedMainFile) { if (Verbose) errs() << "Writing files out\n"; bool StdoutMode = (OutputPostfix == "-" && OutputDir.empty()); - bool StdoutModeSawMainFile = false; SourceManager &SM = C.getSourceManager(); // Iterate over each modified rewrite buffer. for (auto Buffer = R.buffer_begin(); Buffer != R.buffer_end(); ++Buffer) { @@ -226,9 +225,15 @@ static void emit(Rewriter &R, ASTContext &C) { if (StdoutMode) { if (Buffer->first == SM.getMainFileID()) { - // This is the new version of the main file. Print it to stdout. - Buffer->second.write(outs()); - StdoutModeSawMainFile = true; + // This is the new version of the main file. Print it to stdout, + // except in the edge case where we have a compilation database with + // multiple translation units with the same main file and we already + // emitted a copy of the main file for a previous translation unit + // (https://github.com/correctcomputation/checkedc-clang/issues/374#issuecomment-893612654). + if (!StdoutModeEmittedMainFile) { + Buffer->second.write(outs()); + StdoutModeEmittedMainFile = true; + } } else { unsigned ID = DE.getCustomDiagID( UnwritableChangeDiagnosticLevel, @@ -300,9 +305,10 @@ static void emit(Rewriter &R, ASTContext &C) { } } - if (StdoutMode && !StdoutModeSawMainFile) { + if (StdoutMode && !StdoutModeEmittedMainFile) { // The main file is unchanged. Write out its original content. outs() << SM.getBufferOrFake(SM.getMainFileID()).getBuffer(); + StdoutModeEmittedMainFile = true; } } @@ -632,7 +638,7 @@ void RewriteConsumer::HandleTranslationUnit(ASTContext &Context) { } // Output files. - emit(R, Context); + emit(R, Context, StdoutModeEmittedMainFile); Info.getPerfStats().endRewritingTime(); diff --git a/clang/test/3C/multiple_tu.c b/clang/test/3C/multiple_tu.c index d7fc64f5145c..f56cb54e6945 100644 --- a/clang/test/3C/multiple_tu.c +++ b/clang/test/3C/multiple_tu.c @@ -13,10 +13,22 @@ // present in the root cause statistics json output. The json is used to // generate the output for 3c-wrap root_cause, so the error appeared there as well. +// Furthermore, in stdout mode, if the main file was processed twice, the +// rewritten version was printed to stdout twice +// (https://github.com/correctcomputation/checkedc-clang/issues/374#issuecomment-893612654). +// Check that this doesn't happen any more. + _Itype_for_any(T) void my_free(void *pointer : itype(_Array_ptr) byte_count(0)); void foo() { + //CHECK: {{^}}void foo() { int *a; my_free(a); //CHECK: my_free(a); } + +// Make sure the file does not get printed to stdout a second time. Since +// -match-full-lines does not apply to CHECK-NOT, the {{^}} is needed to anchor +// the match to the beginning of the line and prevent the CHECK-NOT from +// matching itself. +//CHECK-NOT: {{^}}void foo() { From 513b4c464cc19f5773e485477a013eb2fb512210 Mon Sep 17 00:00:00 2001 From: "Matt McCutchen (Correct Computation)" Date: Tue, 10 Aug 2021 15:41:07 -0400 Subject: [PATCH 14/38] Reduce boilerplate in 3C diagnostic reporting code. (#514) - Define and use `<<` shortcut for DiagnosticBuilder::AddTaggedVal. - Migrate all other existing DiagnosticBuilder::Add* calls to `<<` (originally Kyle's work). - Define and use reportCustomDiagnostic wrapper for DiagnosticsEngine::{getCustomDiagID + Report}. Co-authored-by: Kyle Headley --- clang/include/clang/3C/Utils.h | 28 ++++++ clang/lib/3C/CastPlacement.cpp | 23 +++-- clang/lib/3C/CheckedRegions.cpp | 9 +- clang/lib/3C/ConstraintBuilder.cpp | 14 ++- clang/lib/3C/ProgramInfo.cpp | 15 ++-- clang/lib/3C/RewriteUtils.cpp | 133 +++++++++++++---------------- 6 files changed, 112 insertions(+), 110 deletions(-) diff --git a/clang/include/clang/3C/Utils.h b/clang/include/clang/3C/Utils.h index 4c2fb0f752bc..441833b9bd4c 100644 --- a/clang/include/clang/3C/Utils.h +++ b/clang/include/clang/3C/Utils.h @@ -13,6 +13,7 @@ #include "clang/3C/PersistentSourceLoc.h" #include "clang/AST/Type.h" +#include "clang/Basic/Diagnostic.h" #include "clang/Basic/SourceLocation.h" #include "llvm/Support/Casting.h" #include "llvm/Support/CommandLine.h" @@ -225,4 +226,31 @@ void getPrintfStringArgIndices(const clang::CallExpr *CE, int64_t getStmtIdWorkaround(const clang::Stmt *St, const clang::ASTContext &Context); +// Shortcut for the getCustomDiagID + Report sequence to report a custom +// diagnostic as we currently do in 3C. +// +// Unlike DiagnosticEngine::Report, to make it harder to forget to provide a +// source location when we intend to, we don't provide a version that doesn't +// take a source location; instead, the caller should just pass +// SourceLocation(). + +template +inline clang::DiagnosticBuilder reportCustomDiagnostic( + clang::DiagnosticsEngine &DE, + clang::DiagnosticsEngine::Level Level, + const char (&FormatString)[N], + clang::SourceLocation Loc) { + return DE.Report(Loc, DE.getCustomDiagID(Level, FormatString)); +} + +// For whatever reason, Clang provides << equivalents for many other +// DiagnosticBuilder::Add* methods but not this one, and we want it in a few +// places. +inline const clang::DiagnosticBuilder &operator<<( + const clang::DiagnosticBuilder &DB, clang::NamedDecl *ND) { + DB.AddTaggedVal(reinterpret_cast(ND), + clang::DiagnosticsEngine::ArgumentKind::ak_nameddecl); + return DB; +} + #endif diff --git a/clang/lib/3C/CastPlacement.cpp b/clang/lib/3C/CastPlacement.cpp index d88db50272b1..ad83833b7e9f 100644 --- a/clang/lib/3C/CastPlacement.cpp +++ b/clang/lib/3C/CastPlacement.cpp @@ -215,12 +215,12 @@ void CastPlacementVisitor::surroundByCast(ConstraintVariable *Dst, // unwritable files in common use cases. Until they are fixed, report a // warning rather than letting the main "unwritable change" error trigger // later. - clang::DiagnosticsEngine &DE = Writer.getSourceMgr().getDiagnostics(); - unsigned ErrorId = DE.getCustomDiagID( + reportCustomDiagnostic( + Writer.getSourceMgr().getDiagnostics(), DiagnosticsEngine::Warning, "3C internal error: tried to insert a cast into an unwritable file " - "(https://github.com/correctcomputation/checkedc-clang/issues/454)"); - DE.Report(E->getBeginLoc(), ErrorId); + "(https://github.com/correctcomputation/checkedc-clang/issues/454)", + E->getBeginLoc()); return; } @@ -277,14 +277,13 @@ void CastPlacementVisitor::reportCastInsertionFailure( // FIXME: This is a warning rather than an error so that a new benchmark // failure is not introduced in Lua. // github.com/correctcomputation/checkedc-clang/issues/439 - clang::DiagnosticsEngine &DE = Context->getDiagnostics(); - unsigned ErrorId = DE.getCustomDiagID( - DiagnosticsEngine::Warning, "Unable to surround expression with cast.\n" - "Intended cast: \"%0\""); - auto ErrorBuilder = DE.Report(E->getExprLoc(), ErrorId); - ErrorBuilder.AddSourceRange( - Context->getSourceManager().getExpansionRange(E->getSourceRange())); - ErrorBuilder.AddString(CastStr); + reportCustomDiagnostic(Context->getDiagnostics(), + DiagnosticsEngine::Warning, + "Unable to surround expression with cast.\n" + "Intended cast: \"%0\"", + E->getExprLoc()) + << Context->getSourceManager().getExpansionRange(E->getSourceRange()) + << CastStr; } void CastPlacementVisitor::updateRewriteStats(CastNeeded CastKind) { diff --git a/clang/lib/3C/CheckedRegions.cpp b/clang/lib/3C/CheckedRegions.cpp index fef651e4e35a..80beb595c9e2 100644 --- a/clang/lib/3C/CheckedRegions.cpp +++ b/clang/lib/3C/CheckedRegions.cpp @@ -432,10 +432,6 @@ void CheckedRegionFinder::markChecked(CompoundStmt *S, int Localwild) { void CheckedRegionFinder::emitCauseDiagnostic(PersistentSourceLoc *PSL) { if (Emitted.find(PSL) == Emitted.end()) { - clang::DiagnosticsEngine &DE = Context->getDiagnostics(); - unsigned ID = - DE.getCustomDiagID(DiagnosticsEngine::Warning, - "Root cause of unchecked region: Variadic Call"); SourceManager &SM = Context->getSourceManager(); llvm::ErrorOr File = SM.getFileManager().getFile(PSL->getFileName()); @@ -444,7 +440,10 @@ void CheckedRegionFinder::emitCauseDiagnostic(PersistentSourceLoc *PSL) { SourceLocation SL = SM.translateFileLineCol(*File, PSL->getLineNo(), PSL->getColSNo()); if (SL.isValid()) - DE.Report(SL, ID); + reportCustomDiagnostic(Context->getDiagnostics(), + DiagnosticsEngine::Warning, + "Root cause of unchecked region: Variadic Call", + SL); Emitted.insert(PSL); } } diff --git a/clang/lib/3C/ConstraintBuilder.cpp b/clang/lib/3C/ConstraintBuilder.cpp index d2425f8c1dae..4fbc2e803ac5 100644 --- a/clang/lib/3C/ConstraintBuilder.cpp +++ b/clang/lib/3C/ConstraintBuilder.cpp @@ -99,9 +99,8 @@ class InlineStructDetector { CVarOption CV = Info.getVariable(VD, Context); CB.constraintCVarToWild(CV, "Inline struct encountered."); } else { - clang::DiagnosticsEngine &DE = Context->getDiagnostics(); - unsigned InlineStructWarning = - DE.getCustomDiagID(DiagnosticsEngine::Warning, + reportCustomDiagnostic(Context->getDiagnostics(), + DiagnosticsEngine::Warning, "\n Rewriting failed" "for %q0 because an inline " "or anonymous struct instance " @@ -110,12 +109,9 @@ class InlineStructDetector { "definition inside the _Ptr " "annotation.\n " "EX. struct {int *a; int *b;} x; " - "_Ptr b;}>;"); - const auto Pointer = reinterpret_cast(VD); - const auto Kind = - clang::DiagnosticsEngine::ArgumentKind::ak_nameddecl; - auto DiagBuilder = DE.Report(VD->getLocation(), InlineStructWarning); - DiagBuilder.AddTaggedVal(Pointer, Kind); + "_Ptr b;}>;", + VD->getLocation()) + << VD; } } } diff --git a/clang/lib/3C/ProgramInfo.cpp b/clang/lib/3C/ProgramInfo.cpp index 3e76b7b35011..232cf880a613 100644 --- a/clang/lib/3C/ProgramInfo.cpp +++ b/clang/lib/3C/ProgramInfo.cpp @@ -567,16 +567,11 @@ ProgramInfo::insertNewFVConstraint(FunctionDecl *FD, FVConstraint *NewC, return (*Map)[FuncName]; // Error reporting - { // block to force DiagBuilder destructor and emit message - clang::DiagnosticsEngine &DE = C->getDiagnostics(); - unsigned FailID = DE.getCustomDiagID(DiagnosticsEngine::Fatal, - "merging failed for %q0 due to %1"); - const auto Pointer = reinterpret_cast(FD); - const auto Kind = clang::DiagnosticsEngine::ArgumentKind::ak_nameddecl; - auto DiagBuilder = DE.Report(FD->getLocation(), FailID); - DiagBuilder.AddTaggedVal(Pointer, Kind); - DiagBuilder.AddString(ReasonFailed); - } + reportCustomDiagnostic(C->getDiagnostics(), + DiagnosticsEngine::Fatal, + "merging failed for %q0 due to %1", + FD->getLocation()) + << FD << ReasonFailed; // A failed merge will provide poor data, but the diagnostic error report // will cause the program to terminate after the variable adder step. return (*Map)[FuncName]; diff --git a/clang/lib/3C/RewriteUtils.cpp b/clang/lib/3C/RewriteUtils.cpp index 31656e9c80da..5f1128957b33 100644 --- a/clang/lib/3C/RewriteUtils.cpp +++ b/clang/lib/3C/RewriteUtils.cpp @@ -97,25 +97,21 @@ void rewriteSourceRange(Rewriter &R, const CharSourceRange &Range, if (!RewriteSuccess) { clang::DiagnosticsEngine &DE = R.getSourceMgr().getDiagnostics(); bool ReportError = ErrFail && !AllowRewriteFailures; - { - // Put this in a block because Clang only allows one DiagnosticBuilder to - // exist at a time. - unsigned ErrorId = DE.getCustomDiagID( - ReportError ? DiagnosticsEngine::Error : DiagnosticsEngine::Warning, - "Unable to rewrite converted source range. Intended rewriting: " - "\"%0\""); - auto ErrorBuilder = DE.Report(Range.getBegin(), ErrorId); - ErrorBuilder.AddSourceRange(R.getSourceMgr().getExpansionRange(Range)); - ErrorBuilder.AddString(NewText); - } + reportCustomDiagnostic( + DE, + ReportError ? DiagnosticsEngine::Error : DiagnosticsEngine::Warning, + "Unable to rewrite converted source range. Intended rewriting: " + "\"%0\"", + Range.getBegin()) + << R.getSourceMgr().getExpansionRange(Range) << NewText; if (ReportError) { - unsigned NoteId = DE.getCustomDiagID( - DiagnosticsEngine::Note, + reportCustomDiagnostic( + DE, DiagnosticsEngine::Note, "you can use the -allow-rewrite-failures option to temporarily " - "downgrade this error to a warning"); - // If we pass the location here, the macro call stack gets dumped again, - // which looks silly. - DE.Report(NoteId); + "downgrade this error to a warning", + // If we pass the location here, the macro call stack gets dumped + // again, which looks silly. + SourceLocation()); } } } @@ -130,6 +126,9 @@ static void emit(Rewriter &R, ASTContext &C, bool &StdoutModeEmittedMainFile) { for (auto Buffer = R.buffer_begin(); Buffer != R.buffer_end(); ++Buffer) { if (const FileEntry *FE = SM.getFileEntryForID(Buffer->first)) { assert(FE->isValid()); + // Used for diagnostics related to the file. + SourceLocation BeginningOfFileSourceLoc = + SM.translateFileLineCol(FE, 1, 1); DiagnosticsEngine &DE = C.getDiagnostics(); DiagnosticsEngine::Level UnwritableChangeDiagnosticLevel = @@ -139,18 +138,18 @@ static void emit(Rewriter &R, ASTContext &C, bool &StdoutModeEmittedMainFile) { // With -dump-unwritable-changes and not -allow-unwritable-changes, we // want the -allow-unwritable-changes note before the dump. if (!DumpUnwritableChanges) { - unsigned DumpNoteId = DE.getCustomDiagID( - DiagnosticsEngine::Note, + reportCustomDiagnostic( + DE, DiagnosticsEngine::Note, "use the -dump-unwritable-changes option to see the new version " - "of the file"); - DE.Report(DumpNoteId); + "of the file", + SourceLocation()); } if (!AllowUnwritableChanges) { - unsigned AllowNoteId = DE.getCustomDiagID( - DiagnosticsEngine::Note, + reportCustomDiagnostic( + DE, DiagnosticsEngine::Note, "you can use the -allow-unwritable-changes option to temporarily " - "downgrade this error to a warning"); - DE.Report(AllowNoteId); + "downgrade this error to a warning", + SourceLocation()); } if (DumpUnwritableChanges) { errs() << "=== Beginning of new version of " << FE->getName() @@ -173,52 +172,41 @@ static void emit(Rewriter &R, ASTContext &C, bool &StdoutModeEmittedMainFile) { std::string FeAbsS = ""; std::error_code EC = tryGetCanonicalFilePath(ToConv, FeAbsS); if (EC) { - unsigned ErrorId = DE.getCustomDiagID( - UnwritableChangeDiagnosticLevel, + reportCustomDiagnostic( + DE, UnwritableChangeDiagnosticLevel, "3C internal error: not writing the new version of this file due " - "to failure to re-canonicalize the file path provided by Clang"); - DE.Report(SM.translateFileLineCol(FE, 1, 1), ErrorId); - { - // Put this in a block because Clang only allows one DiagnosticBuilder - // to exist at a time and the call to PrintExtraUnwritableChangeInfo - // below may create more DiagnosticBuilders. - unsigned NoteId = - DE.getCustomDiagID(DiagnosticsEngine::Note, - "file path from Clang was %0; error was: %1"); - auto ErrorBuilder = DE.Report(NoteId); - ErrorBuilder.AddString(ToConv); - ErrorBuilder.AddString(EC.message()); - } + "to failure to re-canonicalize the file path provided by Clang", + BeginningOfFileSourceLoc); + reportCustomDiagnostic( + DE, DiagnosticsEngine::Note, + "file path from Clang was %0; error was: %1", + SourceLocation()) + << ToConv << EC.message(); PrintExtraUnwritableChangeInfo(); continue; } if (FeAbsS != ToConv) { - unsigned ErrorId = DE.getCustomDiagID( - UnwritableChangeDiagnosticLevel, + reportCustomDiagnostic( + DE, UnwritableChangeDiagnosticLevel, "3C internal error: not writing the new version of this file " - "because the file path provided by Clang was not canonical"); - DE.Report(SM.translateFileLineCol(FE, 1, 1), ErrorId); - { - // Ditto re the block. - unsigned NoteId = DE.getCustomDiagID( - DiagnosticsEngine::Note, "file path from Clang was %0; " - "re-canonicalized file path is %1"); - auto ErrorBuilder = DE.Report(NoteId); - ErrorBuilder.AddString(ToConv); - ErrorBuilder.AddString(FeAbsS); - } + "because the file path provided by Clang was not canonical", + BeginningOfFileSourceLoc); + reportCustomDiagnostic( + DE, DiagnosticsEngine::Note, + "file path from Clang was %0; re-canonicalized file path is %1", + SourceLocation()) + << ToConv << FeAbsS; PrintExtraUnwritableChangeInfo(); continue; } if (!canWrite(FeAbsS)) { - unsigned ID = - DE.getCustomDiagID(UnwritableChangeDiagnosticLevel, + reportCustomDiagnostic(DE, UnwritableChangeDiagnosticLevel, "3C internal error: 3C generated changes to " "this file even though it is not allowed to " "write to the file " "(https://github.com/correctcomputation/" - "checkedc-clang/issues/387)"); - DE.Report(SM.translateFileLineCol(FE, 1, 1), ID); + "checkedc-clang/issues/387)", + BeginningOfFileSourceLoc); PrintExtraUnwritableChangeInfo(); continue; } @@ -235,12 +223,12 @@ static void emit(Rewriter &R, ASTContext &C, bool &StdoutModeEmittedMainFile) { StdoutModeEmittedMainFile = true; } } else { - unsigned ID = DE.getCustomDiagID( - UnwritableChangeDiagnosticLevel, + reportCustomDiagnostic( + DE, UnwritableChangeDiagnosticLevel, "3C generated changes to this file, which is under the base dir " "but is not the main file and thus cannot be written in stdout " - "mode"); - DE.Report(SM.translateFileLineCol(FE, 1, 1), ID); + "mode", + BeginningOfFileSourceLoc); PrintExtraUnwritableChangeInfo(); } continue; @@ -278,11 +266,11 @@ static void emit(Rewriter &R, ASTContext &C, bool &StdoutModeEmittedMainFile) { NFile = std::string(Tmp.str()); EC = llvm::sys::fs::create_directories(sys::path::parent_path(NFile)); if (EC) { - unsigned ID = DE.getCustomDiagID( - DiagnosticsEngine::Error, - "failed to create parent directory of output file \"%0\""); - auto DiagBuilder = DE.Report(SM.translateFileLineCol(FE, 1, 1), ID); - DiagBuilder.AddString(NFile); + reportCustomDiagnostic( + DE, DiagnosticsEngine::Error, + "failed to create parent directory of output file \"%0\"", + BeginningOfFileSourceLoc) + << NFile; continue; } } @@ -294,10 +282,10 @@ static void emit(Rewriter &R, ASTContext &C, bool &StdoutModeEmittedMainFile) { errs() << "writing out " << NFile << "\n"; Buffer->second.write(Out); } else { - unsigned ID = DE.getCustomDiagID(DiagnosticsEngine::Error, - "failed to write output file \"%0\""); - auto DiagBuilder = DE.Report(SM.translateFileLineCol(FE, 1, 1), ID); - DiagBuilder.AddString(NFile); + reportCustomDiagnostic(DE, DiagnosticsEngine::Error, + "failed to write output file \"%0\"", + BeginningOfFileSourceLoc) + << NFile; // This is awkward. What to do? Since we're iterating, we could have // created other files successfully. Do we go back and erase them? Is // that surprising? For now, let's just keep going. @@ -585,10 +573,7 @@ void RewriteConsumer::emitRootCauseDiagnostics(ASTContext &Context) { // SL is invalid when the File is not in the current translation unit. if (SL.isValid()) { EmittedDiagnostics.insert(PSL); - auto DiagBuilder = DE.Report(SL, ID); - DiagBuilder.AddTaggedVal(PtrCount, - DiagnosticsEngine::ArgumentKind::ak_uint); - DiagBuilder.AddString(WReason.second.getWildPtrReason()); + DE.Report(SL, ID) << PtrCount << WReason.second.getWildPtrReason(); } } } From 0dfa3c58e1868e81b7c0794b416dd68512b3e2c4 Mon Sep 17 00:00:00 2001 From: John Kastner Date: Tue, 10 Aug 2021 15:55:30 -0400 Subject: [PATCH 15/38] Make the wild constraint variable used for unsafe mallocs generic (#677) This fixes issue correctcomputation/checkedc-clang#673 by making the wild constraint variable used for unsafe allocator calls a generic variable. This allows it to be used in constraints with other variables of arbitrary pointer depth without triggering incompatible pointer type constraints. --- clang/lib/3C/ConstraintVariables.cpp | 7 +++++++ clang/test/3C/root_cause.c | 1 + 2 files changed, 8 insertions(+) diff --git a/clang/lib/3C/ConstraintVariables.cpp b/clang/lib/3C/ConstraintVariables.cpp index 1e208ab034ee..89aa03eb5a9e 100644 --- a/clang/lib/3C/ConstraintVariables.cpp +++ b/clang/lib/3C/ConstraintVariables.cpp @@ -54,6 +54,13 @@ PointerVariableConstraint *PointerVariableConstraint::getWildPVConstraint( WildPVC->Vars.push_back(VA); WildPVC->SrcVars.push_back(CS.getWild()); + // Mark this constraint variable as generic. This is done because we do not + // know the type of the constraint, and therefore we also don't know the + // number of atoms it needs to have. Fortunately, it's already WILD, so any + // constraint made with it will force the other constraint to WILD, safely + // handling assignment between incompatible pointer depths. + WildPVC->GenericIndex = 0; + return WildPVC; } diff --git a/clang/test/3C/root_cause.c b/clang/test/3C/root_cause.c index 0408b59d7830..f1e1a66561b8 100644 --- a/clang/test/3C/root_cause.c +++ b/clang/test/3C/root_cause.c @@ -34,6 +34,7 @@ void test1() { c++; // expected-warning {{1 unchecked pointer: Pointer arithmetic performed on a function pointer}} int *d = malloc(1); // expected-warning {{1 unchecked pointer: Unsafe call to allocator function}} + int **e = malloc(1); // expected-warning {{1 unchecked pointer: Unsafe call to allocator function}} } // expected-warning@+1 {{1 unchecked pointer: External global variable glob has no definition}} From e1897b57995cf1415736c07a03ba2225cb2b6f34 Mon Sep 17 00:00:00 2001 From: Aaron Eline Date: Thu, 12 Aug 2021 12:51:36 -0400 Subject: [PATCH 16/38] Ensure "non-writable" is the prioritized root cause of wildness (#665) * Test cases * Added method for querying unwritability * First draft. Need more tests to ensure works * Better testing * Documentation * Removed Accidental Formatting Change * Broader coverage on isUnwritable * WIP on improving tests * Add direct link to issue * WIP, found one bug. * proper constraints on typedefs * Cleaner fix * Cosmetic change * Style Cleanup --- clang/include/clang/3C/Constraints.h | 7 ++- clang/lib/3C/Constraints.cpp | 15 ++++++- clang/lib/3C/ProgramInfo.cpp | 18 ++++---- .../3C/base_subdir/unwritable_rootcauses.c | 4 ++ clang/test/3C/canwrite_constraints.h | 28 ++++++------ clang/test/3C/root_cause.c | 44 ++++++++++++++++--- 6 files changed, 84 insertions(+), 32 deletions(-) create mode 100644 clang/test/3C/base_subdir/unwritable_rootcauses.c diff --git a/clang/include/clang/3C/Constraints.h b/clang/include/clang/3C/Constraints.h index 8c1dbc3550f9..cca53f9729f0 100644 --- a/clang/include/clang/3C/Constraints.h +++ b/clang/include/clang/3C/Constraints.h @@ -32,6 +32,7 @@ class ConstraintsGraph; #define DEFAULT_REASON "UNKNOWN_REASON" #define POINTER_IS_ARRAY_REASON "Pointer is array but alltypes is disabled." +#define UNWRITABLE_REASON "Source code in non-writable file." template struct PComp { bool operator()(const T Lhs, const T Rhs) const { return *Lhs < *Rhs; } @@ -287,9 +288,13 @@ class Constraint { virtual bool operator==(const Constraint &Other) const = 0; virtual bool operator!=(const Constraint &Other) const = 0; virtual bool operator<(const Constraint &Other) const = 0; - virtual std::string getReason() { return REASON; } + virtual std::string getReason() const { return REASON; } virtual void setReason(const std::string &Rsn) { REASON = Rsn; } + bool isUnwritable(void) const { + return getReason() == UNWRITABLE_REASON; + } + const PersistentSourceLoc &getLocation() const { return PL; } }; diff --git a/clang/lib/3C/Constraints.cpp b/clang/lib/3C/Constraints.cpp index 9fdda11c83d1..83285bdd1d47 100644 --- a/clang/lib/3C/Constraints.cpp +++ b/clang/lib/3C/Constraints.cpp @@ -96,8 +96,10 @@ void Constraints::editConstraintHook(Constraint *C) { bool Constraints::addConstraint(Constraint *C) { editConstraintHook(C); + auto Search = TheConstraints.find(C); + // Check if C is already in the set of constraints. - if (TheConstraints.find(C) == TheConstraints.end()) { + if (Search == TheConstraints.end()) { TheConstraints.insert(C); if (Geq *G = dyn_cast(C)) { @@ -121,6 +123,17 @@ bool Constraints::addConstraint(Constraint *C) { return true; } + // If the constraint being added is due to unwritability, + // propagate this reason to the existing constraint. + // This way we always prioritize the unwritability as the reason + // for wildness. + // This is needed as 3C will currently only report one cause of wildness + // (See https://github.com/correctcomputation/checkedc-clang/issues/664) + if (C->isUnwritable()) { + auto *StoredConstraint = *Search; + StoredConstraint->setReason(C->getReason()); + } + return false; } diff --git a/clang/lib/3C/ProgramInfo.cpp b/clang/lib/3C/ProgramInfo.cpp index 232cf880a613..e7beac76caed 100644 --- a/clang/lib/3C/ProgramInfo.cpp +++ b/clang/lib/3C/ProgramInfo.cpp @@ -739,8 +739,8 @@ void ProgramInfo::addVariable(clang::DeclaratorDecl *D, assert("We shouldn't be adding a null CV to Variables map." && NewCV); if (!canWrite(PLoc.getFileName())) { - NewCV->equateWithItype(*this, "Declaration in non-writable file", &PLoc); - NewCV->constrainToWild(CS, "Declaration in non-writable file", &PLoc); + NewCV->equateWithItype(*this, UNWRITABLE_REASON, &PLoc); + NewCV->constrainToWild(CS, UNWRITABLE_REASON, &PLoc); } constrainWildIfMacro(NewCV, D->getLocation()); Variables[PLoc] = NewCV; @@ -811,7 +811,7 @@ void ProgramInfo::storePersistentConstraints(Expr *E, const CSetBkeyPair &Vars, auto PSL = PersistentSourceLoc::mkPSL(E, *C); if (PSL.valid() && !canWrite(PSL.getFileName())) for (ConstraintVariable *CVar : Vars.first) - CVar->constrainToWild(CS, "Expression in non-writable file", &PSL); + CVar->constrainToWild(CS, UNWRITABLE_REASON, &PSL); IDAndTranslationUnit Key = getExprKey(E, C); ExprConstraintVars[Key] = Vars; @@ -1190,12 +1190,12 @@ void ProgramInfo::addTypedef(PersistentSourceLoc PSL, bool CanRewriteDef, else V = new PointerVariableConstraint(TD, *this, C); - auto *const Rsn = !CanRewriteDef - ? "Unable to rewrite a typedef with multiple names" - : "Declaration in non-writable file"; - if (!(CanRewriteDef && canWrite(PSL.getFileName()))) { - V->constrainToWild(this->getConstraints(), Rsn, &PSL); - } + if (!CanRewriteDef) + V->constrainToWild(this->getConstraints(), "Unable to rewrite a typedef with multiple names", &PSL); + + if (!canWrite(PSL.getFileName())) + V->constrainToWild(this->getConstraints(), UNWRITABLE_REASON, &PSL); + constrainWildIfMacro(V, TD->getLocation(), &PSL); this->TypedefVars[PSL] = {*V}; } diff --git a/clang/test/3C/base_subdir/unwritable_rootcauses.c b/clang/test/3C/base_subdir/unwritable_rootcauses.c new file mode 100644 index 000000000000..2c4f76c187fe --- /dev/null +++ b/clang/test/3C/base_subdir/unwritable_rootcauses.c @@ -0,0 +1,4 @@ +// RUN: cd %S +// RUN: 3c -use-malloc=my_malloc -alltypes -addcr -output-dir=%t.checked/base_subdir -warn-all-root-cause %s -- -Xclang -verify=unwritable-expected -Wno-everything + +#include "../root_cause.c" diff --git a/clang/test/3C/canwrite_constraints.h b/clang/test/3C/canwrite_constraints.h index c6492a1b486f..56f18a50fa1a 100644 --- a/clang/test/3C/canwrite_constraints.h +++ b/clang/test/3C/canwrite_constraints.h @@ -6,11 +6,11 @@ // "@+1" means "on the next line". If we put the comment on the same line, it // breaks the CHECK_HIGHER. -// expected-warning@+1 {{Declaration in non-writable file}} +// expected-warning@+1 {{Source code in non-writable file}} inline void foo(int *p) {} // CHECK_HIGHER: inline void foo(_Ptr p) _Checked {} -// expected-warning@+1 {{Declaration in non-writable file}} +// expected-warning@+1 {{Source code in non-writable file}} int *foo_var = ((void *)0); // CHECK_HIGHER: _Ptr foo_var = ((void *)0); @@ -20,11 +20,11 @@ int *foo_var = ((void *)0); // Now that itypes can be re-solved, an itype in a non-writable file generates a // root cause warning just like a fully unchecked type. -// expected-warning@+1 {{Declaration in non-writable file}} +// expected-warning@+1 {{Source code in non-writable file}} void strtol_like(int *p : itype(_Ptr)); void atol_like() { - // expected-warning@+1 {{Expression in non-writable file}} + // expected-warning@+1 {{Source code in non-writable file}} strtol_like((int *)0); } @@ -36,7 +36,7 @@ inline void no_op() {} // In the lower case, this should stay wild // In the higher case, this should solve to checked -// expected-warning@+1 {{Declaration in non-writable file}} +// expected-warning@+1 {{Source code in non-writable file}} typedef int *intptr; // CHECK_HIGHER: typedef _Ptr intptr; @@ -46,9 +46,9 @@ typedef int *intptr; // (https://github.com/correctcomputation/checkedc-clang/issues/423) as an // example. -// expected-warning@+1 {{Declaration in non-writable file}} +// expected-warning@+1 {{Source code in non-writable file}} void unwritable_cast(void((*g)(int *q)) : itype(_Ptr)>)) { - // expected-warning@+1 {{Declaration in non-writable file}} + // expected-warning@+1 {{Source code in non-writable file}} int *p = 0; // Now 3C thinks it needs to insert _Assume_bounds_cast<_Ptr> around `p` // because it forgets that it is allowed to use the original type of `g`. @@ -59,29 +59,29 @@ void unwritable_cast(void((*g)(int *q)) : itype(_Ptr)>)) { // Make sure that FVComponentVariable::equateWithItype prevents both of these // from being changed. -// expected-warning@+1 {{Declaration in non-writable file}} +// expected-warning@+1 {{Source code in non-writable file}} void unwritable_func_with_itype(int *p : itype(_Array_ptr)) {} -// expected-warning@+1 {{Declaration in non-writable file}} +// expected-warning@+1 {{Source code in non-writable file}} void unwritable_func_with_itype_and_bounds(int *p : itype(_Array_ptr) count(12)) { } // Test for https://github.com/correctcomputation/checkedc-clang/issues/580 -// expected-warning@+1 {{Declaration in non-writable file}} +// expected-warning@+1 {{Source code in non-writable file}} _Itype_for_any(T) void my_generic_function(void *p : itype(_Ptr)); -// expected-warning@+1 {{Declaration in non-writable file}} +// expected-warning@+1 {{Source code in non-writable file}} _Itype_for_any(T) void *my_generic_return(void) : itype(_Ptr); void unwritable_type_argument() { int i; - int *b; // expected-warning {{Declaration in non-writable file}} + int *b; // expected-warning {{Source code in non-writable file}} // This warning relates to the atom representing the temporary pointer of // `&i`. https://github.com/correctcomputation/checkedc-clang/issues/618 would // make 3C smarter to avoid the need to constrain the temporary pointer. - // expected-warning@+1 {{Expression in non-writable file}} + // expected-warning@+1 {{Source code in non-writable file}} my_generic_function(&i); - b = my_generic_return(); // expected-warning {{Expression in non-writable file}} + b = my_generic_return(); // expected-warning {{Source code in non-writable file}} } diff --git a/clang/test/3C/root_cause.c b/clang/test/3C/root_cause.c index f1e1a66561b8..cbe244955108 100644 --- a/clang/test/3C/root_cause.c +++ b/clang/test/3C/root_cause.c @@ -1,60 +1,90 @@ -// RUN: 3c -base-dir=%S -alltypes -warn-root-cause %s -- -Xclang -verify -Wno-everything +// RUN: 3c -use-malloc=my_malloc -base-dir=%S -alltypes -warn-all-root-cause %s -- -Xclang -verify -Wno-everything // This test is unusual in that it checks for the errors in the code -#include +// We define a prototype for malloc here instead of including +// as including stdlib will create numerous root-cause warnings we don't want to deal with +// unwritable-expected-warning@+2 {{0 unchecked pointers: Source code in non-writable file}} +// expected-warning@+1 {{Unchecked pointer in parameter or return of external function my_malloc}} +_Itype_for_any(T) void *my_malloc(unsigned long size) : itype(_Array_ptr) byte_count(size); + + +// unwritable-expected-warning@+1 {{0 unchecked pointers: Source code in non-writable file}} void *x; // expected-warning {{1 unchecked pointer: Default void* type}} void test0() { + // unwritable-expected-warning@+1 {{0 unchecked pointers: Source code in non-writable file}} int *a; + // unwritable-expected-warning@+1 {{0 unchecked pointers: Source code in non-writable file}} char *b; + // unwritable-expected-warning@+1 {{0 unchecked pointers: Source code in non-writable file}} a = b; // expected-warning {{2 unchecked pointers: Cast from char * to int *}} + // unwritable-expected-warning@+1 {{0 unchecked pointers: Source code in non-writable file}} int *c; (char *)c; // expected-warning {{1 unchecked pointer: Cast from int * to char *}} + // unwritable-expected-warning@+1 {{0 unchecked pointers: Source code in non-writable file}} int *e; + // unwritable-expected-warning@+1 {{0 unchecked pointers: Source code in non-writable file}} char *f; + // unwritable-expected-warning@+1 {{0 unchecked pointers: Source code in non-writable file}} f = (char *)e; // expected-warning {{2 unchecked pointers: Cast from int * to char *}} } void test1() { int a; - int *b; - b = malloc(sizeof(int)); // expected-warning {{1 unchecked pointer: Bad pointer type solution}} + // unwritable-expected-warning@+1 {{0 unchecked pointers: Source code in non-writable file}} + int *b; + // unwritable-expected-warning@+1 {{0 unchecked pointers: Source code in non-writable file}} + b = my_malloc(sizeof(int)); // expected-warning {{1 unchecked pointer: Bad pointer type solution}} b[0] = 1; union u { + // unwritable-expected-warning@+1 {{0 unchecked pointers: Source code in non-writable file}} int *a; // expected-warning {{1 unchecked pointer: Union or external struct field encountered}} + // unwritable-expected-warning@+1 {{0 unchecked pointers: Source code in non-writable file}} int *b; // expected-warning {{1 unchecked pointer: Union or external struct field encountered}} }; - void (*c)(void); + void (*c)(void); // unwritable-expected-warning {{0 unchecked pointers: Source code in non-writable file}} c++; // expected-warning {{1 unchecked pointer: Pointer arithmetic performed on a function pointer}} - int *d = malloc(1); // expected-warning {{1 unchecked pointer: Unsafe call to allocator function}} - int **e = malloc(1); // expected-warning {{1 unchecked pointer: Unsafe call to allocator function}} + // unwritable-expected-warning@+2 {{0 unchecked pointers: Source code in non-writable file}} + // unwritable-expected-warning@+1 {{0 unchecked pointers: Source code in non-writable file}} + int *d = my_malloc(1); // expected-warning {{1 unchecked pointer: Unsafe call to allocator function}} + // unwritable-expected-warning@+2 {{0 unchecked pointers: Source code in non-writable file}} + // unwritable-expected-warning@+1 {{0 unchecked pointers: Source code in non-writable file}} + int **e = my_malloc(1); // expected-warning {{1 unchecked pointer: Unsafe call to allocator function}} } +// unwritable-expected-warning@+2 {{0 unchecked pointers: Source code in non-writable file}} // expected-warning@+1 {{1 unchecked pointer: External global variable glob has no definition}} extern int *glob; +// unwritable-expected-warning@+2 {{0 unchecked pointers: Source code in non-writable file}} // expected-warning@+1 {{1 unchecked pointer: Unchecked pointer in parameter or return of external function glob_f}} int *glob_f(void); +// unwritable-expected-warning@+1 {{0 unchecked pointers: Source code in non-writable file}} void (*void_star_fptr)(void *); // expected-warning {{1 unchecked pointer: Default void* type}} +// unwritable-expected-warning@+1 {{ 0 unchecked pointers: Source code in non-writable file}} void void_star_fn(void *p); // expected-warning {{1 unchecked pointer: Default void* type}} typedef struct { int x; float f; +// unwritable-expected-warning@+1 {{0 unchecked pointers: Source code in non-writable file}} } A, *PA; // expected-warning@-1 {{2 unchecked pointers: Unable to rewrite a typedef with multiple names}} // Two pointers affected by the above root cause. Do not count the typedef // itself as an affected pointer even though that's where the star is written. // Count each of the variables below even though no star is actually written. +// unwritable-expected-warning@+2 {{0 unchecked pointers: Source code in non-writable file}} +// unwritable-expected-warning@+1 {{0 unchecked pointers: Source code in non-writable file}} PA pa_test0, pa_test1; +// unwritable-expected-warning@+2 {{0 unchecked pointers: Source code in non-writable file}} // expected-warning@+1 {{1 unchecked pointer: Internal constraint for generic function declaration, for which 3C currently does not support re-solving.}} _Itype_for_any(T) void remember(void *p : itype(_Ptr)) {} From 0fd38a3b61eec76262d4d05b3e743a7f6660d60f Mon Sep 17 00:00:00 2001 From: Kyle Headley Date: Fri, 13 Aug 2021 10:30:27 -0400 Subject: [PATCH 17/38] Don't add implicit cast constraints to void* arguments (#676) * avoid adding extra constraint when casting arguments to void * add test * more precise test name --- clang/lib/3C/CastPlacement.cpp | 4 ++++ clang/lib/3C/ConstraintBuilder.cpp | 11 +++++----- clang/test/3C/implicit_casts_root_cause.c | 26 +++++++++++++++++++++++ 3 files changed, 35 insertions(+), 6 deletions(-) create mode 100644 clang/test/3C/implicit_casts_root_cause.c diff --git a/clang/lib/3C/CastPlacement.cpp b/clang/lib/3C/CastPlacement.cpp index ad83833b7e9f..fbef1de32165 100644 --- a/clang/lib/3C/CastPlacement.cpp +++ b/clang/lib/3C/CastPlacement.cpp @@ -66,6 +66,10 @@ bool CastPlacementVisitor::VisitCallExpr(CallExpr *CE) { CVarSet ArgConstraints = CR.getExprConstraintVarsSet(ArgExpr); for (auto *ArgC : ArgConstraints) { + // If the function takes a void *, we already know about the wildness, + // so allow the implicit cast. + if (TypeVar == nullptr && FV->getExternalParam(PIdx)->isVoidPtr()) + continue; CastNeeded CastKind = needCasting( ArgC, ArgC, FV->getInternalParam(PIdx), FV->getExternalParam(PIdx)); if (CastKind != NO_CAST) { diff --git a/clang/lib/3C/ConstraintBuilder.cpp b/clang/lib/3C/ConstraintBuilder.cpp index 4fbc2e803ac5..6e1357430f4f 100644 --- a/clang/lib/3C/ConstraintBuilder.cpp +++ b/clang/lib/3C/ConstraintBuilder.cpp @@ -239,12 +239,11 @@ class FunctionVisitor : public RecursiveASTVisitor { for (const auto &A : E->arguments()) { CSetBkeyPair ArgumentConstraints; if (I < TargetFV->numParams()) { - // Remove casts to void* on polymorphic types that are used - // consistently. - const int TyIdx = - TargetFV->getExternalParam(I)->getGenericIndex(); - if (ConsistentTypeParams.find(TyIdx) != - ConsistentTypeParams.end()) + // When the function has a void* parameter, Clang will + // add an implicit cast to void* here. Generating constraints + // will add an extraneous wild constraint to void*. This + // unnecessarily complicates results and root causes. + if (TargetFV->getExternalParam(I)->isVoidPtr()) ArgumentConstraints = CB.getExprConstraintVars(A->IgnoreImpCasts()); else diff --git a/clang/test/3C/implicit_casts_root_cause.c b/clang/test/3C/implicit_casts_root_cause.c new file mode 100644 index 000000000000..c3f1451c96d4 --- /dev/null +++ b/clang/test/3C/implicit_casts_root_cause.c @@ -0,0 +1,26 @@ +// RUN: 3c -base-dir=%S -alltypes -warn-root-cause %s -- -Xclang -verify -Wno-everything + +// This test checks that void* casts of arguments are ignored by the root +// cause code (and handled by the function parameter causes), but other casts +// are still noted. + +void has_void(void* v); // expected-warning {{3 unchecked pointers: Default void* type}} +void test_no_cause() { + int *b, *c; + has_void(b); // specifically no warning here, since it shows up above + has_void(c); +} + +void has_float(float* v); // expected-warning {{Unchecked pointer in parameter or return of external function has_float}} +void test_float_cause() { + int *b, *c; + has_float(b); // expected-warning {{1 unchecked pointer: Cast from int * to float *}} + has_float(c); // expected-warning {{1 unchecked pointer: Cast from int * to float *}} +} + +void has_body(float* v){} +void test_only_args_cause() { + int *b, *c; + has_body(b); // expected-warning {{1 unchecked pointer: Cast from int * to float *}} + has_body(c); // expected-warning {{1 unchecked pointer: Cast from int * to float *}} +} From eefab622b1862344dbbe8a14aec60fb01ff10924 Mon Sep 17 00:00:00 2001 From: John Kastner Date: Wed, 18 Aug 2021 14:59:16 -0400 Subject: [PATCH 18/38] Change test7 in functionDeclEnd.c to infer a valid bound (#684) The purpose of this function was to test function declaration rewriting for itype array pointers declared with bounds but without an explicit itype. This is equally well tested when a correct bound is inferred for the parameter array. --- clang/test/3C/functionDeclEnd.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/clang/test/3C/functionDeclEnd.c b/clang/test/3C/functionDeclEnd.c index 795335db5583..9214b3763b4e 100644 --- a/clang/test/3C/functionDeclEnd.c +++ b/clang/test/3C/functionDeclEnd.c @@ -126,7 +126,7 @@ void test6(int *a) int *test7(int *a) : count(10) //CHECK_NOALL: int *test7(int *a : itype(_Ptr)) : count(10) -//CHECK_ALL: _Array_ptr test7(_Array_ptr a) : count(10) +//CHECK_ALL: _Array_ptr test7(_Array_ptr a : count(10)) : count(10) #else int *test7(int *a) : count(10) @@ -139,8 +139,10 @@ int *test7(int *a) //CHECK: ; int *test7(int *a) : count(10) { - //CHECK_ALL: _Array_ptr test7(_Array_ptr a) : count(10) _Checked { + //CHECK_ALL: _Array_ptr test7(_Array_ptr a : count(10)) : count(10) _Checked { //CHECK_NOALL: int *test7(int *a : itype(_Ptr)) : count(10) { + for (int i = 0; i < 10; i++) + a[i]; return a; } From 5d364ab8901d51b126fbd960aedf5a41b69b7fc9 Mon Sep 17 00:00:00 2001 From: Kyle Headley Date: Tue, 24 Aug 2021 14:25:19 -0400 Subject: [PATCH 19/38] Initial enhancement to allow 3C to infer generics (#683) Enhancement for functions with single `void*` parameter or return to be converted into `_For_any` generics. --- clang/include/clang/3C/ConstraintResolver.h | 2 + clang/include/clang/3C/ConstraintVariables.h | 45 ++++- clang/include/clang/3C/Constraints.h | 1 + clang/include/clang/3C/DeclRewriter.h | 7 +- clang/include/clang/3C/ProgramInfo.h | 41 ++++- clang/include/clang/3C/RewriteUtils.h | 10 +- clang/include/clang/3C/TypeVariableAnalysis.h | 19 ++- clang/lib/3C/CastPlacement.cpp | 8 +- clang/lib/3C/CheckedRegions.cpp | 2 +- clang/lib/3C/ConstraintBuilder.cpp | 3 +- clang/lib/3C/ConstraintResolver.cpp | 57 +++++-- clang/lib/3C/ConstraintVariables.cpp | 133 +++++++++++---- clang/lib/3C/DeclRewriter.cpp | 82 +++++++-- clang/lib/3C/ProgramInfo.cpp | 44 +---- clang/lib/3C/RewriteUtils.cpp | 37 +++- clang/lib/3C/TypeVariableAnalysis.cpp | 49 +++++- clang/lib/3C/Utils.cpp | 17 +- clang/test/3C/cast.c | 20 +++ clang/test/3C/dont_rewrite_cflags.c | 10 ++ clang/test/3C/generalize.c | 159 ++++++++++++++++++ clang/test/3C/liberal_itypes_ptr.c | 5 +- 21 files changed, 605 insertions(+), 146 deletions(-) create mode 100644 clang/test/3C/dont_rewrite_cflags.c create mode 100644 clang/test/3C/generalize.c diff --git a/clang/include/clang/3C/ConstraintResolver.h b/clang/include/clang/3C/ConstraintResolver.h index 8e36be30c001..beeb4fbd2330 100644 --- a/clang/include/clang/3C/ConstraintResolver.h +++ b/clang/include/clang/3C/ConstraintResolver.h @@ -42,6 +42,8 @@ class ConstraintResolver { CVarSet getCalleeConstraintVars(CallExpr *CE); + bool isCastofGeneric(CastExpr *C); + // Handle assignment of RHS expression to LHS expression using the // given action. void constrainLocalAssign(Stmt *TSt, Expr *LHS, Expr *RHS, diff --git a/clang/include/clang/3C/ConstraintVariables.h b/clang/include/clang/3C/ConstraintVariables.h index 5bc7f4a7b92a..bb857e3ec77b 100644 --- a/clang/include/clang/3C/ConstraintVariables.h +++ b/clang/include/clang/3C/ConstraintVariables.h @@ -354,7 +354,9 @@ class PointerVariableConstraint : public ConstraintVariable { // Generic types can be used with fewer restrictions, so this field is used // stop assignments with generic variables from forcing constraint variables // to be wild. - int GenericIndex; + // Source is generated from the source code, Inferred is set internally + int SourceGenericIndex; + int InferredGenericIndex; // Empty array pointers are represented the same as standard pointers. This // lets pointers be passed to functions expecting a zero width array. This @@ -378,7 +380,8 @@ class PointerVariableConstraint : public ConstraintVariable { PointerVariableConstraint(std::string Name) : ConstraintVariable(PointerVariable, "", Name), FV(nullptr), SrcHasItype(false), PartOfFuncPrototype(false), Parent(nullptr), - GenericIndex(-1), IsZeroWidthArray(false), IsTypedef(false), TDT(nullptr), + SourceGenericIndex(-1), InferredGenericIndex(-1), + IsZeroWidthArray(false), IsTypedef(false), TDT(nullptr), TypedefLevelInfo({}), IsVoidPtr(false) {} public: @@ -404,9 +407,12 @@ class PointerVariableConstraint : public ConstraintVariable { // Get bounds annotation. std::string getBoundsStr() const { return BoundsAnnotationStr; } - bool getIsGeneric() const { return GenericIndex >= 0; } - int getGenericIndex() const { return GenericIndex; } - + bool isGeneric() const { return InferredGenericIndex >= 0; } + int getGenericIndex() const { return InferredGenericIndex; } + void setGenericIndex(int idx) { InferredGenericIndex = idx; } + bool isGenericChanged() const { + return SourceGenericIndex != InferredGenericIndex; + } // Was this variable a checked pointer in the input program? // This is important for two reasons: (1) externs that are checked should be // kept that way during solving, (2) nothing that was originally checked @@ -449,6 +455,10 @@ class PointerVariableConstraint : public ConstraintVariable { // ForceGenericIndex: CheckedC supports generic types (_Itype_for_any) which // need less restrictive constraints. Set >= 0 to indicate // that this variable should be considered generic. + // PotentialGeneric: Whether this may become generic after analysis. Disables + // constraint to wild for non-generics. If you use this + // you'll have to add that constraint later if it is + // not generic. // TSI: TypeSourceInfo object gives access to information about the source // code representation of the type. Allows for more precise rewriting by // preserving the exact syntax used to write types that aren't rewritten @@ -458,6 +468,7 @@ class PointerVariableConstraint : public ConstraintVariable { const clang::ASTContext &C, std::string *InFunc = nullptr, int ForceGenericIndex = -1, + bool PotentialGeneric = false, bool VarAtomForChecked = false, TypeSourceInfo *TSI = nullptr, const clang::QualType &ItypeT = QualType()); @@ -550,7 +561,7 @@ class FVComponentVariable { FVComponentVariable(const clang::QualType &QT, const clang::QualType &ITypeT, clang::DeclaratorDecl *D, std::string N, ProgramInfo &I, const clang::ASTContext &C, std::string *InFunc, - bool HasItype); + bool PotentialGeneric, bool HasItype); void mergeDeclaration(FVComponentVariable *From, ProgramInfo &I, std::string &ReasonFailed); @@ -567,6 +578,11 @@ class FVComponentVariable { PVConstraint *getInternal() const { return InternalConstraint; } PVConstraint *getExternal() const { return ExternalConstraint; } + void setGenericIndex(int idx) { + ExternalConstraint->setGenericIndex(idx); + InternalConstraint->setGenericIndex(idx); + } + void equateWithItype(ProgramInfo &CS, const std::string &ReasonUnchangeable, PersistentSourceLoc *PSL) const; @@ -595,7 +611,7 @@ class FunctionVariableConstraint : public ConstraintVariable { // Flag to indicate whether this is a function pointer or not. bool IsFunctionPtr; - // Count of type parameters from `_Itype_for_any(...)`. + // Count of type parameters (originally from `_Itype_for_any(...)`). int TypeParams; void equateFVConstraintVars(ConstraintVariable *CV, ProgramInfo &Info) const; @@ -652,9 +668,24 @@ class FunctionVariableConstraint : public ConstraintVariable { bool srcHasItype() const override; bool srcHasBounds() const override; + // The number of type variables + int getGenericParams() const { + return TypeParams; + } + // remove added generics + // use when we constrain a potential generic param to wild + void resetGenericParams() { + TypeParams = 0; + } + + // The type parameter index of the return int getGenericIndex() const { return ReturnVar.ExternalConstraint->getGenericIndex(); } + // Change the type parameter index of the return + void setGenericIndex(int idx) { + ReturnVar.ExternalConstraint->setGenericIndex(idx); + } bool solutionEqualTo(Constraints &CS, const ConstraintVariable *CV, bool ComparePtyp = true) const override; diff --git a/clang/include/clang/3C/Constraints.h b/clang/include/clang/3C/Constraints.h index cca53f9729f0..38ca8a49ddc0 100644 --- a/clang/include/clang/3C/Constraints.h +++ b/clang/include/clang/3C/Constraints.h @@ -32,6 +32,7 @@ class ConstraintsGraph; #define DEFAULT_REASON "UNKNOWN_REASON" #define POINTER_IS_ARRAY_REASON "Pointer is array but alltypes is disabled." +#define VOID_TYPE_REASON "Default void* type" #define UNWRITABLE_REASON "Source code in non-writable file." template struct PComp { diff --git a/clang/include/clang/3C/DeclRewriter.h b/clang/include/clang/3C/DeclRewriter.h index 0dd3de375fdf..8ebc9f037f49 100644 --- a/clang/include/clang/3C/DeclRewriter.h +++ b/clang/include/clang/3C/DeclRewriter.h @@ -102,9 +102,10 @@ class FunctionDeclBuilder : public RecursiveASTVisitor { // Get existing itype string from constraint variables. std::string getExistingIType(ConstraintVariable *DeclC); - virtual void buildDeclVar(const FVComponentVariable *CV, DeclaratorDecl *Decl, - std::string &Type, std::string &IType, - std::string UseName, bool &RewriteParm, + virtual void buildDeclVar(const FVComponentVariable *CV, + DeclaratorDecl *Decl, std::string &Type, + std::string &IType, std::string UseName, + bool &RewriteGen, bool &RewriteParm, bool &RewriteRet, bool StaticFunc); void buildCheckedDecl(PVConstraint *Defn, DeclaratorDecl *Decl, std::string &Type, std::string &IType, diff --git a/clang/include/clang/3C/ProgramInfo.h b/clang/include/clang/3C/ProgramInfo.h index 7b240ff2cbe8..1ebcabdf7bad 100644 --- a/clang/include/clang/3C/ProgramInfo.h +++ b/clang/include/clang/3C/ProgramInfo.h @@ -43,9 +43,40 @@ class ProgramVariableAdder { typedef std::pair CSetBkeyPair; +// The pair of CVs are the type param constraint and an optional +// constraint used to get the generic index. A better solution would have +// generic constraints saved within ConstraintVariables, but those don't +// exist at this time. +struct TypeParamConstraint { + ConstraintVariable *MainConstraint; + ConstraintVariable *GenericAddition; + TypeParamConstraint() : + MainConstraint(nullptr), GenericAddition(nullptr) {} + TypeParamConstraint(ConstraintVariable *M, ConstraintVariable *G) : + MainConstraint(M), GenericAddition(G) {} + // Fast. Whether `getConstraint` will return something other than nullptr. + bool isConsistent() const { return MainConstraint != nullptr; } + // Provides generic information if available and safe. This is somewhat of + // a hack for nested generics and returns (the constraint for) a local + // parameter. Otherwise, returns the generated constraint, which can also be + // accessed as `MainConstraint`. + ConstraintVariable *getConstraint(const EnvironmentMap &E) { + if (MainConstraint != nullptr && GenericAddition != nullptr && + GenericAddition->isSolutionChecked(E)) { + return GenericAddition; + } else { + return MainConstraint; + } + } +}; + class ProgramInfo : public ProgramVariableAdder { public: - typedef std::map CallTypeParamBindingsT; + + // This map holds similar information as the type variable map in + // ConstraintBuilder.cpp, but it is stored in a form that is usable during + // rewriting. + typedef std::map CallTypeParamBindingsT; typedef std::map ExternalFunctionMapType; typedef std::map StaticFunctionMapType; @@ -119,7 +150,8 @@ class ProgramInfo : public ProgramVariableAdder { } void setTypeParamBinding(CallExpr *CE, unsigned int TypeVarIdx, - ConstraintVariable *CV, ASTContext *C); + ConstraintVariable *CV, + ConstraintVariable* Ident, ASTContext *C); bool hasTypeParamBindings(CallExpr *CE, ASTContext *C) const; const CallTypeParamBindingsT &getTypeParamBindings(CallExpr *CE, ASTContext *C) const; @@ -217,11 +249,6 @@ class ProgramInfo : public ProgramVariableAdder { // used as keys for maps from ASTNodes. std::map TranslationUnitIdxMap; - // Special-case handling for decl introductions. For the moment this covers: - // * void-typed variables - // * va_list-typed variables - void specialCaseVarIntros(ValueDecl *D, ASTContext *Context); - // Inserts the given FVConstraint set into the extern or static function map. // Returns the merged version if it was a redeclaration, or the constraint // parameter if it was new. diff --git a/clang/include/clang/3C/RewriteUtils.h b/clang/include/clang/3C/RewriteUtils.h index 0f312418bf5e..8a86efc5c69a 100644 --- a/clang/include/clang/3C/RewriteUtils.h +++ b/clang/include/clang/3C/RewriteUtils.h @@ -84,21 +84,23 @@ class FunctionDeclReplacement DeclReplacement::DRK_FunctionDecl> { public: explicit FunctionDeclReplacement(FunctionDecl *D, std::string R, bool Return, - bool Params) - : DeclReplacementTempl(D, nullptr, R), RewriteReturn(Return), - RewriteParams(Params) { + bool Params, bool Generic = false) + : DeclReplacementTempl(D, nullptr, R), RewriteGeneric(Generic), + RewriteReturn(Return), RewriteParams(Params) { assert("Doesn't make sense to rewrite nothing!" && - (RewriteReturn || RewriteParams)); + (RewriteGeneric || RewriteReturn || RewriteParams)); } SourceRange getSourceRange(SourceManager &SM) const override; private: // This determines if the full declaration or the return will be replaced. + bool RewriteGeneric; bool RewriteReturn; bool RewriteParams; SourceLocation getDeclBegin(SourceManager &SM) const; + SourceLocation getReturnBegin(SourceManager &SM) const; SourceLocation getParamBegin(SourceManager &SM) const; SourceLocation getReturnEnd(SourceManager &SM) const; SourceLocation getDeclEnd(SourceManager &SM) const; diff --git a/clang/include/clang/3C/TypeVariableAnalysis.h b/clang/include/clang/3C/TypeVariableAnalysis.h index 5810d249ace5..416550a6f378 100644 --- a/clang/include/clang/3C/TypeVariableAnalysis.h +++ b/clang/include/clang/3C/TypeVariableAnalysis.h @@ -20,8 +20,9 @@ class TypeVariableEntry { public: // Note: does not initialize TyVarType! TypeVariableEntry() : IsConsistent(false), TypeParamConsVar(nullptr) {} - TypeVariableEntry(QualType Ty, std::set &CVs, - bool ForceInconsistent = false) + TypeVariableEntry(QualType Ty, std::set &CVs + , bool ForceInconsistent = false + , ConstraintVariable *IdentCV = nullptr) : TypeParamConsVar(nullptr) { // We'll need a name to provide the type arguments during rewriting, so no // anonymous types are allowed. @@ -30,6 +31,7 @@ class TypeVariableEntry { !isTypeAnonymous(Ty->getPointeeOrArrayElementType()); TyVarType = Ty; ArgConsVars = CVs; + GenArgumentCV = IdentCV; } bool getIsConsistent() const; @@ -38,10 +40,12 @@ class TypeVariableEntry { // Note: undefined behaviour if `getIsConsistent` is false std::set &getConstraintVariables(); ConstraintVariable *getTypeParamConsVar(); + ConstraintVariable *getGenArgCV(); void insertConstraintVariables(std::set &CVs); void setTypeParamConsVar(ConstraintVariable *CV); - void updateEntry(QualType Ty, std::set &CVs); + void updateEntry(QualType Ty, std::set &CVs, + ConstraintVariable *IdentCV); private: // Is this type variable instantiated consistently. True when all uses have @@ -61,6 +65,12 @@ class TypeVariableEntry { // A single constraint variable for solving the checked type of the type // variable. It is constrained GEQ all elements of ArgConsVars. ConstraintVariable *TypeParamConsVar; + + // If an argument is a single identifier, store the constraint variable + // to recognize changes in type from inferred generics. Null otherwise. + // Meaningless if `TypeParamConsVar` has a basetype other than void, and + // when we have generic index constraints, those should be favored over this + ConstraintVariable *GenArgumentCV; }; // Stores the instantiated type for each type variables. This map has @@ -100,7 +110,8 @@ class TypeVarVisitor : public RecursiveASTVisitor, ConstraintResolver CR; TypeVariableMapT TVMap; - void insertBinding(CallExpr *CE, const int TyIdx, QualType Ty, CVarSet &CVs); + void insertBinding(CallExpr *CE, const int TyIdx, QualType Ty, + CVarSet &CVs, ConstraintVariable *IdentCV = nullptr); }; bool typeArgsProvided(CallExpr *Call); diff --git a/clang/lib/3C/CastPlacement.cpp b/clang/lib/3C/CastPlacement.cpp index fbef1de32165..b3886eaed7e5 100644 --- a/clang/lib/3C/CastPlacement.cpp +++ b/clang/lib/3C/CastPlacement.cpp @@ -57,9 +57,11 @@ bool CastPlacementVisitor::VisitCallExpr(CallExpr *CE) { Expr *ArgExpr = A; if (FD && PIdx < FD->getNumParams()) { const int TyVarIdx = FV->getExternalParam(PIdx)->getGenericIndex(); - if (TypeVars.find(TyVarIdx) != TypeVars.end() && - TypeVars[TyVarIdx] != nullptr) - TypeVar = TypeVars[TyVarIdx]; + // Check if local type vars are available + if (TypeVars.find(TyVarIdx) != TypeVars.end()) { + TypeVar = TypeVars[TyVarIdx].getConstraint( + Info.getConstraints().getVariables()); + } } if (TypeVar != nullptr) ArgExpr = ArgExpr->IgnoreImpCasts(); diff --git a/clang/lib/3C/CheckedRegions.cpp b/clang/lib/3C/CheckedRegions.cpp index 80beb595c9e2..8500725db594 100644 --- a/clang/lib/3C/CheckedRegions.cpp +++ b/clang/lib/3C/CheckedRegions.cpp @@ -230,7 +230,7 @@ bool CheckedRegionFinder::VisitCallExpr(CallExpr *C) { if (FD) { if (Info.hasTypeParamBindings(C, Context)) for (auto Entry : Info.getTypeParamBindings(C, Context)) - Wild |= (Entry.second == nullptr); + Wild |= !Entry.second.isConsistent(); auto Type = FD->getReturnType(); Wild |= (!(FD->hasPrototype() || FD->doesThisDeclarationHaveABody())) || containsUncheckedPtr(Type); diff --git a/clang/lib/3C/ConstraintBuilder.cpp b/clang/lib/3C/ConstraintBuilder.cpp index 6e1357430f4f..653d26ce38b4 100644 --- a/clang/lib/3C/ConstraintBuilder.cpp +++ b/clang/lib/3C/ConstraintBuilder.cpp @@ -166,7 +166,8 @@ class FunctionVisitor : public RecursiveASTVisitor { // Is cast compatible with LHS type? QualType SrcT = C->getSubExpr()->getType(); QualType DstT = C->getType(); - if (!isCastSafe(DstT, SrcT) && !Info.hasPersistentConstraints(C, Context)) { + if (!CB.isCastofGeneric(C) && !isCastSafe(DstT, SrcT) + && !Info.hasPersistentConstraints(C, Context)) { auto CVs = CB.getExprConstraintVarsSet(C->getSubExpr()); std::string Rsn = "Cast from " + SrcT.getAsString() + " to " + DstT.getAsString(); diff --git a/clang/lib/3C/ConstraintResolver.cpp b/clang/lib/3C/ConstraintResolver.cpp index 88c77bf9806d..88eb913b7573 100644 --- a/clang/lib/3C/ConstraintResolver.cpp +++ b/clang/lib/3C/ConstraintResolver.cpp @@ -157,12 +157,14 @@ inline CSetBkeyPair pairWithEmptyBkey(const CVarSet &Vars) { ConstraintVariable *localReturnConstraint( FVConstraint *FV, ProgramInfo::CallTypeParamBindingsT TypeVars, - Constraints &CS) { + Constraints &CS, + ProgramInfo &Info) { int TyVarIdx = FV->getExternalReturn()->getGenericIndex(); // Check if local type vars are available if (TypeVars.find(TyVarIdx) != TypeVars.end() && - TypeVars[TyVarIdx] != nullptr) { - ConstraintVariable *CV = TypeVars[TyVarIdx]; + TypeVars[TyVarIdx].isConsistent()) { + ConstraintVariable *CV = TypeVars[TyVarIdx].getConstraint( + Info.getConstraints().getVariables()); if (FV->getExternalReturn()->hasBoundsKey()) CV->setBoundsKey(FV->getExternalReturn()->getBoundsKey()); return CV; @@ -256,7 +258,7 @@ CSetBkeyPair ConstraintResolver::getExprConstraintVars(Expr *E) { // If the cast is NULL, it will otherwise seem invalid, but we want to // handle it as usual so the type in the cast can be rewritten. if (!isNULLExpression(ECE, *Context) && TypE->isPointerType() && - !isCastSafe(TypE, TmpE->getType())) { + !isCastSafe(TypE, TmpE->getType()) && !isCastofGeneric(ECE)) { CVarSet Vars = getExprConstraintVarsSet(TmpE); Ret = pairWithEmptyBkey(getInvalidCastPVCons(ECE)); constrainConsVarGeq(Vars, Ret.first, CS, nullptr, Safe_to_Wild, false, @@ -442,10 +444,10 @@ CSetBkeyPair ConstraintResolver::getExprConstraintVars(Expr *E) { for (ConstraintVariable *C : Tmp.first) { if (FVConstraint *FV = dyn_cast(C)) { - ReturnCVs.insert(localReturnConstraint(FV,TypeVars,CS)); + ReturnCVs.insert(localReturnConstraint(FV,TypeVars,CS,Info)); } else if (PVConstraint *PV = dyn_cast(C)) { if (FVConstraint *FV = PV->getFV()) - ReturnCVs.insert(localReturnConstraint(FV,TypeVars,CS)); + ReturnCVs.insert(localReturnConstraint(FV,TypeVars,CS,Info)); } } } else if (DeclaratorDecl *FD = dyn_cast(D)) { @@ -453,8 +455,11 @@ CSetBkeyPair ConstraintResolver::getExprConstraintVars(Expr *E) { if (isFunctionAllocator(std::string(FD->getName()))) { bool DidInsert = false; IsAllocator = true; - if (TypeVars.find(0) != TypeVars.end() && TypeVars[0] != nullptr) { - ReturnCVs.insert(TypeVars[0]); + if (TypeVars.find(0) != TypeVars.end() && + TypeVars[0].isConsistent()) { + ConstraintVariable *CV = TypeVars[0].getConstraint( + Info.getConstraints().getVariables()); + ReturnCVs.insert(CV); DidInsert = true; } else if (CE->getNumArgs() > 0) { QualType ArgTy; @@ -491,13 +496,13 @@ CSetBkeyPair ConstraintResolver::getExprConstraintVars(Expr *E) { assert(CV.hasValue() && "Function without constraint variable."); /* Direct function call */ if (FVConstraint *FVC = dyn_cast(&CV.getValue())) - ReturnCVs.insert(localReturnConstraint(FVC,TypeVars,CS)); + ReturnCVs.insert(localReturnConstraint(FVC,TypeVars,CS,Info)); /* Call via function pointer */ else { PVConstraint *Tmp = dyn_cast(&CV.getValue()); assert(Tmp != nullptr); if (FVConstraint *FVC = Tmp->getFV()) - ReturnCVs.insert(localReturnConstraint(FVC,TypeVars,CS)); + ReturnCVs.insert(localReturnConstraint(FVC,TypeVars,CS,Info)); else { // No FVConstraint -- make WILD. std::string Rsn = "Can't get return variable of function call."; @@ -767,6 +772,38 @@ CVarSet ConstraintResolver::getCalleeConstraintVars(CallExpr *CE) { return FVCons; } +// This serves as an exception to the unsafety of casting from void*. +// In most cases, CheckedC can handle generic casts, so we can ignore them. +// CheckedC can't handle allocators without checked headers, so we add them +// to this exception for when we're dealing with small examples. +bool ConstraintResolver::isCastofGeneric(CastExpr *C) { + Expr *SE = C->getSubExpr(); + if (CHKCBindTemporaryExpr *CE = dyn_cast(SE)) + SE = CE->getSubExpr(); + if (auto *CE = dyn_cast_or_null(SE)) { + // Check for built-in allocators, in case the standard headers are not used. + // This is a required exception, because allocators return void*, and casts + // from it are unsafe. We assume the cast is appropriate. With checked + // headers clang can figure out if it is safe. + if (auto *DD = dyn_cast_or_null(CE->getCalleeDecl())) { + std::string Name = DD->getNameAsString(); + if (isFunctionAllocator(Name)) + return true; + } + // Check for a generic function call. + CVarSet CVS = getCalleeConstraintVars(CE); + // If there are multiple constraints in the expression we + // can't guarantee safety, so only return true if the + // cast is directly on a function call. + if (CVS.size() == 1) { + if (auto *FVC = dyn_cast(*CVS.begin())) { + return FVC->getGenericParams() > 0; + } + } + } + return false; +} + // Construct a PVConstraint for an expression that can safely be used when // rewriting the expression later on. This is done by making the constraint WILD // if the expression is inside a macro. diff --git a/clang/lib/3C/ConstraintVariables.cpp b/clang/lib/3C/ConstraintVariables.cpp index 89aa03eb5a9e..3e984cef8cf2 100644 --- a/clang/lib/3C/ConstraintVariables.cpp +++ b/clang/lib/3C/ConstraintVariables.cpp @@ -59,7 +59,7 @@ PointerVariableConstraint *PointerVariableConstraint::getWildPVConstraint( // number of atoms it needs to have. Fortunately, it's already WILD, so any // constraint made with it will force the other constraint to WILD, safely // handling assignment between incompatible pointer depths. - WildPVC->GenericIndex = 0; + WildPVC->InferredGenericIndex = 0; return WildPVC; } @@ -127,7 +127,9 @@ PointerVariableConstraint::PointerVariableConstraint( SrcHasItype(Ot->SrcHasItype), ItypeStr(Ot->ItypeStr), PartOfFuncPrototype(Ot->PartOfFuncPrototype), Parent(Ot), BoundsAnnotationStr(Ot->BoundsAnnotationStr), - GenericIndex(Ot->GenericIndex), IsZeroWidthArray(Ot->IsZeroWidthArray), + SourceGenericIndex(Ot->SourceGenericIndex), + InferredGenericIndex(Ot->InferredGenericIndex), + IsZeroWidthArray(Ot->IsZeroWidthArray), IsTypedef(Ot->IsTypedef), TDT(Ot->TDT), TypedefString(Ot->TypedefString), TypedefLevelInfo(Ot->TypedefLevelInfo), IsVoidPtr(Ot->IsVoidPtr) { // These are fields of the super class Constraint Variable @@ -140,15 +142,15 @@ PointerVariableConstraint::PointerVariableConstraint(DeclaratorDecl *D, ProgramInfo &I, const ASTContext &C) : PointerVariableConstraint(D->getType(), D, std::string(D->getName()), I, - C, nullptr, -1, false, D->getTypeSourceInfo()) { -} + C, nullptr, -1, false, false, + D->getTypeSourceInfo()) {} PointerVariableConstraint::PointerVariableConstraint(TypedefDecl *D, ProgramInfo &I, const ASTContext &C) : PointerVariableConstraint(D->getUnderlyingType(), nullptr, D->getNameAsString(), I, C, nullptr, -1, false, - D->getTypeSourceInfo()) {} + false, D->getTypeSourceInfo()) {} PointerVariableConstraint::PointerVariableConstraint(Expr *E, ProgramInfo &I, const ASTContext &C) @@ -202,6 +204,7 @@ class TypedefLevelFinder : public RecursiveASTVisitor { PointerVariableConstraint::PointerVariableConstraint( const QualType &QT, DeclaratorDecl *D, std::string N, ProgramInfo &I, const ASTContext &C, std::string *InFunc, int ForceGenericIndex, + bool PotentialGeneric, bool VarAtomForChecked, TypeSourceInfo *TSInfo, const QualType &ITypeT) : ConstraintVariable(ConstraintVariable::PointerVariable, tyToStr(QT.getTypePtr()), N), @@ -296,9 +299,9 @@ PointerVariableConstraint::PointerVariableConstraint( TypedefLevelInfo = TypedefLevelFinder::find(QTy); if (ForceGenericIndex >= 0) { - GenericIndex = ForceGenericIndex; + SourceGenericIndex = ForceGenericIndex; } else { - GenericIndex = -1; + SourceGenericIndex = -1; // This makes a lot of assumptions about how the AST will look, and limits // it to one level. // TODO: Enhance TypedefLevelFinder to get this info. @@ -307,10 +310,11 @@ PointerVariableConstraint::PointerVariableConstraint( if (auto *TypdefTy = dyn_cast_or_null(PtrTy)) { const auto *Tv = dyn_cast(TypdefTy->desugar()); if (Tv) - GenericIndex = Tv->GetIndex(); + SourceGenericIndex = Tv->GetIndex(); } } } + InferredGenericIndex = SourceGenericIndex; bool VarCreated = false; bool IsArr = false; @@ -506,11 +510,17 @@ PointerVariableConstraint::PointerVariableConstraint( // Get a string representing the type without pointer and array indirection. BaseType = extractBaseType(D, TSInfo, QT, Ty, C); - IsVoidPtr = isTypeHasVoid(QT); - bool IsWild = !getIsGeneric() && (isVarArgType(BaseType) || IsVoidPtr); + // check if the type is some depth of pointers to void + // TODO: is this what the field should mean? do we want to include other + // indirection options like arrays? + // https://github.com/correctcomputation/checkedc-clang/issues/648 + IsVoidPtr = QT->isPointerType() && isTypeHasVoid(QT); + // varargs are always wild, as are void pointers that are not generic + bool IsWild = isVarArgType(BaseType) || + (!(PotentialGeneric || isGeneric()) && IsVoidPtr); if (IsWild) { std::string Rsn = - IsVoidPtr ? "Default void* type" : "Default Var arg list type"; + IsVoidPtr ? VOID_TYPE_REASON : "Default Var arg list type"; // TODO: Github issue #61: improve handling of types for variable arguments. for (const auto &V : Vars) if (VarAtom *VA = dyn_cast(V)) @@ -754,6 +764,17 @@ PointerVariableConstraint::mkString(Constraints &CS, EmittedName = true; uint32_t TypeIdx = 0; + // If we've set a GenericIndex for void, it means we're converting it into + // a generic function so give it the default generic type name. + // Add more type names below if we expect to use a lot. + std::string BaseTypeName = BaseType; + if (InferredGenericIndex > -1 && isVoidPtr() && + isSolutionChecked(CS.getVariables())) { + assert(InferredGenericIndex < 3 + && "Trying to use an unexpected type variable name"); + BaseTypeName = std::begin({"T","U","V"})[InferredGenericIndex]; + } + auto It = Vars.begin(); auto I = 0; // Skip over first pointer level if only emitting pointee string. @@ -781,9 +802,9 @@ PointerVariableConstraint::mkString(Constraints &CS, Atom::AtomKind K = C->getKind(); - // If this is not an itype + // If this is not an itype or generic // make this wild as it can hold any pointer type. - if (!ForItype && BaseType == "void") + if (!ForItype && InferredGenericIndex == -1 && isVoidPtr()) K = Atom::A_Wild; if (PrevArr && ArrSizes.at(TypeIdx).first != O_SizedArray && !EmittedName) { @@ -851,9 +872,9 @@ PointerVariableConstraint::mkString(Constraints &CS, getQualString(TypeIdx, FptrInner); } else { if (!EmittedBase) { - assert(!BaseType.empty()); + assert(!BaseTypeName.empty()); EmittedBase = true; - Ss << BaseType << " "; + Ss << BaseTypeName << " "; } Ss << "*"; getQualString(TypeIdx, Ss); @@ -907,7 +928,7 @@ PointerVariableConstraint::mkString(Constraints &CS, auto Name = TypedefLevelInfo.TypedefName; Ss << Buf.str() << Name; } else { - Ss << BaseType; + Ss << BaseTypeName; } } @@ -1013,6 +1034,7 @@ FunctionVariableConstraint::FunctionVariableConstraint( auto PSL = PersistentSourceLoc::mkPSL(D, *TmpCtx); FileName = PSL.getFileName(); IsFunctionPtr = false; + TypeParams = FD->getNumTypeVars(); } bool ReturnHasItype = false; @@ -1057,11 +1079,9 @@ FunctionVariableConstraint::FunctionVariableConstraint( ParmVD = FTL.getParam(J); std::string PName = ParmVD ? ParmVD->getName().str() : ""; - auto ParamVar = FVComponentVariable(QT, ITypeT, ParmVD, PName, I, Ctx, &N, - ParamHasItype); - int GenericIdx = ParamVar.ExternalConstraint->getGenericIndex(); - if (GenericIdx >= 0) - TypeParams = std::max(TypeParams, GenericIdx + 1); + auto ParamVar = + FVComponentVariable(QT, ITypeT, ParmVD, PName, I, Ctx, + &N, true, ParamHasItype); ParamVars.push_back(ParamVar); } @@ -1075,11 +1095,45 @@ FunctionVariableConstraint::FunctionVariableConstraint( } // ConstraintVariable for the return. - ReturnVar = - FVComponentVariable(RT, RTIType, D, RETVAR, I, Ctx, &N, ReturnHasItype); - int GenericIdx = ReturnVar.ExternalConstraint->getGenericIndex(); - if (GenericIdx >= 0) - TypeParams = std::max(TypeParams, GenericIdx + 1); + ReturnVar = FVComponentVariable(RT, RTIType, D, RETVAR, I, Ctx, + &N, true, ReturnHasItype); + + // Locate the void* params that were not marked wild above + // to either do so or use as generics + std::vector Voids; + auto Ext = ReturnVar.ExternalConstraint; + if(Ext->isVoidPtr() && !Ext->isGeneric()) { + Voids.push_back(-1); + } + for(unsigned i=0; i < ParamVars.size();i++) { + auto Ext = ParamVars[i].ExternalConstraint; + if(Ext->isVoidPtr() && !Ext->isGeneric()) { + Voids.push_back(i); + } + } + // Strategy: If there's one void*, turn this into a generic function. + // Otherwise, we need to constraint the void*'s to wild + // Exclude unwritables, external functions, + // function pointers, and source generics for now + // Also exclude params that are checked or have itypes + bool ConvertFunc = canWrite(FileName) && hasBody() && !IsFunctionPtr && + TypeParams == 0 && Voids.size() == 1; + bool DidConvert = false; + auto &CS = I.getConstraints(); + for(int idx : Voids) { + FVComponentVariable *FVCV = idx == -1 ? &ReturnVar : &ParamVars[idx]; + auto Ext = FVCV->ExternalConstraint; + if (ConvertFunc && !Ext->isOriginallyChecked() && !Ext->srcHasItype() && + Ext->getCvars().size() == 1) { + FVCV->setGenericIndex(0); + DidConvert = true; + } else { + if (!Ext->isOriginallyChecked()) { + Ext->constrainToWild(CS, VOID_TYPE_REASON); + } + } + } + if (DidConvert) TypeParams = 1; } void FunctionVariableConstraint::constrainToWild(Constraints &CS, @@ -1287,6 +1341,9 @@ bool PointerVariableConstraint::anyChanges(const EnvironmentMap &E) const { PtrChanged |= SrcType != SolutionType; } + // also check if we've make this generic + PtrChanged |= isGenericChanged(); + if (FV) PtrChanged |= FV->anyChanges(E); @@ -1417,7 +1474,7 @@ bool PointerVariableConstraint::solutionEqualTo(Constraints &CS, if (CV != nullptr) { if (const auto *PV = dyn_cast(CV)) { auto &OthCVars = PV->Vars; - if (getIsGeneric() || PV->getIsGeneric() || + if (isGeneric() || PV->isGeneric() || Vars.size() == OthCVars.size()) { Ret = true; @@ -1800,8 +1857,8 @@ void constrainConsVarGeq(ConstraintVariable *LHS, ConstraintVariable *RHS, // Only generate constraint if LHS is not a base type. if (CLHS.size() != 0) { if (CLHS.size() == CRHS.size() || - (CLHS.size() < CRHS.size() && PCLHS->getIsGeneric()) || - (CLHS.size() > CRHS.size() && PCRHS->getIsGeneric())) { + (CLHS.size() < CRHS.size() && PCLHS->isGeneric()) || + (CLHS.size() > CRHS.size() && PCRHS->isGeneric())) { unsigned Min = std::min(CLHS.size(), CRHS.size()); for (unsigned N = 0; N < Min; N++) { Atom *IAtom = PCLHS->getAtom(N, CS); @@ -1932,8 +1989,10 @@ void PointerVariableConstraint::mergeDeclaration(ConstraintVariable *FromCV, if (!From->BoundsAnnotationStr.empty()) BoundsAnnotationStr = From->BoundsAnnotationStr; - if (From->GenericIndex >= 0) - GenericIndex = From->GenericIndex; + if (From->SourceGenericIndex >= 0) { + SourceGenericIndex = From->SourceGenericIndex; + InferredGenericIndex = From->InferredGenericIndex; + } if (FV) { assert(From->FV); FV->mergeDeclaration(From->FV, Info, ReasonFailed); @@ -2122,14 +2181,18 @@ FVComponentVariable::FVComponentVariable(const QualType &QT, clang::DeclaratorDecl *D, std::string N, ProgramInfo &I, const ASTContext &C, - std::string *InFunc, bool HasItype) { + std::string *InFunc, + bool PotentialGeneric, + bool HasItype) { ExternalConstraint = - new PVConstraint(QT, D, N, I, C, InFunc, -1, HasItype, nullptr, ITypeT); + new PVConstraint(QT, D, N, I, C, InFunc, -1, PotentialGeneric, HasItype, + nullptr, ITypeT); if (!HasItype && QT->isVoidPointerType()) { InternalConstraint = ExternalConstraint; } else { InternalConstraint = - new PVConstraint(QT, D, N, I, C, InFunc, -1, HasItype, nullptr, ITypeT); + new PVConstraint(QT, D, N, I, C, InFunc, -1, PotentialGeneric, HasItype, + nullptr, ITypeT); bool EquateChecked = QT->isVoidPointerType(); linkInternalExternal(I, EquateChecked); } @@ -2149,7 +2212,7 @@ void FVComponentVariable::equateWithItype(ProgramInfo &I, PersistentSourceLoc *PSL) const { Constraints &CS = I.getConstraints(); const std::string ReasonUnchangeable2 = - (ReasonUnchangeable.empty() && ExternalConstraint->getIsGeneric()) + (ReasonUnchangeable.empty() && ExternalConstraint->isGeneric()) ? "Internal constraint for generic function declaration, " "for which 3C currently does not support re-solving." : ReasonUnchangeable; diff --git a/clang/lib/3C/DeclRewriter.cpp b/clang/lib/3C/DeclRewriter.cpp index c2c4a085a9c2..5ef3bbd22dd0 100644 --- a/clang/lib/3C/DeclRewriter.cpp +++ b/clang/lib/3C/DeclRewriter.cpp @@ -561,6 +561,11 @@ bool FunctionDeclBuilder::VisitFunctionDecl(FunctionDecl *FD) { bool RewriteParams = false; bool RewriteReturn = false; + // RewriteGeneric is similar to the above, but we need to further check + // if the potential generic variables were set to wild by the constraint + // resolver. In that case don't rewrite. + bool RewriteGeneric = false; + bool DeclIsTypedef = false; if (TypeSourceInfo *TS = FD->getTypeSourceInfo()) { // This still could possibly be a typedef type if TS was NULL. @@ -571,10 +576,18 @@ bool FunctionDeclBuilder::VisitFunctionDecl(FunctionDecl *FD) { DeclIsTypedef = isa(TS->getType()); } + // If we've made this generic we need add "_For_any" or "_Itype_for_any" + if (FDConstraint->getGenericParams() > 0 + && !FD->isGenericFunction() && !FD->isItypeGenericFunction()) + RewriteGeneric = true; + // Get rewritten parameter variable declarations. Try to use // the source for as much as possible. std::vector ParmStrs; + // Needed to distinguish between Itype_for_any and For_any + bool ProtoHasItype = false; + // Typedefs must be expanded for now, so allow interpret them as rewritable // by ignoring their special case code. // See the FIXME below for more info. @@ -588,9 +601,10 @@ bool FunctionDeclBuilder::VisitFunctionDecl(FunctionDecl *FD) { const FVComponentVariable *CV = FDConstraint->getCombineParam(I); std::string Type, IType; this->buildDeclVar(CV, PVDecl, Type, IType, - PVDecl->getQualifiedNameAsString(), RewriteParams, - RewriteReturn, FD->isStatic()); + PVDecl->getQualifiedNameAsString(), RewriteGeneric, + RewriteParams, RewriteReturn, FD->isStatic()); ParmStrs.push_back(Type + IType); + ProtoHasItype |= !IType.empty(); } } else if (FDConstraint->numParams() != 0) { // lacking params but the constraint has them: mirror the constraint @@ -598,9 +612,10 @@ bool FunctionDeclBuilder::VisitFunctionDecl(FunctionDecl *FD) { ParmVarDecl *PVDecl = nullptr; const FVComponentVariable *CV = FDConstraint->getCombineParam(I); std::string Type, IType; - this->buildDeclVar(CV, PVDecl, Type, IType, "", RewriteParams, - RewriteReturn, FD->isStatic()); + this->buildDeclVar(CV, PVDecl, Type, IType, "", RewriteGeneric, + RewriteParams, RewriteReturn, FD->isStatic()); ParmStrs.push_back(Type + IType); + ProtoHasItype |= !IType.empty(); // FIXME: when the above FIXME is changed this condition will always // be true. This is correct, always rewrite if there were no params // in source but they exist in the constraint variable. @@ -621,7 +636,15 @@ bool FunctionDeclBuilder::VisitFunctionDecl(FunctionDecl *FD) { // For now we still need to check if this needs rewriting, see FIXME below // if (!DeclIsTypedef) this->buildDeclVar(FDConstraint->getCombineReturn(), FD, ReturnVar, ItypeStr, - "", RewriteParams, RewriteReturn, FD->isStatic()); + "", RewriteGeneric, RewriteParams, + RewriteReturn, FD->isStatic()); + + ProtoHasItype |= !ItypeStr.empty(); + + // Generic forany and return are in the same rewrite location, so + // we must rewrite the return if rewriting generic + if (RewriteGeneric) + RewriteReturn = true; // If the return is a function pointer, we need to rewrite the whole // declaration even if no actual changes were made to the parameters because @@ -631,6 +654,13 @@ bool FunctionDeclBuilder::VisitFunctionDecl(FunctionDecl *FD) { if (FD->getReturnType()->isFunctionPointerType() && RewriteReturn) RewriteParams = true; + // If we're making this into a generic function, we'll + // rewrite parameters in case there's an itype in there that won't trigger + // a normal rewrite. Temp fix for #678 in generics case. + if (RewriteGeneric) { + RewriteParams = true; + } + // If the function is declared using a typedef for the function type, then we // need to rewrite parameters and the return if either would have been // rewritten. What this does is expand the typedef to the full function type @@ -645,11 +675,37 @@ bool FunctionDeclBuilder::VisitFunctionDecl(FunctionDecl *FD) { RewriteReturn = true; } + // Mirrors the check above that sets RewriteGeneric to true. + // If we've decided against making this generic, remove the generic params + // so later rewrites (of typeparams) don't happen + if (!RewriteGeneric && FDConstraint->getGenericParams() > 0 + && !FD->isGenericFunction() && !FD->isItypeGenericFunction()) + FDConstraint->resetGenericParams(); + + // If this was an itype but is now checked, we'll be changing + // "_Itype_for_any" to "_For_any" + if (!RewriteGeneric && FD->isItypeGenericFunction() && !ProtoHasItype) { + RewriteGeneric = true; + RewriteReturn = true; + } + // Combine parameter and return variables rewritings into a single rewriting // for the entire function declaration. std::string NewSig = ""; + if (RewriteGeneric) { + if (ProtoHasItype) + NewSig += "_Itype_for_any(T"; + else + NewSig += "_For_any(T"; + for (int i = 0; i < FDConstraint->getGenericParams() - 1; i++) { + assert(i < 2 && + "Need an unexpected number of type variables"); + NewSig += std::begin({",U",",V"})[i]; + } + NewSig += ") "; + } if (RewriteReturn) - NewSig = getStorageQualifierString(FD) + ReturnVar; + NewSig += getStorageQualifierString(FD) + ReturnVar; if (RewriteReturn && RewriteParams) NewSig += FDConstraint->getName(); @@ -674,7 +730,8 @@ bool FunctionDeclBuilder::VisitFunctionDecl(FunctionDecl *FD) { if (RewriteReturn || RewriteParams) { RewriteThese.insert(std::make_pair( FD, - new FunctionDeclReplacement(FD, NewSig, RewriteReturn, RewriteParams))); + new FunctionDeclReplacement(FD, NewSig, RewriteReturn, + RewriteParams, RewriteGeneric))); } return true; @@ -711,8 +768,8 @@ void FunctionDeclBuilder::buildItypeDecl(PVConstraint *Defn, void FunctionDeclBuilder::buildDeclVar(const FVComponentVariable *CV, DeclaratorDecl *Decl, std::string &Type, std::string &IType, std::string UseName, - bool &RewriteParm, bool &RewriteRet, - bool StaticFunc) { + bool &RewriteGen, bool &RewriteParm, + bool &RewriteRet, bool StaticFunc) { bool CheckedSolution = CV->hasCheckedSolution(Info.getConstraints()); bool ItypeSolution = CV->hasItypeSolution(Info.getConstraints()); @@ -727,7 +784,12 @@ void FunctionDeclBuilder::buildDeclVar(const FVComponentVariable *CV, return; } - // If the type of the pointer hasn't changed, then neither of the above + // Don't add generics if one of the potential generic params is wild, + // even if it could have an itype + if (!CheckedSolution && CV->getExternal()->isGenericChanged()) + RewriteGen = false; + +// If the type of the pointer hasn't changed, then neither of the above // branches will be taken, but it's still possible for the bounds of an array // pointer to change. if (ABRewriter.hasNewBoundsString(CV->getExternal(), Decl)) { diff --git a/clang/lib/3C/ProgramInfo.cpp b/clang/lib/3C/ProgramInfo.cpp index e7beac76caed..c10e4fc43b74 100644 --- a/clang/lib/3C/ProgramInfo.cpp +++ b/clang/lib/3C/ProgramInfo.cpp @@ -440,14 +440,14 @@ bool ProgramInfo::link() { const FVComponentVariable *Ret = G->getCombineReturn(); Ret->getInternal()->constrainToWild(CS, Rsn); if (!Ret->getExternal()->srcHasItype() && - !Ret->getExternal()->getIsGeneric()) + !Ret->getExternal()->isGeneric()) Ret->getExternal()->constrainToWild(CS, Rsn); for (unsigned I = 0; I < G->numParams(); I++) { const FVComponentVariable *Param = G->getCombineParam(I); Param->getInternal()->constrainToWild(CS, Rsn); if (!Param->getExternal()->srcHasItype() && - !Param->getExternal()->getIsGeneric()) + !Param->getExternal()->isGeneric()) Param->getExternal()->constrainToWild(CS, Rsn); } } @@ -476,10 +476,10 @@ bool ProgramInfo::link() { if (!G->hasBody()) { - if (!G->getExternalReturn()->getIsGeneric()) + if (!G->getExternalReturn()->isGeneric()) G->getExternalReturn()->constrainToWild(CS, Rsn); for (unsigned I = 0; I < G->numParams(); I++) - if (!G->getExternalParam(I)->getIsGeneric()) + if (!G->getExternalParam(I)->isGeneric()) G->getExternalParam(I)->constrainToWild(CS, Rsn); } } @@ -577,31 +577,6 @@ ProgramInfo::insertNewFVConstraint(FunctionDecl *FD, FVConstraint *NewC, return (*Map)[FuncName]; } -void ProgramInfo::specialCaseVarIntros(ValueDecl *D, ASTContext *Context) { - // Special-case for va_list, constrain to wild. - bool IsGeneric = false; - PVConstraint *PVC = nullptr; - - CVarOption CVOpt = getVariable(D, Context); - if (CVOpt.hasValue()) { - ConstraintVariable &CV = CVOpt.getValue(); - PVC = dyn_cast(&CV); - } - - if (isa(D)) - IsGeneric = PVC && PVC->getIsGeneric(); - bool IsVarArg = isVarArgType(D->getType().getAsString()); - bool IsVoidPtr = hasVoidType(D) && !IsGeneric; - if (IsVarArg || IsVoidPtr) { - // Set the reason for making this variable WILD. - PersistentSourceLoc PL = PersistentSourceLoc::mkPSL(D, *Context); - std::string Rsn = IsVoidPtr ? "Variable type void." - : "Variable type is va_list."; - if (PVC != nullptr) - PVC->constrainToWild(CS, Rsn, &PL); - } -} - // For each pointer type in the declaration of D, add a variable to the // constraint system for that pointer type. void ProgramInfo::addVariable(clang::DeclaratorDecl *D, @@ -686,7 +661,6 @@ void ProgramInfo::addVariable(clang::DeclaratorDecl *D, // Constraint variable is stored on the parent function, so we need to // constrain to WILD even if we don't end up storing this in the map. constrainWildIfMacro(PVExternal, PVD->getLocation()); - specialCaseVarIntros(PVD, AstContext); // If this is "main", constrain its argv parameter to a nested arr if (AllTypes && FuncName == "main" && FD->isGlobal() && I == 1) { PVInternal->constrainOuterTo(CS, CS.getArr()); @@ -722,7 +696,6 @@ void ProgramInfo::addVariable(clang::DeclaratorDecl *D, } GlobalVariableSymbols[VarName].insert(P); } - specialCaseVarIntros(D, AstContext); } } else if (FieldDecl *FlD = dyn_cast(D)) { @@ -732,7 +705,6 @@ void ProgramInfo::addVariable(clang::DeclaratorDecl *D, unifyIfTypedef(Ty, *AstContext, FlD, P); NewCV = P; NewCV->setValidDecl(); - specialCaseVarIntros(D, AstContext); } } else llvm_unreachable("unknown decl type"); @@ -1148,16 +1120,18 @@ void ProgramInfo::computePtrLevelStats() { } void ProgramInfo::setTypeParamBinding(CallExpr *CE, unsigned int TypeVarIdx, - ConstraintVariable *CV, ASTContext *C) { + ConstraintVariable *CV, + ConstraintVariable *Ident, + ASTContext *C) { auto Key = getExprKey(CE, C); auto CallMap = TypeParamBindings[Key]; if (CallMap.find(TypeVarIdx) == CallMap.end()) { - TypeParamBindings[Key][TypeVarIdx] = CV; + TypeParamBindings[Key][TypeVarIdx] = TypeParamConstraint(CV,Ident); } else { // If this CE/idx is at the same location, it's in a macro, // so mark it as inconsistent. - TypeParamBindings[Key][TypeVarIdx] = nullptr; + TypeParamBindings[Key][TypeVarIdx] = TypeParamConstraint(nullptr,nullptr); } } diff --git a/clang/lib/3C/RewriteUtils.cpp b/clang/lib/3C/RewriteUtils.cpp index 5f1128957b33..cc42c548a45f 100644 --- a/clang/lib/3C/RewriteUtils.cpp +++ b/clang/lib/3C/RewriteUtils.cpp @@ -365,23 +365,31 @@ class TypeArgumentAdder : public clang::RecursiveASTVisitor { : Context(C), Info(I), Writer(R) {} bool VisitCallExpr(CallExpr *CE) { - if (isa_and_nonnull(CE->getCalleeDecl())) { + if (auto *FD = dyn_cast_or_null(CE->getCalleeDecl())) { // If the function call already has type arguments, we'll trust that // they're correct and not add anything else. if (typeArgsProvided(CE)) return true; + // If the function is not generic, we have nothing to do. + // This could happen even if it has type param binding if we + // reset generics because of wildness + if (Info.getFuncConstraint(FD,Context)->getGenericParams() == 0 && + !FD->isItypeGenericFunction()) + return true; + if (Info.hasTypeParamBindings(CE, Context)) { // Construct a string containing concatenation of all type arguments for // the function call. std::string TypeParamString; bool AllInconsistent = true; for (auto Entry : Info.getTypeParamBindings(CE, Context)) - if (Entry.second != nullptr) { + if (Entry.second.isConsistent()) { AllInconsistent = false; - std::string TyStr = Entry.second->mkString( - Info.getConstraints(), - MKSTRING_OPTS(EmitName = false, EmitPointee = true)); + std::string TyStr = Entry.second.getConstraint( + Info.getConstraints().getVariables() + )->mkString(Info.getConstraints(), MKSTRING_OPTS( + EmitName = false, EmitPointee = true)); if (TyStr.back() == ' ') TyStr.pop_back(); TypeParamString += TyStr + ","; @@ -420,7 +428,8 @@ class TypeArgumentAdder : public clang::RecursiveASTVisitor { }; SourceRange FunctionDeclReplacement::getSourceRange(SourceManager &SM) const { - SourceLocation Begin = RewriteReturn ? getDeclBegin(SM) : getParamBegin(SM); + SourceLocation Begin = RewriteGeneric ? getDeclBegin(SM) : + (RewriteReturn ? getReturnBegin(SM) : getParamBegin(SM)); SourceLocation End = RewriteParams ? getDeclEnd(SM) : getReturnEnd(SM); // Begin can be equal to End if the SourceRange only contains one token. assert("Invalid FunctionDeclReplacement SourceRange!" && @@ -433,6 +442,22 @@ SourceLocation FunctionDeclReplacement::getDeclBegin(SourceManager &SM) const { return Begin; } +SourceLocation FunctionDeclReplacement::getReturnBegin(SourceManager &SM) const { + // TODO: more accuracy + // This code gets the point after a modifier like "static" + // But currently, that leads to multiple "static"s + // SourceRange ReturnSource = Decl->getReturnTypeSourceRange(); + // if (ReturnSource.isValid()) + // return ReturnSource.getBegin(); + + // Invalid return means we're in a macro or typedef, so just get the + // starting point. We may overwrite a _For_any(..), but those only + // exist in partially converted code, so we're relying on the user + // to have it correct anyway. + + return getDeclBegin(SM); +} + SourceLocation FunctionDeclReplacement::getParamBegin(SourceManager &SM) const { FunctionTypeLoc FTypeLoc = getFunctionTypeLoc(Decl); // If we can't get a FunctionTypeLoc instance, then we'll guess that the diff --git a/clang/lib/3C/TypeVariableAnalysis.cpp b/clang/lib/3C/TypeVariableAnalysis.cpp index 63c2d5ce11c8..3145b149e20c 100644 --- a/clang/lib/3C/TypeVariableAnalysis.cpp +++ b/clang/lib/3C/TypeVariableAnalysis.cpp @@ -31,7 +31,8 @@ void TypeVariableEntry::setTypeParamConsVar(ConstraintVariable *CV) { TypeParamConsVar = CV; } -void TypeVariableEntry::updateEntry(QualType Ty, CVarSet &CVs) { +void TypeVariableEntry::updateEntry(QualType Ty, CVarSet &CVs, + ConstraintVariable *IdentCV) { if (!(Ty->isArrayType() || Ty->isPointerType())) { // We need to have a pointer or an array type for an instantiation to make // sense. Anything else is treated as inconsistent. @@ -45,9 +46,20 @@ void TypeVariableEntry::updateEntry(QualType Ty, CVarSet &CVs) { getType()->getPointeeOrArrayElementType() != PtrTy)) IsConsistent = false; } + // If these came from two different type params originally and are both + // passed to the same type param, we have no way of knowing if they were + // the same and in general they will not always be, so this must be marked + // inconsistent. + if (auto PVC1 = dyn_cast_or_null(GenArgumentCV)) + if (auto PVC2 = dyn_cast_or_null(IdentCV)) + if (PVC1->getGenericIndex() != PVC2->getGenericIndex()) + IsConsistent = false; + // Record new constraints for the entry. These are used even when the variable // is not consistent. insertConstraintVariables(CVs); + if (!GenArgumentCV) + GenArgumentCV = IdentCV; } ConstraintVariable *TypeVariableEntry::getTypeParamConsVar() { @@ -57,6 +69,12 @@ ConstraintVariable *TypeVariableEntry::getTypeParamConsVar() { return TypeParamConsVar; } +ConstraintVariable *TypeVariableEntry::getGenArgCV() { + assert("Accessing constraint variable for inconsistent Type Variable." && + IsConsistent); + return GenArgumentCV; +} + QualType TypeVariableEntry::getType() { assert("Accessing Type for inconsistent Type Variable." && IsConsistent); return TyVarType; @@ -107,8 +125,19 @@ bool TypeVarVisitor::VisitCallExpr(CallExpr *CE) { const int TyIdx = FVCon->getExternalParam(I)->getGenericIndex(); if (TyIdx >= 0) { Expr *Uncast = A->IgnoreImpCasts(); - std::set CVs = - CR.getExprConstraintVarsSet(Uncast); + std::set CVs = CR.getExprConstraintVarsSet(Uncast); + if (auto *DRE = dyn_cast(Uncast)){ + CVarOption Var = Info.getVariable(DRE->getFoundDecl(),Context); + if (Var.hasValue()) + if (PVConstraint *GenVar = + dyn_cast(&Var.getValue())) + if (GenVar->isGeneric()) { + insertBinding(CE,TyIdx,Uncast->getType(), + CVs,GenVar); + ++I; + continue; + } + } insertBinding(CE, TyIdx, Uncast->getType(), CVs); } ++I; @@ -123,7 +152,7 @@ bool TypeVarVisitor::VisitCallExpr(CallExpr *CE) { FD->getNameAsString() + "_tyarg_" + std::to_string(TVEntry.first); PVConstraint *P = new PVConstraint(TVEntry.second.getType(), nullptr, Name, Info, - *Context, nullptr, TVEntry.first); + *Context, nullptr); // Constrain this variable GEQ the function arguments using the type // variable so if any of them are wild, the type argument will also be @@ -161,7 +190,8 @@ bool TypeVarVisitor::VisitCallExpr(CallExpr *CE) { // the exact type variable is identified by the call expression where it is // used and the index of the type variable type in the function declaration. void TypeVarVisitor::insertBinding(CallExpr *CE, const int TyIdx, - clang::QualType Ty, CVarSet &CVs) { + clang::QualType Ty, CVarSet &CVs, + ConstraintVariable *IdentCV) { // if we need to rewrite it but can't (macro, etc), it isn't safe bool ForceInconsistent = !typeArgsProvided(CE) && @@ -173,11 +203,12 @@ void TypeVarVisitor::insertBinding(CallExpr *CE, const int TyIdx, auto &CallTypeVarMap = TVMap[CE]; if (CallTypeVarMap.find(TyIdx) == CallTypeVarMap.end()) { // If the type variable hasn't been seen before, add it to the map. - TypeVariableEntry TVEntry = TypeVariableEntry(Ty, CVs, ForceInconsistent); + TypeVariableEntry TVEntry = TypeVariableEntry(Ty, CVs, ForceInconsistent, + IdentCV); CallTypeVarMap[TyIdx] = TVEntry; } else { // Otherwise, update entry with new type and constraints. - CallTypeVarMap[TyIdx].updateEntry(Ty, CVs); + CallTypeVarMap[TyIdx].updateEntry(Ty, CVs, IdentCV); } } @@ -203,9 +234,11 @@ void TypeVarVisitor::setProgramInfoTypeVars() { if (TVCallEntry.second.getIsConsistent()) Info.setTypeParamBinding(TVEntry.first, TVCallEntry.first, TVCallEntry.second.getTypeParamConsVar(), + TVCallEntry.second.getGenArgCV(), Context); else - Info.setTypeParamBinding(TVEntry.first, TVCallEntry.first, nullptr, + Info.setTypeParamBinding(TVEntry.first, TVCallEntry.first, + nullptr, nullptr, Context); } } diff --git a/clang/lib/3C/Utils.cpp b/clang/lib/3C/Utils.cpp index 30ee345f8607..3dada2ad08df 100644 --- a/clang/lib/3C/Utils.cpp +++ b/clang/lib/3C/Utils.cpp @@ -304,8 +304,7 @@ bool hasVoidType(clang::ValueDecl *D) { return isTypeHasVoid(D->getType()); } // return D->isPointerType() == S->isPointerType(); //} -static bool castCheck(clang::QualType DstType, clang::QualType SrcType, - bool AllowVoidCast) { +static bool castCheck(clang::QualType DstType, clang::QualType SrcType) { // Check if both types are same. if (SrcType == DstType) @@ -321,9 +320,8 @@ static bool castCheck(clang::QualType DstType, clang::QualType SrcType, // Both are pointers? check their pointee if (SrcPtrTypePtr && DstPtrTypePtr) { - return (AllowVoidCast && SrcPtrTypePtr->isVoidPointerType()) || - castCheck(DstPtrTypePtr->getPointeeType(), - SrcPtrTypePtr->getPointeeType(), AllowVoidCast); + return castCheck(DstPtrTypePtr->getPointeeType(), + SrcPtrTypePtr->getPointeeType()); } if (SrcPtrTypePtr || DstPtrTypePtr) @@ -337,12 +335,11 @@ static bool castCheck(clang::QualType DstType, clang::QualType SrcType, return false; for (unsigned I = 0; I < SrcFnType->getNumParams(); I++) - if (!castCheck(SrcFnType->getParamType(I), DstFnType->getParamType(I), - false)) + if (!castCheck(SrcFnType->getParamType(I), + DstFnType->getParamType(I))) return false; - return castCheck(SrcFnType->getReturnType(), DstFnType->getReturnType(), - false); + return castCheck(SrcFnType->getReturnType(), DstFnType->getReturnType()); } // If both are not scalar types? Then the types must be exactly same. @@ -366,7 +363,7 @@ bool isCastSafe(clang::QualType DstType, clang::QualType SrcType) { dyn_cast(DstTypePtr); if (!DstPtrTypePtr) // Safe to cast to a non-pointer. return true; - return castCheck(DstType, SrcType, true); + return castCheck(DstType, SrcType); } bool canWrite(const std::string &FilePath) { diff --git a/clang/test/3C/cast.c b/clang/test/3C/cast.c index c94459f1abc6..deba20691f52 100644 --- a/clang/test/3C/cast.c +++ b/clang/test/3C/cast.c @@ -23,6 +23,26 @@ void bar(void) { /*int *e = (int *)(a+5);*/ } +// malloc and generics are exceptions to our policy of unsafe void* casts +#include +_Itype_for_any(T) +void *ident(void *i : itype(_Ptr)) +: itype(_Ptr) { return i;}; +void test_generic_casts(void) { + int *ptr_cast = (int *)malloc(3 * sizeof(int)); + // CHECK_ALL: _Ptr ptr_cast = (_Ptr)malloc(3 * sizeof(int)); + int *ptr_nocast = malloc(3 * sizeof(int)); + // CHECK_ALL: _Ptr ptr_nocast = malloc(3 * sizeof(int)); + char *ptr_badcast = (int *)malloc(3 * sizeof(int)); + // CHECK_ALL: char *ptr_badcast = (int *)malloc(3 * sizeof(int)); + int *ptr_custom = (int *)ident(ptr_cast); + // CHECK_ALL: _Ptr ptr_custom = (_Ptr)ident(ptr_cast); + + int *ptr_cast_c = (int *)calloc(3, sizeof(int)); + // CHECK_ALL: _Ptr ptr_cast_c = (_Ptr)calloc(3, sizeof(int)); + int *ptr_cast_r = (int *)realloc(ptr_cast_c, 3 * sizeof(int)); + // CHECK_ALL: _Ptr ptr_cast_r = (_Ptr)realloc(ptr_cast_c, 3 * sizeof(int)); +} // Check that casts to generics use the local type variables int *mk_ptr(void); diff --git a/clang/test/3C/dont_rewrite_cflags.c b/clang/test/3C/dont_rewrite_cflags.c new file mode 100644 index 000000000000..7992230f7f55 --- /dev/null +++ b/clang/test/3C/dont_rewrite_cflags.c @@ -0,0 +1,10 @@ +// This tests a case of unwritable files being written under specific flags +// to the compiler, as seen in the run command below. It was originally noticed +// converting a header file to use generic variables. The solution is to check +// again that your change is in a writable file, as seen here: +// https://github.com/correctcomputation/checkedc-clang/commit/b6d8ed1dcfc9d4174dc87b1b34a5ff6def3f2d5c + +// RUN: 3c -base-dir=%S %s -- -O2 -D_FORTIFY_SOURCE=2 + +#include + diff --git a/clang/test/3C/generalize.c b/clang/test/3C/generalize.c new file mode 100644 index 000000000000..454cb06c12fa --- /dev/null +++ b/clang/test/3C/generalize.c @@ -0,0 +1,159 @@ +// RUN: rm -rf %t* +// RUN: 3c -base-dir=%S -alltypes -addcr %s -- | FileCheck -match-full-lines %s +// RUN: 3c -base-dir=%S -alltypes -addcr %s -- | %clang -c -fcheckedc-extension -x c -o /dev/null - +// RUN: 3c -base-dir=%S -alltypes -output-dir=%t.checked %s -- +// RUN: 3c -base-dir=%t.checked -alltypes %t.checked/generalize.c -- | diff %t.checked/generalize.c - + +// Test the code that adds generics to replace void* + +// Basic functionality +void viewer(void *i) { return; } +// CHECK: _For_any(T) void viewer(_Ptr i) { return; } +void viewer_badnum(void *i, int *j) { +// CHECK: _Itype_for_any(T) void viewer_badnum(_Ptr i, int *j : itype(_Ptr)) { + j = (int*)3; + return; +} +void viewer_update(void *i) { +// CHECK: void viewer_update(void *i) { + i = 2; + return; +} +void *getNull() { return 0; } +// CHECK: _For_any(T) _Ptr getNull(void) { return 0; } +void *getOne() { return 1; } +// CHECK: void *getOne(void) { return 1; } +extern void *glob = 0; +void *getGlobal() { return glob; } +// CHECK: void *getGlobal(void) { return glob; } + +// check type parameters +void call_from_fn() { + // CHECK: void call_from_fn() _Checked { + int *i; + viewer(i); + // CHECK: viewer(i); +} +void call_from_gen_fn(void *i) { + // CHECK: _For_any(T) void call_from_gen_fn(_Ptr i) { + viewer(i); + // CHECK: viewer(i); +} + +// nameless decls use a different code path for rewriting, +// second param forces rewrite +// check to see that we don't rewrite unsafe vals. ie T* +void nameless(void *, char *); +void nameless(void *a, char *b) +{ + a = 1; // make it unsafe +} +// CHECK: void nameless(void * a, _Ptr b); +// CHECK: void nameless(void *a, _Ptr b) + +// Safe functions should be upgraded from "_Itype_for_any" to "_For_any" +_Itype_for_any(T) void has_safe_params(_Ptr i, int *t : itype(_Ptr)) {} +// CHECK: _For_any(T) void has_safe_params(_Ptr i, _Ptr t) _Checked {} + +// itypes are not converted to generics +int recv0(void *buf : itype(_Array_ptr) byte_count(n), int n) {} +// CHECK: int recv0(void *buf : itype(_Array_ptr) byte_count(n), int n) {} + +// greater depth pointers are not converted to generics +void double_ptr(void** dp) {} +// CHECK: void double_ptr(void** dp) {} + +// externs are not converted to generics +void elsewhere(void *x, int *y); +// CHECK: void elsewhere(void *x, int *y); + +// existing generics are not rewritten +_For_any(T) void forany(T* t, _Ptr p, void * v) {} +// CHECK: _For_any(T) void forany(_Ptr t, _Ptr p, void * v) {} + +// functions with multiple potential generics are not rewritten +void twoparams(void *a, void *b) {} +void* inout(void *in) {} +// CHECK: void twoparams(void *a, void *b) {} +// CHECK: void* inout(void *in) {} + +// safe pointers are not converted to generics +void safevoid(_Ptr s){} +// CHECK: void safevoid(_Ptr s){} + + + +// Code reduced from parsons +_Itype_for_any(T) void sys_free(void *free_ptr : itype(_Ptr)); +void extern_fp((*free_fun)(void*)); +static void wrap_free(void *wrap_ptr) { + sys_free(wrap_ptr); + // CHECK: sys_free(wrap_ptr); +} +void make_wild(void) { + extern_fp(wrap_free); +} + +// Code from vsftpd +#include +#include +extern void bug(char*); +extern void die(char*); + +// generics can be added, but not internally, so the result is unchecked +// and we don't add generics for unchecked potential params +void* vsf_sysutil_malloc(unsigned int size) +// CHECK: void* vsf_sysutil_malloc(unsigned int size) +{ + void* p_ret; + /* Paranoia - what if we got an integer overflow/underflow? */ + if (size == 0 || size > INT_MAX) + { + bug("zero or big size in vsf_sysutil_malloc"); + } + p_ret = malloc(size); + if (p_ret == NULL) + { + die("malloc"); + } + return p_ret; +} + +// current generics strategy only converts protos with 1 `void*` +void* vsf_sysutil_realloc(void* p_ptr, unsigned int size) +// CHECK: void* vsf_sysutil_realloc(void* p_ptr, unsigned int size) +{ + void* p_ret; + if (size == 0 || size > INT_MAX) + { + bug("zero or big size in vsf_sysutil_realloc"); + } + p_ret = realloc(p_ptr, size); + if (p_ret == NULL) + { + die("realloc"); + } + return p_ret; +} + +void +vsf_sysutil_free(void* p_ptr) +// CHECK: _For_any(T) void vsf_sysutil_free(_Ptr p_ptr) +{ + if (p_ptr == NULL) + { + bug("vsf_sysutil_free got a null pointer"); + } + free(p_ptr); +} + +void run_vsf_sysutil (void) { + typedef struct {char a; char b;} char_node; + char_node *node1; + // These currently return unsafe values, which hits a bug in 3c: + // https://github.com/correctcomputation/checkedc-clang/issues/622 + //node1 = vsf_sysutil_malloc(sizeof(*node1)); + //node1 = vsf_sysutil_realloc(node1, sizeof(*node1)); + vsf_sysutil_free(node1); + // CHECK: vsf_sysutil_free(node1); +} diff --git a/clang/test/3C/liberal_itypes_ptr.c b/clang/test/3C/liberal_itypes_ptr.c index 9af4eee80838..74eeabfcaad9 100644 --- a/clang/test/3C/liberal_itypes_ptr.c +++ b/clang/test/3C/liberal_itypes_ptr.c @@ -1,7 +1,7 @@ // RUN: rm -rf %t* // RUN: 3c -base-dir=%S -alltypes -addcr %s -- | FileCheck -match-full-lines -check-prefixes="CHECK_ALL","CHECK" %s // RUN: 3c -base-dir=%S -addcr %s -- | FileCheck -match-full-lines -check-prefixes="CHECK_NOALL","CHECK" %s -// RUN: 3c -base-dir=%S -alltypes -addcr %s -- | %clang -c -fcheckedc-extension -x c -o /dev/null - +// RUN: 3c -base-dir=%S -addcr %s -- | %clang -c -fcheckedc-extension -x c -o /dev/null - // RUN: 3c -base-dir=%S -alltypes -output-dir=%t.checked %s -- // RUN: 3c -base-dir=%t.checked -alltypes %t.checked/liberal_itypes_ptr.c -- | diff %t.checked/liberal_itypes_ptr.c - @@ -116,7 +116,8 @@ void bounds_fn(void *b : byte_count(1)); // CHECK: void bounds_fn(void *b : byte_count(1)); void bounds_call(void *p) { - // CHECK: void bounds_call(void *p) { + // CHECK_NOALL: void bounds_call(void *p) { + // CHECK_ALL: _For_any(T) void bounds_call(_Array_ptr p) { bounds_fn(p); // CHECK: bounds_fn(p); } From 16f757397708ae986d49ab60f22af0255e55fc71 Mon Sep 17 00:00:00 2001 From: John Kastner Date: Thu, 26 Aug 2021 16:05:10 -0400 Subject: [PATCH 20/38] Don't add `void` prototypes on functions that would otherwise not need to be rewritten (#688) A function that does not otherwise need to be rewritten would sometimes be rewritten in order to replace a missing prototype with (void). This can trigger a rewriting error if the function is defined by a macro or in an unwritable file. After these changes, 3C still adds a prototype for functions with checked or itype return types, so int `*foo() { return 0; }` will still convert to `_Ptr foo(void) { return 0; }` as is required by Checked C. Checked C also requires that a functions has a prototype for it to be called from inside a checked scope, but this is not handled by 3C; see issue correctcomputation/checkedc-clang#382. The status of this issue has not changed. --- clang/lib/3C/DeclRewriter.cpp | 20 ++++++++++++------ clang/test/3C/dont_add_prototype.c | 34 ++++++++++++++++++++++++++++++ clang/test/3C/generalize.c | 4 ++-- 3 files changed, 50 insertions(+), 8 deletions(-) create mode 100644 clang/test/3C/dont_add_prototype.c diff --git a/clang/lib/3C/DeclRewriter.cpp b/clang/lib/3C/DeclRewriter.cpp index 5ef3bbd22dd0..ee9f9d52a979 100644 --- a/clang/lib/3C/DeclRewriter.cpp +++ b/clang/lib/3C/DeclRewriter.cpp @@ -567,7 +567,8 @@ bool FunctionDeclBuilder::VisitFunctionDecl(FunctionDecl *FD) { bool RewriteGeneric = false; bool DeclIsTypedef = false; - if (TypeSourceInfo *TS = FD->getTypeSourceInfo()) { + TypeSourceInfo *TS = FD->getTypeSourceInfo(); + if (TS != nullptr) { // This still could possibly be a typedef type if TS was NULL. // TypeSourceInfo is null for implicit function declarations, so if a // implicit declaration uses a typedef, it will be missed. That's fine @@ -623,12 +624,10 @@ bool FunctionDeclBuilder::VisitFunctionDecl(FunctionDecl *FD) { RewriteParams = true; } } else { - // No params and no param source: make explicit + // Functions in CheckedC need prototypes, so replace empty parameter lists + // with an explict (void). This updates the parameter list; the rewrite flag + // will be set once it is known if the return needs to be rewritten. ParmStrs.push_back("void"); - QualType ReturnTy = FD->getReturnType(); - QualType Ty = FD->getTypeSourceInfo()->getType(); - if (!Ty->isFunctionProtoType() && ReturnTy->isPointerType()) - RewriteParams = true; } // Get rewritten return variable. @@ -661,6 +660,15 @@ bool FunctionDeclBuilder::VisitFunctionDecl(FunctionDecl *FD) { RewriteParams = true; } + // If this function was declared without a prototype, then we must add one + // to be able to give it a checked return type. This was done by adding "void" + // to the parameter list above. Here we indicate the parameter list should be + // rewritten to include "void" only if the return is already being rewritten. + // This avoids unnecessarily adding void to empty parameter lists on unchecked + // functions. + if (TS && !TS->getType()->isFunctionProtoType() && RewriteReturn) + RewriteParams = true; + // If the function is declared using a typedef for the function type, then we // need to rewrite parameters and the return if either would have been // rewritten. What this does is expand the typedef to the full function type diff --git a/clang/test/3C/dont_add_prototype.c b/clang/test/3C/dont_add_prototype.c new file mode 100644 index 000000000000..3007c57b2ad2 --- /dev/null +++ b/clang/test/3C/dont_add_prototype.c @@ -0,0 +1,34 @@ +// RUN: rm -rf %t* +// RUN: 3c -base-dir=%S -alltypes -addcr %s -- | FileCheck -match-full-lines -check-prefixes="CHECK_ALL","CHECK" %s +// RUN: 3c -base-dir=%S -addcr %s -- | FileCheck -match-full-lines -check-prefixes="CHECK_NOALL","CHECK" %s +// RUN: 3c -base-dir=%S -alltypes -addcr %s -- | %clang -c -fcheckedc-extension -x c -o /dev/null - +// RUN: 3c -base-dir=%S -alltypes -output-dir=%t.checked %s -- +// RUN: 3c -base-dir=%t.checked -alltypes %t.checked/dont_add_prototype.c -- | diff %t.checked/dont_add_prototype.c - + +// Don't add a void prototype on functions if we don't need to. This can +// potentially cause rewriting errors when attempting to rewrite functions +// defined outside the basedir or defined by a macro. + +// This function does not need to be rewritten. Its return type is unchecked +// after solving. It should not be rewritten to use a void prototype in order +// to avoid any potential rewritting issues. +void *test0() { return 1; } +//CHECK: void *test0() { return 1; } + +// Trying to add a prototype in these examples caused a rewriting error +// because the functions are defined by macros. + +#define test_macro0 int *test_macro0() +test_macro0 { +//CHECK: test_macro0 { + return 0; +} + +#define test_macro1 test_macro1() +int *test_macro1 { +//CHECK: int *test_macro1 { + return 0; +} + +// Force conversion output. +int *a; diff --git a/clang/test/3C/generalize.c b/clang/test/3C/generalize.c index 454cb06c12fa..9a9badc7fc3a 100644 --- a/clang/test/3C/generalize.c +++ b/clang/test/3C/generalize.c @@ -22,10 +22,10 @@ void viewer_update(void *i) { void *getNull() { return 0; } // CHECK: _For_any(T) _Ptr getNull(void) { return 0; } void *getOne() { return 1; } -// CHECK: void *getOne(void) { return 1; } +// CHECK: void *getOne() { return 1; } extern void *glob = 0; void *getGlobal() { return glob; } -// CHECK: void *getGlobal(void) { return glob; } +// CHECK: void *getGlobal() { return glob; } // check type parameters void call_from_fn() { From d7b9b2de54257682c03db5c4023443ab0f5f08cb Mon Sep 17 00:00:00 2001 From: John Kastner Date: Thu, 26 Aug 2021 16:54:03 -0400 Subject: [PATCH 21/38] Ensure bounds are not inserted into unwritable files (#689) Adds checks to ensure that 3C will not attempt to rewrite in unwritable files to insert array bounds. The test file in this PR contains an example using a function body in an unwritable file which fails on the current main. After changes made to support rewriting in functions declarations without definitions (correctcomputation/checkedc-clang#691), this will be able to happen even when the functions are not defined, and so it will becomes a more prevalent issue. --- clang/include/clang/3C/CtxSensAVarBounds.h | 3 + clang/lib/3C/AVarBoundsInfo.cpp | 59 +++++++++------ clang/lib/3C/CtxSensAVarBounds.cpp | 51 ++++++------- clang/test/3C/base_subdir/unwritable_bounds.c | 74 +++++++++++++++++++ clang/test/3C/unwritable_bounds.h | 34 +++++++++ 5 files changed, 173 insertions(+), 48 deletions(-) create mode 100644 clang/test/3C/base_subdir/unwritable_bounds.c create mode 100644 clang/test/3C/unwritable_bounds.h diff --git a/clang/include/clang/3C/CtxSensAVarBounds.h b/clang/include/clang/3C/CtxSensAVarBounds.h index 11b41a880c0c..f9d4d7705d86 100644 --- a/clang/include/clang/3C/CtxSensAVarBounds.h +++ b/clang/include/clang/3C/CtxSensAVarBounds.h @@ -92,6 +92,9 @@ class CtxSensitiveBoundsKeyHandler { std::map &BKMap, bool IsGlobal); + void contextualizeCVar(RecordDecl *RD, std::string AccessKey, bool IsGlobal, + ASTContext *C, ProgramInfo &I); + // Get string that represents a context sensitive key for the struct // member access ME. std::string getCtxStructKey(MemberExpr *ME, ASTContext *C); diff --git a/clang/lib/3C/AVarBoundsInfo.cpp b/clang/lib/3C/AVarBoundsInfo.cpp index 0f8d0b6df5cf..a13ff98cde12 100644 --- a/clang/lib/3C/AVarBoundsInfo.cpp +++ b/clang/lib/3C/AVarBoundsInfo.cpp @@ -592,18 +592,37 @@ void PotentialBoundsInfo::addPotentialBoundsPOne( } bool AVarBoundsInfo::isValidBoundVariable(clang::Decl *D) { + if (D == nullptr) + return false; + + // Any pointer declaration in an unwritable file without existing bounds + // annotations is not valid. This ensures we do not add bounds onto pointers + // where attempting to rewrite the variable to insert the bound would be an + // error. If there are existing bounds, no new bound will be inferred, so no + // rewriting will be attempted. By leaving existing bounds as valid, these + // bounds can be used infer bounds on other (writeable) declarations. + // Non-pointer types are also still valid because these will never need bounds + // expression, and they need to remain valid so that they can be used by + // existing array pointer bounds. + auto PSL = PersistentSourceLoc::mkPSL(D, D->getASTContext()); + if (!canWrite(PSL.getFileName())) { + if (auto *DD = dyn_cast(D)) + return !DD->getType()->isPointerType() || DD->hasBoundsExpr(); + return false; + } + // All return and field values are valid bound variables. - if (D && (isa(D) || isa(D))) + if (isa(D) || isa(D)) return true; // For Parameters, check if they belong to a valid function. // Function pointer types are not considered valid functions, so function - // pointer parameters are are disqualified as valid bound variables here. - if (auto *PD = dyn_cast_or_null(D)) + // pointer parameters are disqualified as valid bound variables here. + if (auto *PD = dyn_cast(D)) return PD->getParentFunctionOrMethod() != nullptr; - // For VarDecls, check if these are are not dummy and have a name. - if (auto *VD = dyn_cast_or_null(D)) + // For VarDecls, check if these are not dummy and have a name. + if (auto *VD = dyn_cast(D)) return !VD->getNameAsString().empty(); return false; @@ -651,27 +670,25 @@ bool AVarBoundsInfo::tryGetVariable(clang::Decl *D, BoundsKey &R) { bool AVarBoundsInfo::tryGetVariable(clang::Expr *E, const ASTContext &C, BoundsKey &Res) { - Optional OptConsVal; - bool Ret = false; if (E != nullptr) { E = E->IgnoreParenCasts(); - if (E->getType()->isArithmeticType() && - (OptConsVal = E->getIntegerConstantExpr(C))) { + + // Get the BoundsKey for the constant value if the expression is a constant + // integer expression. + Optional OptConsVal = E->getIntegerConstantExpr(C); + if (E->getType()->isArithmeticType() && OptConsVal.hasValue()) { Res = getVarKey(*OptConsVal); - Ret = true; - } else if (DeclRefExpr *DRE = dyn_cast(E)) { - auto *D = DRE->getDecl(); - Ret = tryGetVariable(D, Res); - if (!Ret) { - assert(false && "Invalid declaration found inside bounds expression"); - } - } else if (MemberExpr *ME = dyn_cast(E)) { - return tryGetVariable(ME->getMemberDecl(), Res); - } else { - // assert(false && "Variable inside bounds declaration is an expression"); + return true; } + + // For declarations or struct member references, get the bounds key for the + // references variables or field. + if (auto *DRE = dyn_cast(E)) + return tryGetVariable(DRE->getDecl(), Res); + if (auto *ME = dyn_cast(E)) + return tryGetVariable(ME->getMemberDecl(), Res); } - return Ret; + return false; } // Merging bounds B with the present bounds of key L at the same priority P diff --git a/clang/lib/3C/CtxSensAVarBounds.cpp b/clang/lib/3C/CtxSensAVarBounds.cpp index 0224f56cf169..9ef28f4a1aaf 100644 --- a/clang/lib/3C/CtxSensAVarBounds.cpp +++ b/clang/lib/3C/CtxSensAVarBounds.cpp @@ -170,7 +170,7 @@ bool CtxSensitiveBoundsKeyHandler::tryGetFieldCSKey( FieldDecl *FD, CtxStKeyMap *CSK, const std::string &AK, ASTContext *C, ProgramInfo &I, BoundsKey &CSKey) { bool RetVal = false; - if (CSK->find(AK) != CSK->end()) { + if (ABI->isValidBoundVariable(FD) && CSK->find(AK) != CSK->end()) { CVarOption CV = I.getVariable(FD, C); BoundsKey OrigK; if (CV.hasValue() && CV.getValue().hasBoundsKey()) { @@ -225,44 +225,41 @@ void CtxSensitiveBoundsKeyHandler::contextualizeStructRecord( } } +void CtxSensitiveBoundsKeyHandler::contextualizeCVar(RecordDecl *RD, + std::string AccessKey, + bool IsGlobal, + ASTContext *C, + ProgramInfo &I) { + std::string FileName = PersistentSourceLoc::mkPSL(RD, *C).getFileName(); + if (canWrite(FileName)) { + // Context sensitive struct key map. + CtxStKeyMap *MECSMap = getCtxStKeyMap(IsGlobal); + auto &BKeyMap = (*MECSMap)[AccessKey]; + contextualizeStructRecord(I, C, RD, AccessKey, BKeyMap, IsGlobal); + } +} + void CtxSensitiveBoundsKeyHandler::contextualizeCVar(MemberExpr *ME, ASTContext *C, ProgramInfo &I) { FieldDecl *FD = dyn_cast_or_null(ME->getMemberDecl()); - if (FD != nullptr) { - RecordDecl *RD = FD->getParent(); - // If the base decl is not null. - if (RD != nullptr) { - // Get structure access key. - StructAccessVisitor SKV(C); - SKV.TraverseStmt(ME->getBase()->getExprStmt()); - std::string AK = SKV.getStructAccessKey(); - // Context sensitive struct key map. - CtxStKeyMap *MECSMap = getCtxStKeyMap(SKV.IsGlobal); - auto &BKeyMap = (*MECSMap)[AK]; - contextualizeStructRecord(I, C, RD, AK, BKeyMap, SKV.IsGlobal); - } + if (RecordDecl *RD = FD != nullptr ? FD->getParent() : nullptr) { + // Get structure access key. + StructAccessVisitor SKV(C); + SKV.TraverseStmt(ME->getBase()->getExprStmt()); + contextualizeCVar(RD, SKV.getStructAccessKey(), SKV.IsGlobal, C, I); } } void CtxSensitiveBoundsKeyHandler::contextualizeCVar(VarDecl *VD, ASTContext *C, ProgramInfo &I) { - const RecordType *RT = dyn_cast_or_null( - VD->getType()->getUnqualifiedDesugaredType()); - const RecordDecl *RD = nullptr; - if (RT != nullptr) { - RD = RT->getDecl(); - } - // If the base decl is not null. - if (RT != nullptr) { + const auto *RT = dyn_cast_or_null( + VD->getType()->getUnqualifiedDesugaredType()); + if (RecordDecl *RD = RT != nullptr ? RT->getDecl() : nullptr) { // Get structure access key. StructAccessVisitor SKV(C); SKV.processVarDecl(VD); - // Context sensitive struct key map. - CtxStKeyMap *MECSMap = getCtxStKeyMap(SKV.IsGlobal); - std::string AK = SKV.getStructAccessKey(); - auto &BKeyMap = (*MECSMap)[AK]; - contextualizeStructRecord(I, C, RD, AK, BKeyMap, SKV.IsGlobal); + contextualizeCVar(RD, SKV.getStructAccessKey(), SKV.IsGlobal, C, I); } } diff --git a/clang/test/3C/base_subdir/unwritable_bounds.c b/clang/test/3C/base_subdir/unwritable_bounds.c new file mode 100644 index 000000000000..7636ea1006f5 --- /dev/null +++ b/clang/test/3C/base_subdir/unwritable_bounds.c @@ -0,0 +1,74 @@ +// RUN: rm -rf %t* +// RUN: cd %S +// RUN: 3c -alltypes -addcr -output-dir=%t.checked/base_subdir %s -- + +// The functions called from this file would normally have bounds inferred if +// they were declared in a writable file. The file is not writable, so the new +// bounds must not be inferred. This possibility of this happening existed +// before 3C started inferring itypes on undefined functions, but it became a +// significant issue and was noticed with the introduction of this feature. + +#include "../unwritable_bounds.h" +void test_functions() { + { + char s[0]; + unwrite0(s); + } + { + char s[0]; + unwrite1(s); + } + { + char s[0]; + unwrite2(s); + } + { + char s[0]; + unwrite3(s); + } + { + char s[0]; + unwrite4(s); + } + { + char s[0]; + unwrite5(s); + } + { + char s[0]; + unwrite6(s); + } +} + +void test_struct() { + // There is a code path for variable declarations and a different path for + // uses of a variable. The initializer is required to test the declaration + // path. + struct arr_struct a = {}; + for (int i = 0; i < a.n; i++) + a.arr[i]; + + // I don't quite understand why this was a problem, but it caused an + // assertion to fail after apply the fix for the first struct test + // case. + union e { + float d + }; + int i = struct_ret()->c; + union e f; + f.d; +} + +void test_glob() { + for (int i = 0; i < 10; i++) + glob0[i]; + + for (int i = 0; i < 10; i++) + glob1[i]; + + for (int i = 0; i < 10; i++) + glob2[i]; + + for (int i = 0; i < 10; i++) + glob3[i]; +} diff --git a/clang/test/3C/unwritable_bounds.h b/clang/test/3C/unwritable_bounds.h new file mode 100644 index 000000000000..898e373b7869 --- /dev/null +++ b/clang/test/3C/unwritable_bounds.h @@ -0,0 +1,34 @@ +// Used by base_subdir/unwritable_bounds.c . + +void unwrite0(void *p : itype(_Array_ptr)); +void unwrite1(char *p); +void unwrite2(char *p : itype(_Array_ptr)); +void unwrite3(_Array_ptr p); + +void unwrite4(char *p) { + for (int i = 0; i < 10; i++) + p[i]; +} +void unwrite5(char *p : itype(_Array_ptr)) { + for (int i = 0; i < 10; i++) + p[i]; +} +void unwrite6(_Array_ptr p) { + for (int i = 0; i < 10; i++) + p[i]; +} + +struct arr_struct { + int *arr; + int n; +}; + +struct other_struct { + char *c; +}; +struct other_struct *struct_ret(); + +int *glob0; +int *glob1 : itype(_Array_ptr); +int *glob2 : count(10); +int *glob3 : itype(_Ptr); From df738a40c33f5dbc19eac2191f645d779c924839 Mon Sep 17 00:00:00 2001 From: John Kastner Date: Fri, 27 Aug 2021 13:46:58 -0400 Subject: [PATCH 22/38] Insert itypes for parameters with typedef types (#690) This allows a function using an otherwise unchecked typedef type to expose an checked interface. It also lets a function use a typedef'ed parameter unsafely without forcing the entire typedef to be unchecked. In the following example, the typedef td is unchecked because of the unsafe use of b. Prior to this change, test would not be rewritten, leaving it with a fully unchecked type. The new rewriting is `void test(td a : itype(_Ptr))`. typedef int *td; void test(td a) { td b = 1; } In the other direction, the parameter can be used unsafely, but the typedef will still be rewritten to `_Ptr`, and the parameter will get an itype `int * : itype(td)`. typedef int *td; void test(td a) { a = 1; } --- clang/include/clang/3C/ConstraintVariables.h | 9 +- clang/include/clang/3C/ProgramInfo.h | 4 +- clang/lib/3C/ConstraintVariables.cpp | 25 ++-- clang/lib/3C/DeclRewriter.cpp | 25 +++- clang/lib/3C/ProgramInfo.cpp | 36 +++--- clang/test/3C/basic_checks.c | 2 +- clang/test/3C/itype_typedef.c | 120 +++++++++++++++++++ 7 files changed, 184 insertions(+), 37 deletions(-) create mode 100644 clang/test/3C/itype_typedef.c diff --git a/clang/include/clang/3C/ConstraintVariables.h b/clang/include/clang/3C/ConstraintVariables.h index bb857e3ec77b..71b8b109d0ea 100644 --- a/clang/include/clang/3C/ConstraintVariables.h +++ b/clang/include/clang/3C/ConstraintVariables.h @@ -365,7 +365,7 @@ class PointerVariableConstraint : public ConstraintVariable { bool IsZeroWidthArray; bool IsTypedef = false; - TypedefNameDecl *TDT; + ConstraintVariable *TypedefVar; std::string TypedefString; // Does the type internally contain a typedef, and if so: at what level and // what is it's name? @@ -381,7 +381,7 @@ class PointerVariableConstraint : public ConstraintVariable { ConstraintVariable(PointerVariable, "", Name), FV(nullptr), SrcHasItype(false), PartOfFuncPrototype(false), Parent(nullptr), SourceGenericIndex(-1), InferredGenericIndex(-1), - IsZeroWidthArray(false), IsTypedef(false), TDT(nullptr), + IsZeroWidthArray(false), IsTypedef(false), TypedefLevelInfo({}), IsVoidPtr(false) {} public: @@ -392,8 +392,9 @@ class PointerVariableConstraint : public ConstraintVariable { // Check if any of the pointers is either a sized or unsized arr. bool hasSomeSizedArr() const; - bool isTypedef(void); - void setTypedef(TypedefNameDecl *T, std::string S); + bool isTypedef(void) const; + const ConstraintVariable *getTypedefVar() const; + void setTypedef(ConstraintVariable *TDVar, std::string S); // Return true if this constraint had an itype in the original source code. bool srcHasItype() const override { return SrcHasItype; } diff --git a/clang/include/clang/3C/ProgramInfo.h b/clang/include/clang/3C/ProgramInfo.h index 1ebcabdf7bad..23dfe71c0712 100644 --- a/clang/include/clang/3C/ProgramInfo.h +++ b/clang/include/clang/3C/ProgramInfo.h @@ -162,8 +162,8 @@ class ProgramInfo : public ProgramVariableAdder { void ensureNtCorrect(const QualType &QT, const ASTContext &C, PointerVariableConstraint *PV); - void unifyIfTypedef(const clang::Type *, clang::ASTContext &, - clang::DeclaratorDecl *, PVConstraint *); + void unifyIfTypedef(const QualType &QT, clang::ASTContext &, + PVConstraint *, ConsAction CA = Same_to_Same); CVarOption lookupTypedef(PersistentSourceLoc PSL); diff --git a/clang/lib/3C/ConstraintVariables.cpp b/clang/lib/3C/ConstraintVariables.cpp index 3e984cef8cf2..6d5751a4c423 100644 --- a/clang/lib/3C/ConstraintVariables.cpp +++ b/clang/lib/3C/ConstraintVariables.cpp @@ -130,7 +130,7 @@ PointerVariableConstraint::PointerVariableConstraint( SourceGenericIndex(Ot->SourceGenericIndex), InferredGenericIndex(Ot->InferredGenericIndex), IsZeroWidthArray(Ot->IsZeroWidthArray), - IsTypedef(Ot->IsTypedef), TDT(Ot->TDT), TypedefString(Ot->TypedefString), + IsTypedef(Ot->IsTypedef), TypedefString(Ot->TypedefString), TypedefLevelInfo(Ot->TypedefLevelInfo), IsVoidPtr(Ot->IsVoidPtr) { // These are fields of the super class Constraint Variable this->HasEqArgumentConstraints = Ot->HasEqArgumentConstraints; @@ -206,8 +206,7 @@ PointerVariableConstraint::PointerVariableConstraint( const ASTContext &C, std::string *InFunc, int ForceGenericIndex, bool PotentialGeneric, bool VarAtomForChecked, TypeSourceInfo *TSInfo, const QualType &ITypeT) - : ConstraintVariable(ConstraintVariable::PointerVariable, - tyToStr(QT.getTypePtr()), N), + : ConstraintVariable(ConstraintVariable::PointerVariable, qtyToStr(QT), N), FV(nullptr), SrcHasItype(false), PartOfFuncPrototype(InFunc != nullptr), Parent(nullptr) { QualType QTy = QT; @@ -704,14 +703,20 @@ void PointerVariableConstraint::addArrayAnnotations( assert(ConstArrs.empty()); } -bool PointerVariableConstraint::isTypedef(void) { return IsTypedef; } +bool PointerVariableConstraint::isTypedef(void) const { return IsTypedef; } -void PointerVariableConstraint::setTypedef(TypedefNameDecl *T, std::string S) { +void PointerVariableConstraint::setTypedef(ConstraintVariable *TDVar, + std::string S) { IsTypedef = true; - TDT = T; + TypedefVar = TDVar; TypedefString = S; } +const ConstraintVariable *PointerVariableConstraint::getTypedefVar() const { + assert(isTypedef()); + return TypedefVar; +} + // Mesh resolved constraints with the PointerVariableConstraints set of // variables and potentially nested function pointer declaration. Produces a // string that can be replaced in the source code. @@ -736,8 +741,12 @@ PointerVariableConstraint::mkString(Constraints &CS, UseName = getName(); if (IsTypedef && !UnmaskTypedef) { - return gatherQualStrings() + TypedefString + - (EmitName && !IsReturn ? (" " + UseName) : " "); + std::string QualTypedef = gatherQualStrings() + TypedefString; + if (!ForItype) + QualTypedef += " "; + if (EmitName && !IsReturn) + QualTypedef += UseName; + return QualTypedef; } std::ostringstream Ss; diff --git a/clang/lib/3C/DeclRewriter.cpp b/clang/lib/3C/DeclRewriter.cpp index ee9f9d52a979..a0768d36097d 100644 --- a/clang/lib/3C/DeclRewriter.cpp +++ b/clang/lib/3C/DeclRewriter.cpp @@ -35,6 +35,10 @@ using namespace clang; void DeclRewriter::buildItypeDecl(PVConstraint *Defn, DeclaratorDecl *Decl, std::string &Type, std::string &IType, ProgramInfo &Info, ArrayBoundsRewriter &ABR) { + const EnvironmentMap &Env = Info.getConstraints().getVariables(); + bool IsTypedefVarUnchecked = + Defn->isTypedef() && (ItypesForExtern || + !Defn->getTypedefVar()->isSolutionChecked(Env)); if (Defn->getFV()) { // This declaration is for a function pointer. Writing itypes on function // pointers is a little bit harder since the original type string will not @@ -42,10 +46,22 @@ void DeclRewriter::buildItypeDecl(PVConstraint *Defn, DeclaratorDecl *Decl, // unchecked type from the PVConstraint. The last argument of this call // tells mkString to generate a string using unchecked types instead of // checked types. - Type = Defn->mkString(Info.getConstraints(), - MKSTRING_OPTS(ForItypeBase = true)); + if (Defn->isTypedef() && !IsTypedefVarUnchecked) + Type = Defn->mkString(Info.getConstraints(), + MKSTRING_OPTS(UnmaskTypedef = true, + ForItypeBase = true)); + else + Type = Defn->mkString(Info.getConstraints(), + MKSTRING_OPTS(ForItypeBase = true)); } else { - Type = Defn->getRewritableOriginalTy(); + if (Defn->isTypedef() && !IsTypedefVarUnchecked) + Type = Defn->mkString(Info.getConstraints(), + MKSTRING_OPTS(UnmaskTypedef = true, + ForItypeBase = true, + EmitName = false)); + else + Type = Defn->getRewritableOriginalTy(); + if (isa_and_nonnull(Decl)) { if (Decl->getName().empty()) Type += Defn->getName(); @@ -59,7 +75,8 @@ void DeclRewriter::buildItypeDecl(PVConstraint *Defn, DeclaratorDecl *Decl, } IType = " : itype("; - if (ItypesForExtern && Defn->isTypedef()) { + + if (IsTypedefVarUnchecked) { // In -itypes-for-extern mode we do not rewrite typedefs to checked types. // They are given a checked itype instead. The unchecked portion of the // itype continues to use the original typedef, but the typedef in the diff --git a/clang/lib/3C/ProgramInfo.cpp b/clang/lib/3C/ProgramInfo.cpp index c10e4fc43b74..54aaebc2e432 100644 --- a/clang/lib/3C/ProgramInfo.cpp +++ b/clang/lib/3C/ProgramInfo.cpp @@ -640,8 +640,8 @@ void ProgramInfo::addVariable(clang::DeclaratorDecl *D, NewCV = F; auto RetTy = FD->getReturnType(); - unifyIfTypedef(RetTy.getTypePtr(), *AstContext, FD, F->getExternalReturn()); - unifyIfTypedef(RetTy.getTypePtr(), *AstContext, FD, F->getInternalReturn()); + unifyIfTypedef(RetTy, *AstContext, F->getExternalReturn(), Wild_to_Safe); + unifyIfTypedef(RetTy, *AstContext, F->getInternalReturn(), Safe_to_Wild); ensureNtCorrect(RetTy, *AstContext, F->getExternalReturn()); ensureNtCorrect(RetTy, *AstContext, F->getInternalReturn()); @@ -649,13 +649,13 @@ void ProgramInfo::addVariable(clang::DeclaratorDecl *D, // the parameters. for (unsigned I = 0; I < FD->getNumParams(); I++) { ParmVarDecl *PVD = FD->getParamDecl(I); - const Type *Ty = PVD->getType().getTypePtr(); + QualType ParamTy = PVD->getType(); PVConstraint *PVInternal = F->getInternalParam(I); PVConstraint *PVExternal = F->getExternalParam(I); - unifyIfTypedef(Ty, *AstContext, PVD, PVInternal); - unifyIfTypedef(Ty, *AstContext, PVD, PVExternal); - ensureNtCorrect(PVD->getType(), *AstContext, PVInternal); - ensureNtCorrect(PVD->getType(), *AstContext, PVExternal); + unifyIfTypedef(ParamTy, *AstContext, PVExternal, Wild_to_Safe); + unifyIfTypedef(ParamTy, *AstContext, PVInternal, Safe_to_Wild); + ensureNtCorrect(ParamTy, *AstContext, PVInternal); + ensureNtCorrect(ParamTy, *AstContext, PVExternal); PVInternal->setValidDecl(); PersistentSourceLoc PSL = PersistentSourceLoc::mkPSL(PVD, *AstContext); // Constraint variable is stored on the parent function, so we need to @@ -675,13 +675,13 @@ void ProgramInfo::addVariable(clang::DeclaratorDecl *D, } else if (VarDecl *VD = dyn_cast(D)) { assert(!isa(VD)); - const Type *Ty = VD->getTypeSourceInfo()->getTypeLoc().getTypePtr(); - if (Ty->isPointerType() || Ty->isArrayType()) { + QualType QT = VD->getTypeSourceInfo()->getTypeLoc().getType(); + if (QT->isPointerType() || QT->isArrayType()) { PVConstraint *P = new PVConstraint(D, *this, *AstContext); P->setValidDecl(); NewCV = P; std::string VarName(VD->getName()); - unifyIfTypedef(Ty, *AstContext, VD, P); + unifyIfTypedef(QT, *AstContext, P); ensureNtCorrect(VD->getType(), *AstContext, P); if (VD->hasGlobalStorage()) { // If we see a definition for this global variable, indicate so in @@ -699,10 +699,10 @@ void ProgramInfo::addVariable(clang::DeclaratorDecl *D, } } else if (FieldDecl *FlD = dyn_cast(D)) { - const Type *Ty = FlD->getTypeSourceInfo()->getTypeLoc().getTypePtr(); - if (Ty->isPointerType() || Ty->isArrayType()) { + QualType QT = FlD->getTypeSourceInfo()->getTypeLoc().getType(); + if (QT->isPointerType() || QT->isArrayType()) { PVConstraint *P = new PVConstraint(D, *this, *AstContext); - unifyIfTypedef(Ty, *AstContext, FlD, P); + unifyIfTypedef(QT, *AstContext, P); NewCV = P; NewCV->setValidDecl(); } @@ -725,16 +725,16 @@ void ProgramInfo::ensureNtCorrect(const QualType &QT, const ASTContext &C, } } -void ProgramInfo::unifyIfTypedef(const Type *Ty, ASTContext &Context, - DeclaratorDecl *Decl, PVConstraint *P) { - if (const auto *TDT = dyn_cast(Ty)) { +void ProgramInfo::unifyIfTypedef(const QualType &QT, ASTContext &Context, + PVConstraint *P, ConsAction CA) { + if (const auto *TDT = dyn_cast(QT.getTypePtr())) { auto *TDecl = TDT->getDecl(); auto PSL = PersistentSourceLoc::mkPSL(TDecl, Context); auto O = lookupTypedef(PSL); if (O.hasValue()) { auto *Bounds = &O.getValue(); - P->setTypedef(TDecl, TDecl->getNameAsString()); - constrainConsVarGeq(P, Bounds, CS, &PSL, Same_to_Same, true, this); + P->setTypedef(Bounds, TDecl->getNameAsString()); + constrainConsVarGeq(P, Bounds, CS, &PSL, CA, false, this); } } } diff --git a/clang/test/3C/basic_checks.c b/clang/test/3C/basic_checks.c index 5ba1b44d9f80..b94ec1e49916 100644 --- a/clang/test/3C/basic_checks.c +++ b/clang/test/3C/basic_checks.c @@ -38,7 +38,7 @@ void mut_pa(PA p) { p->a = 0; p->b = 1; } -//CHECK: void mut_pa(PA p) { +//CHECK: void mut_pa(PA p : itype(_Ptr)) { void pa_driver(void) { A a = {0}; diff --git a/clang/test/3C/itype_typedef.c b/clang/test/3C/itype_typedef.c new file mode 100644 index 000000000000..f86d7d6b914e --- /dev/null +++ b/clang/test/3C/itype_typedef.c @@ -0,0 +1,120 @@ +// RUN: rm -rf %t* +// RUN: 3c -base-dir=%S -alltypes -addcr %s -- | FileCheck -match-full-lines -check-prefixes="CHECK_ALL","CHECK" %s +// RUN: 3c -base-dir=%S -addcr %s -- | FileCheck -match-full-lines -check-prefixes="CHECK_NOALL","CHECK" %s +// RUN: 3c -base-dir=%S -addcr %s -- | %clang -c -fcheckedc-extension -x c -o /dev/null - +// RUN: 3c -base-dir=%S -alltypes -output-dir=%t.checked %s -- +// RUN: 3c -base-dir=%t.checked -alltypes %t.checked/itype_typedef.c -- | diff %t.checked/itype_typedef.c - + +// Itype typedef on the paramter + +typedef int *td0; +void test0(td0 a) { +//CHECK: typedef _Ptr td0; +//CHECK: void test0(int *a : itype(td0)) { + a = 1; +} + +typedef int *td1; +void test1(td1 a) { +//CHECK: typedef int *td1; +//CHECK: void test1(td1 a : itype(_Ptr)) { + td1 b = a; + b = 1; +} + +typedef int *td2; +void test2(td2 a) { +//CHECK: typedef int *td2; +//CHECK: void test2(td2 a : itype(_Ptr)) { + td2 b = 1; +} + +// Itype typedef on the return + +typedef int *td3; +td3 test3() { +//CHECK: typedef _Ptr td3; +//CHECK: int * test3(void) : itype(td3) { + return (int*) 1; +} + +typedef int *td4; +td4 test4() { +//CHECK: typedef int *td4; +//CHECK: td4 test4(void) : itype(_Ptr) { + td4 a = 1; + return a; +} + +// Itype typedef with array bounds + +typedef int *td5; +void test5(td5 a, int n) { +//CHECK: typedef int *td5; +//CHECK_ALL: void test5(td5 a : itype(_Array_ptr) count(n), int n) { +//CHECK_NOALL: void test5(td5 a : itype(_Ptr), int n) { + for (int i = 0; i < n; i++) + a[i]; + td5 b = 1; +} + +typedef int *td6; +void test6(td6 a, int n) { +//CHECK_ALL: typedef _Array_ptr td6; +//CHECK_ALL: void test6(int *a : itype(td6) count(n), int n) { +//CHECK_NOALL: typedef _Ptr td6; +//CHECK_NOALL: void test6(int *a : itype(td6), int n) { + for (int i = 0; i < n; i++) + a[i]; + a = 1; +} + +// Itype typedef with type qualifiers + +typedef int *td7; +void test7(const td7 a) { +//CHECK: typedef _Ptr td7; +//CHECK: void test7(int *const a : itype(const td7)) { + int *b = a; + b = 1; +} + +typedef int *td8; +void test8(const td8 a) { +//CHECK: typedef int *td8; +//CHECK: void test8(const td8 a : itype(const _Ptr)) { + td8 b = a; + b = 1; +} + +typedef const int *td9; +void test9(td9 a) { +//CHECK: typedef _Ptr td9; +//CHECK: void test9(const int *a : itype(td9)) { + int *b = a; + b = 1; +} + +typedef const int *td10; +void test10(td10 a) { +//CHECK: typedef const int *td10; +//CHECK: void test10(td10 a : itype(_Ptr)) { + td10 b = a; + b = 1; +} + +// With functions pointers + +typedef int (*fp_td0)(int); +void fp_test0(fp_td0 a) { +//CHECK: typedef _Ptr fp_td0; +//CHECK: void fp_test0(int ((*a)(int)) : itype(fp_td0)) { + a = 1; +} + +typedef int (*fp_td1)(int); +void fp_test1(fp_td1 a) { +//CHECK: typedef int (*fp_td1)(int); +//CHECK: void fp_test1(fp_td1 a : itype(_Ptr)) { + fp_td1 b = 1; +} From fd4d8af4383d40af10ee8bc92b7bf88061a11035 Mon Sep 17 00:00:00 2001 From: John Kastner Date: Mon, 30 Aug 2021 15:32:49 -0400 Subject: [PATCH 23/38] Command line options clean up (#692) We previously used the _3COptions structure to store command line options immediately after they were parsed, but later copied the option values out of this structure and into global variables for use in the rest of the program. This change deletes the global variables and replaces them with a single global instance of the _3COptions structure. --- clang/include/clang/3C/3C.h | 54 ------- clang/include/clang/3C/3CGlobalOptions.h | 74 +++++++--- clang/lib/3C/3C.cpp | 139 ++++++------------ clang/lib/3C/ArrayBoundsInferenceConsumer.cpp | 2 +- clang/lib/3C/ConstraintBuilder.cpp | 23 +-- clang/lib/3C/ConstraintResolver.cpp | 11 +- clang/lib/3C/Constraints.cpp | 6 +- clang/lib/3C/DeclRewriter.cpp | 14 +- clang/lib/3C/MappingVisitor.cpp | 4 +- clang/lib/3C/ProgramInfo.cpp | 21 +-- clang/lib/3C/RewriteUtils.cpp | 38 ++--- clang/lib/3C/Utils.cpp | 7 +- clang/tools/3c/3CStandalone.cpp | 5 +- 13 files changed, 170 insertions(+), 228 deletions(-) diff --git a/clang/include/clang/3C/3C.h b/clang/include/clang/3C/3C.h index 543fb97dbfd4..7253ba252096 100644 --- a/clang/include/clang/3C/3C.h +++ b/clang/include/clang/3C/3C.h @@ -22,60 +22,6 @@ #include "clang/Tooling/CommonOptionsParser.h" #include -// Options used to initialize 3C tool. -// -// See clang/docs/checkedc/3C/clang-tidy.md#_3c-name-prefix -// NOLINTNEXTLINE(readability-identifier-naming) -struct _3COptions { - bool DumpIntermediate; - - bool Verbose; - - std::string OutputPostfix; - std::string OutputDir; - - std::string ConstraintOutputJson; - - bool DumpStats; - - std::string StatsOutputJson; - - std::string WildPtrInfoJson; - - std::string PerPtrInfoJson; - - std::vector AllocatorFunctions; - - bool HandleVARARGS; - - bool EnablePropThruIType; - - std::string BaseDir; - bool AllowSourcesOutsideBaseDir; - - bool EnableAllTypes; - - bool AddCheckedRegions; - - bool EnableCCTypeChecker; - - bool WarnRootCause; - - bool WarnAllRootCause; - -#ifdef FIVE_C - bool RemoveItypes; - bool ForceItypes; -#endif - - bool DumpUnwritableChanges; - bool AllowUnwritableChanges; - - bool AllowRewriteFailures; - - bool ItypesForExtern; -}; - // The main interface exposed by the 3C to interact with the tool. // // See clang/docs/checkedc/3C/clang-tidy.md#_3c-name-prefix diff --git a/clang/include/clang/3C/3CGlobalOptions.h b/clang/include/clang/3C/3CGlobalOptions.h index 08a038f97f70..ab2748ea6e12 100644 --- a/clang/include/clang/3C/3CGlobalOptions.h +++ b/clang/include/clang/3C/3CGlobalOptions.h @@ -15,29 +15,61 @@ #include "llvm/Support/CommandLine.h" -extern bool Verbose; -extern bool DumpIntermediate; -extern std::string OutputPostfix; -extern std::string OutputDir; -extern bool HandleVARARGS; -extern bool SeperateMultipleFuncDecls; -extern bool EnablePropThruIType; -extern bool ConsiderAllocUnsafe; -extern bool AllTypes; -extern bool NewSolver; -extern std::string BaseDir; -extern std::vector AllocatorFunctions; -extern bool AddCheckedRegions; -extern bool WarnRootCause; -extern bool WarnAllRootCause; -extern bool DumpUnwritableChanges; -extern bool AllowUnwritableChanges; -extern bool AllowRewriteFailures; -extern bool ItypesForExtern; +// Options used to initialize 3C tool. +// +// See clang/docs/checkedc/3C/clang-tidy.md#_3c-name-prefix +// NOLINTNEXTLINE(readability-identifier-naming) +struct _3COptions { + bool DumpIntermediate; + + bool Verbose; + + std::string OutputPostfix; + std::string OutputDir; + + std::string ConstraintOutputJson; + + bool DumpStats; + + std::string StatsOutputJson; + + std::string WildPtrInfoJson; + + std::string PerWildPtrInfoJson; + + std::vector AllocatorFunctions; + + bool HandleVARARGS; + + bool EnablePropThruIType; + + std::string BaseDir; + bool AllowSourcesOutsideBaseDir; + + bool AllTypes; + + bool AddCheckedRegions; + + bool EnableCCTypeChecker; + + bool WarnRootCause; + + bool WarnAllRootCause; #ifdef FIVE_C -extern bool RemoveItypes; -extern bool ForceItypes; + bool RemoveItypes; + bool ForceItypes; #endif + bool DumpUnwritableChanges; + bool AllowUnwritableChanges; + + bool AllowRewriteFailures; + + bool ItypesForExtern; +}; + +// NOLINTNEXTLINE(readability-identifier-naming) +extern struct _3COptions _3COpts; + #endif // LLVM_CLANG_3C_3CGLOBALOPTIONS_H diff --git a/clang/lib/3C/3C.cpp b/clang/lib/3C/3C.cpp index 702accddee75..8dee4ce82679 100644 --- a/clang/lib/3C/3C.cpp +++ b/clang/lib/3C/3C.cpp @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// #include "clang/3C/3C.h" +#include "clang/3C/3CGlobalOptions.h" #include "clang/3C/ArrayBoundsInferenceConsumer.h" #include "clang/3C/ConstraintBuilder.h" #include "clang/3C/IntermediateToolHook.h" @@ -36,35 +37,9 @@ static cl::opt cl::desc("Dump array bounds inference graph"), cl::init(false), cl::cat(ArrBoundsInferCat)); -bool DumpIntermediate; -bool Verbose; -std::string OutputPostfix; -std::string OutputDir; -std::string ConstraintOutputJson; -std::vector AllocatorFunctions; -bool DumpStats; -bool HandleVARARGS; -bool EnablePropThruIType; -bool ConsiderAllocUnsafe; -std::string StatsOutputJson; -std::string WildPtrInfoJson; -std::string PerWildPtrInfoJson; -bool AllTypes; -std::string BaseDir; -bool AddCheckedRegions; -bool EnableCCTypeChecker; -bool WarnRootCause; -bool WarnAllRootCause; std::set FilePaths; -bool DumpUnwritableChanges; -bool AllowUnwritableChanges; -bool AllowRewriteFailures; -bool ItypesForExtern; -#ifdef FIVE_C -bool RemoveItypes; -bool ForceItypes; -#endif +struct _3COptions _3COpts; static CompilationDatabase *CurrCompDB = nullptr; static tooling::CommandLineArguments SourceFiles; @@ -194,7 +169,7 @@ class _3CASTBuilderAction : public ToolAction { // a LibTooling ArgumentsAdjuster, but we access the options in their parsed // data structure rather than as strings, so it is much more robust. - if (!EnableCCTypeChecker) + if (!_3COpts.EnableCCTypeChecker) // Corresponds to the -f3c-tool compiler option. Invocation->LangOpts->_3C = true; @@ -283,8 +258,8 @@ class _3CASTBuilderAction : public ToolAction { void dumpConstraintOutputJson(const std::string &PostfixStr, ProgramInfo &Info) { - if (DumpIntermediate) { - std::string FilePath = ConstraintOutputJson + PostfixStr + ".json"; + if (_3COpts.DumpIntermediate) { + std::string FilePath = _3COpts.ConstraintOutputJson + PostfixStr + ".json"; errs() << "Writing json output to:" << FilePath << "\n"; std::error_code Ec; llvm::raw_fd_ostream OutputJson(FilePath, Ec); @@ -300,7 +275,7 @@ void dumpConstraintOutputJson(const std::string &PostfixStr, void runSolver(ProgramInfo &Info, std::set &SourceFiles) { Constraints &CS = Info.getConstraints(); - if (Verbose) { + if (_3COpts.Verbose) { errs() << "Trying to capture Constraint Variables for all functions\n"; } @@ -311,7 +286,7 @@ void runSolver(ProgramInfo &Info, std::set &SourceFiles) { clock_t StartTime = clock(); CS.solve(); - if (Verbose) { + if (_3COpts.Verbose) { errs() << "Solver time:" << getTimeSpentInSeconds(StartTime) << "\n"; } } @@ -334,33 +309,8 @@ _3CInterface::_3CInterface(const struct _3COptions &CCopt, const std::vector &SourceFileList, CompilationDatabase *CompDB) { - DumpIntermediate = CCopt.DumpIntermediate; - Verbose = CCopt.Verbose; - OutputPostfix = CCopt.OutputPostfix; - OutputDir = CCopt.OutputDir; - ConstraintOutputJson = CCopt.ConstraintOutputJson; - StatsOutputJson = CCopt.StatsOutputJson; - WildPtrInfoJson = CCopt.WildPtrInfoJson; - PerWildPtrInfoJson = CCopt.PerPtrInfoJson; - DumpStats = CCopt.DumpStats; - HandleVARARGS = CCopt.HandleVARARGS; - EnablePropThruIType = CCopt.EnablePropThruIType; - BaseDir = CCopt.BaseDir; - AllTypes = CCopt.EnableAllTypes; - AddCheckedRegions = CCopt.AddCheckedRegions; - EnableCCTypeChecker = CCopt.EnableCCTypeChecker; - AllocatorFunctions = CCopt.AllocatorFunctions; - WarnRootCause = CCopt.WarnRootCause || CCopt.WarnAllRootCause; - WarnAllRootCause = CCopt.WarnAllRootCause; - DumpUnwritableChanges = CCopt.DumpUnwritableChanges; - AllowUnwritableChanges = CCopt.AllowUnwritableChanges; - AllowRewriteFailures = CCopt.AllowRewriteFailures; - ItypesForExtern = CCopt.ItypesForExtern; - -#ifdef FIVE_C - RemoveItypes = CCopt.RemoveItypes; - ForceItypes = CCopt.ForceItypes; -#endif + _3COpts = CCopt; + _3COpts.WarnRootCause |= _3COpts.WarnAllRootCause; llvm::InitializeAllTargets(); llvm::InitializeAllTargetMCs(); @@ -369,13 +319,14 @@ _3CInterface::_3CInterface(const struct _3COptions &CCopt, ConstraintsBuilt = false; - if (OutputPostfix != "-" && !OutputDir.empty()) { + if (_3COpts.OutputPostfix != "-" && !_3COpts.OutputDir.empty()) { errs() << "3C initialization error: Cannot use both -output-postfix and " "-output-dir\n"; ConstructionFailed = true; return; } - if (OutputPostfix == "-" && OutputDir.empty() && SourceFileList.size() > 1) { + if (_3COpts.OutputPostfix == "-" && _3COpts.OutputDir.empty() && + SourceFileList.size() > 1) { errs() << "3C initialization error: Cannot specify more than one input " "file when output is to stdout\n"; ConstructionFailed = true; @@ -385,40 +336,41 @@ _3CInterface::_3CInterface(const struct _3COptions &CCopt, std::string TmpPath; std::error_code EC; - if (BaseDir.empty()) { - BaseDir = "."; + if (_3COpts.BaseDir.empty()) { + _3COpts.BaseDir = "."; } // Get the canonical path of the base directory. - TmpPath = BaseDir; - EC = tryGetCanonicalFilePath(BaseDir, TmpPath); + TmpPath = _3COpts.BaseDir; + EC = tryGetCanonicalFilePath(_3COpts.BaseDir, TmpPath); if (EC) { errs() << "3C initialization error: Failed to canonicalize base directory " - << "\"" << BaseDir << "\": " << EC.message() << "\n"; + << "\"" << _3COpts.BaseDir << "\": " << EC.message() << "\n"; ConstructionFailed = true; return; } - BaseDir = TmpPath; + _3COpts.BaseDir = TmpPath; - if (!OutputDir.empty()) { + if (!_3COpts.OutputDir.empty()) { // tryGetCanonicalFilePath will fail if the output dir doesn't exist yet, so // create it first. - EC = llvm::sys::fs::create_directories(OutputDir); + EC = llvm::sys::fs::create_directories(_3COpts.OutputDir); if (EC) { errs() << "3C initialization error: Failed to create output directory \"" - << OutputDir << "\": " << EC.message() << "\n"; + << _3COpts.OutputDir << "\": " << EC.message() << "\n"; ConstructionFailed = true; return; } - TmpPath = OutputDir; - EC = tryGetCanonicalFilePath(OutputDir, TmpPath); + TmpPath = _3COpts.OutputDir; + EC = tryGetCanonicalFilePath(_3COpts.OutputDir, TmpPath); if (EC) { errs() << "3C initialization error: Failed to canonicalize output " - << "directory \"" << OutputDir << "\": " << EC.message() << "\n"; + << "directory \"" << _3COpts.OutputDir << "\": " << EC.message() + << "\n"; ConstructionFailed = true; return; } - OutputDir = TmpPath; + _3COpts.OutputDir = TmpPath; } SourceFiles = SourceFileList; @@ -434,25 +386,26 @@ _3CInterface::_3CInterface(const struct _3COptions &CCopt, continue; } FilePaths.insert(AbsPath); - if (!filePathStartsWith(AbsPath, BaseDir)) { + if (!filePathStartsWith(AbsPath, _3COpts.BaseDir)) { errs() << "3C initialization " - << (OutputDir != "" || !CCopt.AllowSourcesOutsideBaseDir ? "error" - : "warning") + << (_3COpts.OutputDir != "" || !_3COpts.AllowSourcesOutsideBaseDir + ? "error" + : "warning") << ": File \"" << AbsPath << "\" specified on the command line is outside the base directory\n"; SawInputOutsideBaseDir = true; } } if (SawInputOutsideBaseDir) { - errs() << "The base directory is currently \"" << BaseDir + errs() << "The base directory is currently \"" << _3COpts.BaseDir << "\" and can be changed with the -base-dir option.\n"; - if (OutputDir != "") { + if (_3COpts.OutputDir != "") { ConstructionFailed = true; errs() << "When using -output-dir, input files outside the base " "directory cannot be handled because there is no way to " "compute their output paths.\n"; - } else if (!CCopt.AllowSourcesOutsideBaseDir) { + } else if (!_3COpts.AllowSourcesOutsideBaseDir) { ConstructionFailed = true; errs() << "You can use the -allow-sources-outside-base-dir option to " "temporarily downgrade this error to a warning.\n"; @@ -556,10 +509,10 @@ bool _3CInterface::solveConstraints() { assert(ConstraintsBuilt && "Constraints not yet built. We need to call " "build constraint before trying to solve them."); // 3. Solve constraints. - if (Verbose) + if (_3COpts.Verbose) errs() << "Solving constraints\n"; - if (DumpIntermediate) + if (_3COpts.DumpIntermediate) GlobalProgramInfo.dump(); auto &PStats = GlobalProgramInfo.getPerfStats(); @@ -568,16 +521,16 @@ bool _3CInterface::solveConstraints() { runSolver(GlobalProgramInfo, FilePaths); PStats.endConstraintSolverTime(); - if (Verbose) + if (_3COpts.Verbose) errs() << "Constraints solved\n"; - if (WarnRootCause) + if (_3COpts.WarnRootCause) GlobalProgramInfo.computeInterimConstraintState(FilePaths); - if (DumpIntermediate) + if (_3COpts.DumpIntermediate) dumpConstraintOutputJson(FINAL_OUTPUT_SUFFIX, GlobalProgramInfo); - if (AllTypes) { + if (_3COpts.AllTypes) { if (DebugArrSolver) GlobalProgramInfo.getABoundsInfo().dumpAVarGraph( "arr_bounds_initial.dot"); @@ -606,7 +559,7 @@ bool _3CInterface::solveConstraints() { if (!isSuccessfulSoFar()) return false; - if (AllTypes) { + if (_3COpts.AllTypes) { // Propagate data-flow information for Array pointers. GlobalProgramInfo.getABoundsInfo().performFlowAnalysis(&GlobalProgramInfo); @@ -662,33 +615,33 @@ bool _3CInterface::writeAllConvertedFilesToDisk() { } bool _3CInterface::dumpStats() { - if (AllTypes && DebugArrSolver) { + if (_3COpts.AllTypes && DebugArrSolver) { GlobalProgramInfo.getABoundsInfo().dumpAVarGraph("arr_bounds_final.dot"); } - if (DumpStats) { + if (_3COpts.DumpStats) { GlobalProgramInfo.printStats(FilePaths, llvm::errs(), true); GlobalProgramInfo.computeInterimConstraintState(FilePaths); std::error_code Ec; - llvm::raw_fd_ostream OutputJson(StatsOutputJson, Ec); + llvm::raw_fd_ostream OutputJson(_3COpts.StatsOutputJson, Ec); if (!OutputJson.has_error()) { GlobalProgramInfo.printStats(FilePaths, OutputJson, false, true); OutputJson.close(); } - std::string AggregateStats = StatsOutputJson + ".aggregate.json"; + std::string AggregateStats = _3COpts.StatsOutputJson + ".aggregate.json"; llvm::raw_fd_ostream AggrJson(AggregateStats, Ec); if (!AggrJson.has_error()) { GlobalProgramInfo.printAggregateStats(FilePaths, AggrJson); AggrJson.close(); } - llvm::raw_fd_ostream WildPtrInfo(WildPtrInfoJson, Ec); + llvm::raw_fd_ostream WildPtrInfo(_3COpts.WildPtrInfoJson, Ec); if (!WildPtrInfo.has_error()) { GlobalProgramInfo.getInterimConstraintState().printStats(WildPtrInfo); WildPtrInfo.close(); } - llvm::raw_fd_ostream PerWildPtrInfo(PerWildPtrInfoJson, Ec); + llvm::raw_fd_ostream PerWildPtrInfo(_3COpts.PerWildPtrInfoJson, Ec); if (!PerWildPtrInfo.has_error()) { GlobalProgramInfo.getInterimConstraintState().printRootCauseStats( PerWildPtrInfo, GlobalProgramInfo.getConstraints()); diff --git a/clang/lib/3C/ArrayBoundsInferenceConsumer.cpp b/clang/lib/3C/ArrayBoundsInferenceConsumer.cpp index 2131fe51ee39..278aac39effe 100644 --- a/clang/lib/3C/ArrayBoundsInferenceConsumer.cpp +++ b/clang/lib/3C/ArrayBoundsInferenceConsumer.cpp @@ -894,7 +894,7 @@ void LengthVarInference::VisitArraySubscriptExpr(ArraySubscriptExpr *ASE) { void handleArrayVariablesBoundsDetection(ASTContext *C, ProgramInfo &I, bool UseHeuristics) { // Run array bounds - for (auto FuncName : AllocatorFunctions) { + for (auto FuncName : _3COpts.AllocatorFunctions) { AllocatorSizeAssoc[FuncName] = {0}; } GlobalABVisitor GlobABV(C, I); diff --git a/clang/lib/3C/ConstraintBuilder.cpp b/clang/lib/3C/ConstraintBuilder.cpp index 653d26ce38b4..03cf8f714ef6 100644 --- a/clang/lib/3C/ConstraintBuilder.cpp +++ b/clang/lib/3C/ConstraintBuilder.cpp @@ -95,7 +95,7 @@ class InlineStructDetector { bool IsNamedInLineStruct = IsInLineStruct && LastRecordDecl->getNameAsString() != ""; if (IsInLineStruct && !IsNamedInLineStruct) { - if (!AllTypes) { + if (!_3COpts.AllTypes) { CVarOption CV = Info.getVariable(VD, Context); CB.constraintCVarToWild(CV, "Inline struct encountered."); } else { @@ -269,7 +269,8 @@ class FunctionVisitor : public RecursiveASTVisitor { constrainConsVarGeq(ParameterDC, ArgumentConstraints.first, CS, &PL, CA, false, &Info, false); - if (AllTypes && TFD != nullptr && I < TFD->getNumParams()) { + if (_3COpts.AllTypes && TFD != nullptr && + I < TFD->getNumParams()) { auto *PVD = TFD->getParamDecl(I); auto &CSBI = Info.getABoundsInfo().getCtxSensBoundsHandler(); // Here, we need to handle context-sensitive assignment. @@ -279,7 +280,7 @@ class FunctionVisitor : public RecursiveASTVisitor { } } else { // The argument passed to a function ith varargs; make it wild - if (HandleVARARGS) { + if (_3COpts.HandleVARARGS) { CB.constraintAllCVarsToWild(ArgumentConstraints.first, "Passing argument to a function " "accepting var args.", @@ -292,7 +293,7 @@ class FunctionVisitor : public RecursiveASTVisitor { // (https://github.com/correctcomputation/checkedc-clang/issues/549). constrainVarsTo(ArgumentConstraints.first, CS.getNTArr()); } - if (Verbose) { + if (_3COpts.Verbose) { std::string FuncName = TargetFV->getName(); errs() << "Ignoring function as it contains varargs:" << FuncName << "\n"; @@ -514,7 +515,7 @@ class ConstraintGenVisitor : public RecursiveASTVisitor { bool VisitFunctionDecl(FunctionDecl *D) { FullSourceLoc FL = Context->getFullLoc(D->getBeginLoc()); - if (Verbose) + if (_3COpts.Verbose) errs() << "Analyzing function " << D->getName() << "\n"; if (FL.isValid()) { // TODO: When would this ever be false? @@ -522,7 +523,7 @@ class ConstraintGenVisitor : public RecursiveASTVisitor { Stmt *Body = D->getBody(); FunctionVisitor FV = FunctionVisitor(Context, Info, D); FV.TraverseStmt(Body); - if (AllTypes) { + if (_3COpts.AllTypes) { // Only do this, if all types is enabled. LengthVarInference LVI(Info, Context, D); LVI.Visit(Body); @@ -530,7 +531,7 @@ class ConstraintGenVisitor : public RecursiveASTVisitor { } } - if (Verbose) + if (_3COpts.Verbose) errs() << "Done analyzing function\n"; return true; @@ -618,7 +619,7 @@ class VariableAdderVisitor : public RecursiveASTVisitor { void VariableAdderConsumer::HandleTranslationUnit(ASTContext &C) { Info.enterCompilationUnit(C); - if (Verbose) { + if (_3COpts.Verbose) { SourceManager &SM = C.getSourceManager(); FileID MainFileId = SM.getMainFileID(); const FileEntry *FE = SM.getFileEntryForID(MainFileId); @@ -635,7 +636,7 @@ void VariableAdderConsumer::HandleTranslationUnit(ASTContext &C) { VAV.TraverseDecl(D); } - if (Verbose) + if (_3COpts.Verbose) errs() << "Done analyzing\n"; Info.exitCompilationUnit(); @@ -644,7 +645,7 @@ void VariableAdderConsumer::HandleTranslationUnit(ASTContext &C) { void ConstraintBuilderConsumer::HandleTranslationUnit(ASTContext &C) { Info.enterCompilationUnit(C); - if (Verbose) { + if (_3COpts.Verbose) { SourceManager &SM = C.getSourceManager(); FileID MainFileId = SM.getMainFileID(); const FileEntry *FE = SM.getFileEntryForID(MainFileId); @@ -684,7 +685,7 @@ void ConstraintBuilderConsumer::HandleTranslationUnit(ASTContext &C) { SR.TraverseDecl(D); } - if (Verbose) + if (_3COpts.Verbose) errs() << "Done analyzing\n"; PStats.endConstraintBuilderTime(); diff --git a/clang/lib/3C/ConstraintResolver.cpp b/clang/lib/3C/ConstraintResolver.cpp index 88eb913b7573..0964e27e6938 100644 --- a/clang/lib/3C/ConstraintResolver.cpp +++ b/clang/lib/3C/ConstraintResolver.cpp @@ -106,8 +106,9 @@ static ConstAtom *analyzeAllocExpr(CallExpr *CE, Constraints &CS, ConstAtom *Ret = CS.getPtr(); Expr *E; - if (std::find(AllocatorFunctions.begin(), AllocatorFunctions.end(), - FuncName) != AllocatorFunctions.end() || + if (std::find(_3COpts.AllocatorFunctions.begin(), + _3COpts.AllocatorFunctions.end(), + FuncName) != _3COpts.AllocatorFunctions.end() || FuncName.compare("malloc") == 0) E = CE->getArg(0); else { @@ -646,7 +647,7 @@ CSetBkeyPair ConstraintResolver::getExprConstraintVars(Expr *E) { P->constrainToWild(Info.getConstraints(), Rsn, &PL); Ret = pairWithEmptyBkey({P}); } else { - if (Verbose) { + if (_3COpts.Verbose) { llvm::errs() << "WARNING! Initialization expression ignored: "; E->dump(llvm::errs(), *Context); llvm::errs() << "\n"; @@ -693,7 +694,7 @@ void ConstraintResolver::constrainLocalAssign(Stmt *TSt, Expr *LHS, Expr *RHS, // Only if all types are enabled and these are not pointers, then track // the assignment. - if (AllTypes) { + if (_3COpts.AllTypes) { if ((!containsValidCons(L.first) && !containsValidCons(R.first)) || !HandleBoundsKey) { ABI.handleAssignment(LHS, L.first, L.second, RHS, R.first, R.second, @@ -718,7 +719,7 @@ void ConstraintResolver::constrainLocalAssign(Stmt *TSt, DeclaratorDecl *D, if (V.hasValue()) constrainConsVarGeq(&V.getValue(), RHSCons.first, Info.getConstraints(), PLPtr, CAction, false, &Info, HandleBoundsKey); - if (AllTypes && !IgnoreBnds) { + if (_3COpts.AllTypes && !IgnoreBnds) { if (!HandleBoundsKey || (!(V.hasValue() && isValidCons(&V.getValue())) && !containsValidCons(RHSCons.first))) { auto &ABI = Info.getABoundsInfo(); diff --git a/clang/lib/3C/Constraints.cpp b/clang/lib/3C/Constraints.cpp index 83285bdd1d47..436a95c97339 100644 --- a/clang/lib/3C/Constraints.cpp +++ b/clang/lib/3C/Constraints.cpp @@ -64,7 +64,7 @@ bool Constraints::removeConstraint(Constraint *C) { // Check if we can add this constraint. This provides a global switch to // control what constraints we can add to our system. void Constraints::editConstraintHook(Constraint *C) { - if (!AllTypes) { + if (!_3COpts.AllTypes) { // Invalidate any pointer-type constraints. if (Geq *E = dyn_cast(C)) { if (!E->constraintIsChecked()) { @@ -236,7 +236,7 @@ doSolve(ConstraintsGraph &CG, // new WILD-ness. Conflicts.insert(VA); // Failure case. - if (Verbose) { + if (_3COpts.Verbose) { errs() << "Unsolvable constraints: "; VA->print(errs()); errs() << "="; @@ -352,7 +352,7 @@ bool Constraints::graphBasedSolve() { bool Res = doSolve(SolChkCG, Env, this, true, nullptr, Conflicts); // Now solve PtrType constraints - if (Res && AllTypes) { + if (Res && _3COpts.AllTypes) { Env.doCheckedSolve(false); bool RegularSolve = !(OnlyGreatestSol || OnlyLeastSol); diff --git a/clang/lib/3C/DeclRewriter.cpp b/clang/lib/3C/DeclRewriter.cpp index a0768d36097d..bd37b53c044f 100644 --- a/clang/lib/3C/DeclRewriter.cpp +++ b/clang/lib/3C/DeclRewriter.cpp @@ -37,7 +37,7 @@ void DeclRewriter::buildItypeDecl(PVConstraint *Defn, DeclaratorDecl *Decl, ProgramInfo &Info, ArrayBoundsRewriter &ABR) { const EnvironmentMap &Env = Info.getConstraints().getVariables(); bool IsTypedefVarUnchecked = - Defn->isTypedef() && (ItypesForExtern || + Defn->isTypedef() && (_3COpts.ItypesForExtern || !Defn->getTypedefVar()->isSolutionChecked(Env)); if (Defn->getFV()) { // This declaration is for a function pointer. Writing itypes on function @@ -122,7 +122,7 @@ void DeclRewriter::rewriteDecls(ASTContext &Context, ProgramInfo &Info, // Don't convert typedefs when -itype-for-extern is passed. Typedefs will // keep their unchecked type but function using the typedef will be given a // checked itype. - if (!ItypesForExtern && TD) { + if (!_3COpts.ItypesForExtern && TD) { auto PSL = PersistentSourceLoc::mkPSL(TD, Context); // Don't rewrite base types like int if (!TD->getUnderlyingType()->isBuiltinType()) { @@ -195,7 +195,8 @@ void DeclRewriter::rewriteDecls(ASTContext &Context, ProgramInfo &Info, bool IsExternGlobalVar = isa(D) && cast(D)->getFormalLinkage() == Linkage::ExternalLinkage; - if (ItypesForExtern && (isa(D) || IsExternGlobalVar)) { + if (_3COpts.ItypesForExtern && + (isa(D) || IsExternGlobalVar)) { // Give record fields and global variables itypes when using // -itypes-for-extern. Note that we haven't properly implemented // itypes for structures and globals. This just rewrites to an itype @@ -247,7 +248,7 @@ void DeclRewriter::rewrite(RSet &ToRewrite) { DeclReplacement *N = Pair.second; assert(N->getDecl() != nullptr); - if (Verbose) { + if (_3COpts.Verbose) { errs() << "Replacing type of decl:\n"; N->getDecl()->dump(); errs() << "with " << N->getReplacement() << "\n"; @@ -278,7 +279,7 @@ void DeclRewriter::rewriteTypedefDecl(TypedefDeclReplacement *TDR, // SourceLocations will be generated incorrectly if we rewrite it as a // normal multidecl. bool isInlineStruct(std::vector &InlineDecls) { - if (InlineDecls.size() >= 2 && AllTypes) + if (InlineDecls.size() >= 2 && _3COpts.AllTypes) return isa(InlineDecls[0]) && std::all_of(InlineDecls.begin() + 1, InlineDecls.end(), [](Decl *D) { return isa(D); }); @@ -798,7 +799,8 @@ void FunctionDeclBuilder::buildDeclVar(const FVComponentVariable *CV, bool CheckedSolution = CV->hasCheckedSolution(Info.getConstraints()); bool ItypeSolution = CV->hasItypeSolution(Info.getConstraints()); - if (ItypeSolution || (CheckedSolution && ItypesForExtern && !StaticFunc)) { + if (ItypeSolution || + (CheckedSolution && _3COpts.ItypesForExtern && !StaticFunc)) { buildItypeDecl(CV->getExternal(), Decl, Type, IType, RewriteParm, RewriteRet); return; diff --git a/clang/lib/3C/MappingVisitor.cpp b/clang/lib/3C/MappingVisitor.cpp index efc85254e89e..3d1f6219fc4a 100644 --- a/clang/lib/3C/MappingVisitor.cpp +++ b/clang/lib/3C/MappingVisitor.cpp @@ -28,7 +28,7 @@ bool MappingVisitor::VisitDeclStmt(DeclStmt *S) { Decl *D = nullptr; Stmt *So = nullptr; std::tie(So, D) = PSLtoSDT[PSL]; - if (So != nullptr && Verbose) { + if (So != nullptr && _3COpts.Verbose) { llvm::errs() << "\nOverriding "; S->dump(); llvm::errs() << "\n"; @@ -65,7 +65,7 @@ bool MappingVisitor::VisitDecl(Decl *D) { Decl *Do = nullptr; Stmt *S = nullptr; std::tie(S, Do) = PSLtoSDT[PSL]; - if (Do != nullptr && Verbose) { + if (Do != nullptr && _3COpts.Verbose) { llvm::errs() << "Overriding "; Do->dump(); llvm::errs() << " with "; diff --git a/clang/lib/3C/ProgramInfo.cpp b/clang/lib/3C/ProgramInfo.cpp index 54aaebc2e432..836586287090 100644 --- a/clang/lib/3C/ProgramInfo.cpp +++ b/clang/lib/3C/ProgramInfo.cpp @@ -169,7 +169,8 @@ void ProgramInfo::printAggregateStats(const std::set &F, for (auto &I : Variables) { ConstraintVariable *C = I.second; std::string FileName = I.first.getFileName(); - if (F.count(FileName) || FileName.find(BaseDir) != std::string::npos) { + if (F.count(FileName) || + FileName.find(_3COpts.BaseDir) != std::string::npos) { if (C->isForValidDecl()) { FoundVars.clear(); getVarsFromConstraint(C, FoundVars, Visited); @@ -236,8 +237,9 @@ void ProgramInfo::printAggregateStats(const std::set &F, void ProgramInfo::printStats(const std::set &F, raw_ostream &O, bool OnlySummary, bool JsonFormat) { if (!OnlySummary && !JsonFormat) { - O << "Enable itype propagation:" << EnablePropThruIType << "\n"; - O << "Sound handling of var args functions:" << HandleVARARGS << "\n"; + O << "Enable itype propagation:" << _3COpts.EnablePropThruIType << "\n"; + O << "Sound handling of var args functions:" << _3COpts.HandleVARARGS + << "\n"; } std::map> FilesToVars; CVarSet InSrcCVars, Visited; @@ -247,7 +249,8 @@ void ProgramInfo::printStats(const std::set &F, raw_ostream &O, // First, build the map and perform the aggregation. for (auto &I : Variables) { std::string FileName = I.first.getFileName(); - if (F.count(FileName) || FileName.find(BaseDir) != std::string::npos) { + if (F.count(FileName) || + FileName.find(_3COpts.BaseDir) != std::string::npos) { int VarC = 0; int PC = 0; int NtaC = 0; @@ -352,7 +355,7 @@ void ProgramInfo::printStats(const std::set &F, raw_ostream &O, O << "}},\n"; } - if (AllTypes) { + if (_3COpts.AllTypes) { if (JsonFormat) { O << "\"BoundsStats\":"; } @@ -375,7 +378,7 @@ void ProgramInfo::printStats(const std::set &F, raw_ostream &O, bool ProgramInfo::link() { // For every global symbol in all the global symbols that we have found // go through and apply rules for whether they are functions or variables. - if (Verbose) + if (_3COpts.Verbose) llvm::errs() << "Linking!\n"; // Equate the constraints for all global variables. @@ -387,7 +390,7 @@ bool ProgramInfo::link() { std::set::iterator I = C.begin(); std::set::iterator J = C.begin(); ++J; - if (Verbose) + if (_3COpts.Verbose) llvm::errs() << "Global variables:" << V.first << "\n"; while (J != C.end()) { constrainConsVarGeq(*I, *J, CS, nullptr, Same_to_Same, true, this); @@ -662,7 +665,7 @@ void ProgramInfo::addVariable(clang::DeclaratorDecl *D, // constrain to WILD even if we don't end up storing this in the map. constrainWildIfMacro(PVExternal, PVD->getLocation()); // If this is "main", constrain its argv parameter to a nested arr - if (AllTypes && FuncName == "main" && FD->isGlobal() && I == 1) { + if (_3COpts.AllTypes && FuncName == "main" && FD->isGlobal() && I == 1) { PVInternal->constrainOuterTo(CS, CS.getArr()); PVInternal->constrainIdxTo(CS, CS.getNTArr(), 1); } @@ -720,7 +723,7 @@ void ProgramInfo::addVariable(clang::DeclaratorDecl *D, void ProgramInfo::ensureNtCorrect(const QualType &QT, const ASTContext &C, PointerVariableConstraint *PV) { - if (AllTypes && !canBeNtArray(QT)) { + if (_3COpts.AllTypes && !canBeNtArray(QT)) { PV->constrainOuterTo(CS, CS.getArr(), true, true); } } diff --git a/clang/lib/3C/RewriteUtils.cpp b/clang/lib/3C/RewriteUtils.cpp index cc42c548a45f..128ef1ce060f 100644 --- a/clang/lib/3C/RewriteUtils.cpp +++ b/clang/lib/3C/RewriteUtils.cpp @@ -96,7 +96,7 @@ void rewriteSourceRange(Rewriter &R, const CharSourceRange &Range, // crashing with an assert fail. if (!RewriteSuccess) { clang::DiagnosticsEngine &DE = R.getSourceMgr().getDiagnostics(); - bool ReportError = ErrFail && !AllowRewriteFailures; + bool ReportError = ErrFail && !_3COpts.AllowRewriteFailures; reportCustomDiagnostic( DE, ReportError ? DiagnosticsEngine::Error : DiagnosticsEngine::Warning, @@ -117,10 +117,10 @@ void rewriteSourceRange(Rewriter &R, const CharSourceRange &Range, } static void emit(Rewriter &R, ASTContext &C, bool &StdoutModeEmittedMainFile) { - if (Verbose) + if (_3COpts.Verbose) errs() << "Writing files out\n"; - bool StdoutMode = (OutputPostfix == "-" && OutputDir.empty()); + bool StdoutMode = (_3COpts.OutputPostfix == "-" && _3COpts.OutputDir.empty()); SourceManager &SM = C.getSourceManager(); // Iterate over each modified rewrite buffer. for (auto Buffer = R.buffer_begin(); Buffer != R.buffer_end(); ++Buffer) { @@ -132,26 +132,26 @@ static void emit(Rewriter &R, ASTContext &C, bool &StdoutModeEmittedMainFile) { DiagnosticsEngine &DE = C.getDiagnostics(); DiagnosticsEngine::Level UnwritableChangeDiagnosticLevel = - AllowUnwritableChanges ? DiagnosticsEngine::Warning - : DiagnosticsEngine::Error; + _3COpts.AllowUnwritableChanges ? DiagnosticsEngine::Warning + : DiagnosticsEngine::Error; auto PrintExtraUnwritableChangeInfo = [&]() { // With -dump-unwritable-changes and not -allow-unwritable-changes, we // want the -allow-unwritable-changes note before the dump. - if (!DumpUnwritableChanges) { + if (!_3COpts.DumpUnwritableChanges) { reportCustomDiagnostic( DE, DiagnosticsEngine::Note, "use the -dump-unwritable-changes option to see the new version " "of the file", SourceLocation()); } - if (!AllowUnwritableChanges) { + if (!_3COpts.AllowUnwritableChanges) { reportCustomDiagnostic( DE, DiagnosticsEngine::Note, "you can use the -allow-unwritable-changes option to temporarily " "downgrade this error to a warning", SourceLocation()); } - if (DumpUnwritableChanges) { + if (_3COpts.DumpUnwritableChanges) { errs() << "=== Beginning of new version of " << FE->getName() << " ===\n"; Buffer->second.write(errs()); @@ -240,7 +240,7 @@ static void emit(Rewriter &R, ASTContext &C, bool &StdoutModeEmittedMainFile) { // because stdout mode is handled above. OutputPostfix defaults to "-" // when it's not provided, so any other value means that we should use // OutputPostfix. Otherwise, we must be in OutputDir mode. - if (OutputPostfix != "-") { + if (_3COpts.OutputPostfix != "-") { // That path should be the same as the old one, with a // suffix added between the file name and the extension. // For example \foo\bar\a.c should become \foo\bar\a.checked.c @@ -250,19 +250,20 @@ static void emit(Rewriter &R, ASTContext &C, bool &StdoutModeEmittedMainFile) { std::string FileName = sys::path::remove_leading_dotslash(PfName).str(); std::string Ext = sys::path::extension(FileName).str(); std::string Stem = sys::path::stem(FileName).str(); - NFile = Stem + "." + OutputPostfix + Ext; + NFile = Stem + "." + _3COpts.OutputPostfix + Ext; if (!DirName.empty()) NFile = DirName + sys::path::get_separator().str() + NFile; } else { - assert(!OutputDir.empty()); + assert(!_3COpts.OutputDir.empty()); // If this does not hold when OutputDir is set, it should have been a // fatal error in the _3CInterface constructor. - assert(filePathStartsWith(FeAbsS, BaseDir)); + assert(filePathStartsWith(FeAbsS, _3COpts.BaseDir)); // replace_path_prefix is not smart about separators, but this should be // OK because tryGetCanonicalFilePath should ensure that neither BaseDir // nor OutputDir has a trailing separator. SmallString<255> Tmp(FeAbsS); - llvm::sys::path::replace_path_prefix(Tmp, BaseDir, OutputDir); + llvm::sys::path::replace_path_prefix(Tmp, _3COpts.BaseDir, + _3COpts.OutputDir); NFile = std::string(Tmp.str()); EC = llvm::sys::fs::create_directories(sys::path::parent_path(NFile)); if (EC) { @@ -278,7 +279,7 @@ static void emit(Rewriter &R, ASTContext &C, bool &StdoutModeEmittedMainFile) { raw_fd_ostream Out(NFile, EC, sys::fs::F_None); if (!EC) { - if (Verbose) + if (_3COpts.Verbose) errs() << "writing out " << NFile << "\n"; Buffer->second.write(Out); } else { @@ -594,7 +595,7 @@ void RewriteConsumer::emitRootCauseDiagnostics(ASTContext &Context) { // or are in the main file of the TU. Alternatively, don't filter causes // if -warn-all-root-cause is passed. int PtrCount = I.getNumPtrsAffected(WReason.first); - if (WarnAllRootCause || SM.isInMainFile(SL) || PtrCount > 1) { + if (_3COpts.WarnAllRootCause || SM.isInMainFile(SL) || PtrCount > 1) { // SL is invalid when the File is not in the current translation unit. if (SL.isValid()) { EmittedDiagnostics.insert(PSL); @@ -611,7 +612,7 @@ void RewriteConsumer::HandleTranslationUnit(ASTContext &Context) { Info.getPerfStats().startRewritingTime(); - if (WarnRootCause) + if (_3COpts.WarnRootCause) emitRootCauseDiagnostics(Context); // Rewrite Variable declarations @@ -621,7 +622,8 @@ void RewriteConsumer::HandleTranslationUnit(ASTContext &Context) { // Take care of some other rewriting tasks std::set Seen; std::map NodeMap; - CheckedRegionFinder CRF(&Context, R, Info, Seen, NodeMap, WarnRootCause); + CheckedRegionFinder CRF(&Context, R, Info, Seen, NodeMap, + _3COpts.WarnRootCause); CheckedRegionAdder CRA(&Context, R, NodeMap, Info); CastLocatorVisitor CLV(&Context); CastPlacementVisitor ECPV(&Context, Info, R, CLV.getExprsWithCast()); @@ -629,7 +631,7 @@ void RewriteConsumer::HandleTranslationUnit(ASTContext &Context) { TypeArgumentAdder TPA(&Context, Info, R); TranslationUnitDecl *TUD = Context.getTranslationUnitDecl(); for (const auto &D : TUD->decls()) { - if (AddCheckedRegions) { + if (_3COpts.AddCheckedRegions) { // Adding checked regions enabled? // TODO: Should checked region finding happen somewhere else? This is // supposed to be rewriting. diff --git a/clang/lib/3C/Utils.cpp b/clang/lib/3C/Utils.cpp index 3dada2ad08df..69cb1cfdb28b 100644 --- a/clang/lib/3C/Utils.cpp +++ b/clang/lib/3C/Utils.cpp @@ -182,8 +182,9 @@ bool functionHasVarArgs(clang::FunctionDecl *FD) { } bool isFunctionAllocator(std::string FuncName) { - return std::find(AllocatorFunctions.begin(), AllocatorFunctions.end(), - FuncName) != AllocatorFunctions.end() || + return std::find(_3COpts.AllocatorFunctions.begin(), + _3COpts.AllocatorFunctions.end(), + FuncName) != _3COpts.AllocatorFunctions.end() || llvm::StringSwitch(FuncName) .Cases("malloc", "calloc", "realloc", true) .Default(false); @@ -372,7 +373,7 @@ bool canWrite(const std::string &FilePath) { return true; // Get the absolute path of the file and check that // the file path starts with the base directory. - return filePathStartsWith(FilePath, BaseDir); + return filePathStartsWith(FilePath, _3COpts.BaseDir); } bool isInSysHeader(clang::Decl *D) { diff --git a/clang/tools/3c/3CStandalone.cpp b/clang/tools/3c/3CStandalone.cpp index 67befeab9dd1..164a18749114 100644 --- a/clang/tools/3c/3CStandalone.cpp +++ b/clang/tools/3c/3CStandalone.cpp @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// #include "clang/3C/3C.h" +#include "clang/3C/3CGlobalOptions.h" #include "clang/Tooling/CommonOptionsParser.h" #include "llvm/Support/Signals.h" #include "llvm/Support/TargetSelect.h" @@ -291,9 +292,9 @@ int main(int argc, const char **argv) { CcOptions.ConstraintOutputJson = OptConstraintOutputJson.getValue(); CcOptions.StatsOutputJson = OptStatsOutputJson.getValue(); CcOptions.WildPtrInfoJson = OptWildPtrInfoJson.getValue(); - CcOptions.PerPtrInfoJson = OptPerPtrWILDInfoJson.getValue(); + CcOptions.PerWildPtrInfoJson = OptPerPtrWILDInfoJson.getValue(); CcOptions.AddCheckedRegions = OptAddCheckedRegions; - CcOptions.EnableAllTypes = OptAllTypes; + CcOptions.AllTypes = OptAllTypes; CcOptions.EnableCCTypeChecker = OptEnableCCTypeChecker; CcOptions.WarnRootCause = OptWarnRootCause; CcOptions.WarnAllRootCause = OptWarnAllRootCause; From 07c5311730f44050ea2ef0a8181d4415036fa189 Mon Sep 17 00:00:00 2001 From: John Kastner Date: Thu, 2 Sep 2021 11:04:15 -0400 Subject: [PATCH 24/38] Generate itype string from AST when extracting from source fails (#693) Fixes an assertion failure (correctcomputation/checkedc-clang#594) when attempting to extract the string representation of an itype expression from the original source code if the itype expression is inside a macro. The string representation for itypes in macros is now re-generated from the AST. The fix in its current form leads to some unnecessary expansion of macros. This is discussed in correctcomputation/checkedc-clang#694. --- clang/lib/3C/ConstraintVariables.cpp | 11 ++++++++- clang/test/3C/macro_itype.c | 34 ++++++++++++++++++++++++++++ clang/test/3C/params_in_macro.c | 9 +++----- 3 files changed, 47 insertions(+), 7 deletions(-) create mode 100644 clang/test/3C/macro_itype.c diff --git a/clang/lib/3C/ConstraintVariables.cpp b/clang/lib/3C/ConstraintVariables.cpp index 6d5751a4c423..a90c8d5103cf 100644 --- a/clang/lib/3C/ConstraintVariables.cpp +++ b/clang/lib/3C/ConstraintVariables.cpp @@ -280,7 +280,16 @@ PointerVariableConstraint::PointerVariableConstraint( SourceRange R = ITE->getSourceRange(); if (R.isValid()) { ItypeStr = getSourceText(R, C); - assert(ItypeStr.size() > 0); + } + + // ITE->isCompilerGenerated will be true when an itype expression is + // implied by a bounds expression on the declaration. When the itype + // expression is not generated by the compiler, but we failed to extract + // its string representation from the source, build the itype string + // from the AST. + if (!ITE->isCompilerGenerated() && ItypeStr.empty()) { + assert(!InteropType.getAsString().empty()); + ItypeStr = "itype(" + InteropType.getAsString() + ")"; } } } diff --git a/clang/test/3C/macro_itype.c b/clang/test/3C/macro_itype.c new file mode 100644 index 000000000000..fd138524d6fa --- /dev/null +++ b/clang/test/3C/macro_itype.c @@ -0,0 +1,34 @@ +// RUN: rm -rf %t* +// RUN: 3c -base-dir=%S -alltypes -addcr %s -- | FileCheck -match-full-lines -check-prefixes="CHECK_ALL","CHECK" %s +// RUN: 3c -base-dir=%S -addcr %s -- | FileCheck -match-full-lines -check-prefixes="CHECK_NOALL","CHECK" %s +// RUN: 3c -base-dir=%S -alltypes -addcr %s -- | %clang -c -fcheckedc-extension -x c -o /dev/null - +// RUN: 3c -base-dir=%S -alltypes -output-dir=%t.checked %s -- +// RUN: 3c -base-dir=%t.checked -alltypes %t.checked/macro_itype.c -- | diff %t.checked/macro_itype.c - + +// Example encountered while converting libjpeg. This triggered an assertion +// fail because the ItypeStr extracted from the source was empty. +#define macro0 int *a : itype(_Ptr); +struct s { macro0 }; +//CHECK: struct s { macro0 }; + +// Example from issue correctcomputation/checkedc-clang#594. +#define PARAM_DECL_WITH_ITYPE int *p : itype(_Ptr) +void foo(PARAM_DECL_WITH_ITYPE); +//CHECK: void foo(PARAM_DECL_WITH_ITYPE); + +// Just removing the assertion that failed on the above example caused this to +// rewrite incorrectly. The ItypeStr would be left empty, so first parameter +// would be rewritten to `int *b` even though the rewriter intended to give it +// an itype. If the parameter was then passed a checked pointer, there would be +// a Checked C compiler error. Ideally, 3C wouldn't need to change the +// declaration of `b` at all (see issue correctcomputation/checkedc-clang#694). +#define macro1 : itype(_Ptr) +void fn(int *b macro1, int *c) { +//CHECK: void fn(int *b : itype(_Ptr), _Ptr c) { + b = 1; +} +void caller() { + int *e; + //CHECK: _Ptr e = ((void *)0); + fn(e, 0); +} diff --git a/clang/test/3C/params_in_macro.c b/clang/test/3C/params_in_macro.c index 1850419d6fff..ceaabe58f967 100644 --- a/clang/test/3C/params_in_macro.c +++ b/clang/test/3C/params_in_macro.c @@ -11,14 +11,11 @@ typedef double mydouble; -// TODO: FunctionDeclBuilder::buildDeclVar should be able to handle an itype -// here, but currently the PointerConstraintVariable constructor asserts when it -// fails to retrieve the original source of the itype declaration. #define parms1 volatile mydouble d, void (*f)(void) -#define parms2 int *const y : count(7), _Ptr z +#define parms2 int *const y : count(7), _Ptr z, int *zz : itype(_Ptr) void test(parms1, int *x, parms2) {} -// CHECK: void test(volatile mydouble d, void (*f)(void), _Ptr x, int *const y : count(7), _Ptr z) {} +// CHECK: void test(volatile mydouble d, void (*f)(void), _Ptr x, int *const y : count(7), _Ptr z, int *zz : itype(_Ptr)) {} // Before the bug fix, we got: -// void test(, , _Ptr x, , ) {} +// void test(, , _Ptr x, , , ) {} From 584c6ba655ed2dce665f87a124115c1336acea7f Mon Sep 17 00:00:00 2001 From: John Kastner Date: Wed, 8 Sep 2021 17:14:55 -0400 Subject: [PATCH 25/38] Add flag to infer checked pointers types for undefined functions (#691) Add a new flag (`-infer-types-for-undefs`) that enables inferring pointer types for undefined functions. Undefined functions are still constrained to be internally unchecked, so they can only be rewritten to itypes but never fully checked types. For example: `void foo(int *);` converts to `void foo(int * : itype(_Ptr));`. This change also eliminates some code that was previously duplicated inside `ProgramInfo::link()` for constraining undefined external and static functions. The root cause message emitted for undefined functions has been changed to use the same message for both static and external functions. --- clang/include/clang/3C/3CGlobalOptions.h | 2 + clang/include/clang/3C/ProgramInfo.h | 2 + clang/include/clang/3C/Utils.h | 2 + clang/lib/3C/ConstraintVariables.cpp | 41 +++++- clang/lib/3C/DeclRewriter.cpp | 9 +- clang/lib/3C/ProgramInfo.cpp | 98 +++++-------- clang/lib/3C/Utils.cpp | 8 +- clang/test/3C/implicit_casts_root_cause.c | 2 +- clang/test/3C/itype_undef.c | 162 ++++++++++++++++++++++ clang/test/3C/root_cause.c | 4 +- clang/tools/3c/3CStandalone.cpp | 19 +++ 11 files changed, 277 insertions(+), 72 deletions(-) create mode 100644 clang/test/3C/itype_undef.c diff --git a/clang/include/clang/3C/3CGlobalOptions.h b/clang/include/clang/3C/3CGlobalOptions.h index ab2748ea6e12..e092c126683b 100644 --- a/clang/include/clang/3C/3CGlobalOptions.h +++ b/clang/include/clang/3C/3CGlobalOptions.h @@ -67,6 +67,8 @@ struct _3COptions { bool AllowRewriteFailures; bool ItypesForExtern; + + bool InferTypesForUndefs; }; // NOLINTNEXTLINE(readability-identifier-naming) diff --git a/clang/include/clang/3C/ProgramInfo.h b/clang/include/clang/3C/ProgramInfo.h index 23dfe71c0712..167b1f8244a6 100644 --- a/clang/include/clang/3C/ProgramInfo.h +++ b/clang/include/clang/3C/ProgramInfo.h @@ -269,6 +269,8 @@ class ProgramInfo : public ProgramVariableAdder { // constraint system for that pointer type. void addVariable(clang::DeclaratorDecl *D, clang::ASTContext *AstContext) override; + + void linkFunction(FunctionVariableConstraint *FV); }; #endif diff --git a/clang/include/clang/3C/Utils.h b/clang/include/clang/3C/Utils.h index 441833b9bd4c..8bcccc7596f5 100644 --- a/clang/include/clang/3C/Utils.h +++ b/clang/include/clang/3C/Utils.h @@ -181,6 +181,8 @@ bool isInSysHeader(clang::Decl *D); std::string getSourceText(const clang::SourceRange &SR, const clang::ASTContext &C); +std::string getSourceText(const clang::CharSourceRange &SR, + const clang::ASTContext &C); // Find the longest common subsequence. unsigned longestCommonSubsequence(const char *Str1, const char *Str2, diff --git a/clang/lib/3C/ConstraintVariables.cpp b/clang/lib/3C/ConstraintVariables.cpp index a90c8d5103cf..47e408ee4603 100644 --- a/clang/lib/3C/ConstraintVariables.cpp +++ b/clang/lib/3C/ConstraintVariables.cpp @@ -556,12 +556,21 @@ std::string PointerVariableConstraint::tryExtractBaseType(DeclaratorDecl *D, QualType QT, const Type *Ty, const ASTContext &C) { - bool FoundBaseTypeInSrc = false; - if (D && !TSI) + // Implicit parameters declarations from typedef function declarations will + // still have valid and non-empty source ranges, but implicit declarations + // aren't written in the source, so extracting the base type from this range + // gives incorrect type strings. For example, the base type for the implicit + // parameter for `foo_decl` in `typedef void foo(int*); foo foo_decl;` would + // be extracted as "foo_decl", when it should be "int". + if (!D || D->isImplicit()) + return ""; + + if (!TSI) TSI = D->getTypeSourceInfo(); - if (!QT->isOrContainsCheckedType() && !Ty->getAs() && D && TSI) { + if (!QT->isOrContainsCheckedType() && !Ty->getAs() && TSI) { // Try to extract the type from original source to preserve defines TypeLoc TL = TSI->getTypeLoc(); + bool FoundBaseTypeInSrc = false; if (isa(D)) { FoundBaseTypeInSrc = D->getAsFunction()->getReturnType() == QT; TL = getBaseTypeLoc(TL).getAs(); @@ -2220,8 +2229,30 @@ FVComponentVariable::FVComponentVariable(const QualType &QT, // parameters similarly to how we do it for pointers in regular function // declarations. if (D && D->getType() == QT) { - SourceRange SR = D->getSourceRange(); - SourceDeclaration = SR.isValid() ? getSourceText(SR, C) : ""; + SourceRange DRange = D->getSourceRange(); + if (DRange.isValid()) { + const SourceManager &SM = C.getSourceManager(); + SourceLocation DLoc = D->getLocation(); + CharSourceRange CSR; + if (SM.isBeforeInTranslationUnit(DRange.getEnd(), DLoc)) { + // It's not clear to me why, but the end of the SourceRange for the + // declaration can come before the SourceLocation for the declaration. + // This can result in SourceDeclaration failing to capture the whole + // declaration string, causing rewriting errors. In this case it is also + // necessary to use CharSourceRange::getCharRange to work around some + // cases where CharSourceRange::getTokenRange (the default behavior of + // getSourceText when passed a SourceRange) would not get the full + // declaration. For example: a parameter declared without a name such as + // `_Ptr` was captured as `_Ptr`. + CSR = CharSourceRange::getCharRange(DRange.getBegin(), DLoc); + } else { + CSR = CharSourceRange::getTokenRange(DRange); + } + + SourceDeclaration = getSourceText(CSR , C); + } else { + SourceDeclaration = ""; + } } } diff --git a/clang/lib/3C/DeclRewriter.cpp b/clang/lib/3C/DeclRewriter.cpp index bd37b53c044f..6645b50d5f80 100644 --- a/clang/lib/3C/DeclRewriter.cpp +++ b/clang/lib/3C/DeclRewriter.cpp @@ -567,7 +567,14 @@ bool FunctionDeclBuilder::VisitFunctionDecl(FunctionDecl *FD) { // If this is an external function, there is no need to rewrite the // declaration. We cannot change the signature of external functions. - if (!FDConstraint->hasBody()) + // Under the flag -infer-types-for-undef, however, undefined functions do need + // to be rewritten. If the rest of the 3c inference and rewriting code is + // correct, short-circuiting here shouldn't be necessary; the rest of the + // logic in this function should successfully not rewrite undefined functions + // when -infer-types-for-undef is not passed. This assumption could be + // transformed into an assertion if we're confident it won't fail in too many + // places. + if (!_3COpts.InferTypesForUndefs && !FDConstraint->hasBody()) return true; // RewriteParams and RewriteReturn track if we will need to rewrite the diff --git a/clang/lib/3C/ProgramInfo.cpp b/clang/lib/3C/ProgramInfo.cpp index 836586287090..6cf40e3d9384 100644 --- a/clang/lib/3C/ProgramInfo.cpp +++ b/clang/lib/3C/ProgramInfo.cpp @@ -417,44 +417,8 @@ bool ProgramInfo::link() { // For every global function that is an unresolved external, constrain // its parameter types to be wild. Unless it has a bounds-safe annotation. - for (const auto &U : ExternalFunctionFVCons) { - std::string FuncName = U.first; - FVConstraint *G = U.second; - - // If there was a checked type on a variable in the input program, it - // should stay that way. Otherwise, we shouldn't be adding a checked type - // to an extern function. - std::string Rsn = (G->hasBody() ? "" - : "Unchecked pointer in parameter or " - "return of external function " + - FuncName); - - // Handle the cases where itype parameters should not be treated as their - // unchecked type. - // TODO: Ditto re getting a PSL (in the case in which Rsn is non-empty and - // it is actually used). - G->equateWithItype(*this, Rsn, nullptr); - - // If we've seen this symbol, but never seen a body for it, constrain - // everything about it. - // Some global symbols we don't need to constrain to wild, like - // malloc and free. Check those here and skip if we find them. - if (!G->hasBody()) { - const FVComponentVariable *Ret = G->getCombineReturn(); - Ret->getInternal()->constrainToWild(CS, Rsn); - if (!Ret->getExternal()->srcHasItype() && - !Ret->getExternal()->isGeneric()) - Ret->getExternal()->constrainToWild(CS, Rsn); - - for (unsigned I = 0; I < G->numParams(); I++) { - const FVComponentVariable *Param = G->getCombineParam(I); - Param->getInternal()->constrainToWild(CS, Rsn); - if (!Param->getExternal()->srcHasItype() && - !Param->getExternal()->isGeneric()) - Param->getExternal()->constrainToWild(CS, Rsn); - } - } - } + for (const auto &U : ExternalFunctionFVCons) + linkFunction(U.second); // Repeat for static functions. // @@ -462,33 +426,45 @@ bool ProgramInfo::link() { // error during compilation. They may still be useful as code is developed, // so we treat them as if they are external, and constrain parameters // to wild as appropriate. - for (const auto &U : StaticFunctionFVCons) { - for (const auto &V : U.second) { + for (const auto &U : StaticFunctionFVCons) + for (const auto &V : U.second) + linkFunction(V.second); - std::string FileName = U.first; - std::string FuncName = V.first; - FVConstraint *G = V.second; - - std::string Rsn = (G->hasBody() ? "" - : "Unchecked pointer in parameter or " - "return of static function " + - FuncName + " in " + FileName); - - // TODO: Ditto re getting a PSL - G->equateWithItype(*this, Rsn, nullptr); + return true; +} - if (!G->hasBody()) { +void ProgramInfo::linkFunction(FunctionVariableConstraint *FV) { + // If there was a checked type on a variable in the input program, it + // should stay that way. Otherwise, we shouldn't be adding a checked type + // to an undefined function. + std::string Rsn = (FV->hasBody() ? "" : "Unchecked pointer in parameter or " + "return of undefined function " + + FV->getName()); + + // Handle the cases where itype parameters should not be treated as their + // unchecked type. + // TODO: Ditto re getting a PSL (in the case in which Rsn is non-empty and + // it is actually used). + FV->equateWithItype(*this, Rsn, nullptr); + + // Used to apply constraints to parameters and returns for function without a + // body. In the default configuration, the function is fully constrained so + // that parameters and returns are considered unchecked. When 3C is run with + // --infer-types-for-undefs, only internal variables are constrained, allowing + // external variables to solve to checked types meaning the parameter will be + // rewritten to an itype. + auto LinkComponent = [this, Rsn](const FVComponentVariable *FVC) { + FVC->getInternal()->constrainToWild(CS, Rsn); + if (!_3COpts.InferTypesForUndefs && + !FVC->getExternal()->srcHasItype() && !FVC->getExternal()->isGeneric()) + FVC->getExternal()->constrainToWild(CS, Rsn); + }; - if (!G->getExternalReturn()->isGeneric()) - G->getExternalReturn()->constrainToWild(CS, Rsn); - for (unsigned I = 0; I < G->numParams(); I++) - if (!G->getExternalParam(I)->isGeneric()) - G->getExternalParam(I)->constrainToWild(CS, Rsn); - } - } + if (!FV->hasBody()) { + LinkComponent(FV->getCombineReturn()); + for (unsigned I = 0; I < FV->numParams(); I++) + LinkComponent(FV->getCombineParam(I)); } - - return true; } // Populate Variables, VarDeclToStatement, RVariables, and DepthMap with diff --git a/clang/lib/3C/Utils.cpp b/clang/lib/3C/Utils.cpp index 69cb1cfdb28b..09208228ad9a 100644 --- a/clang/lib/3C/Utils.cpp +++ b/clang/lib/3C/Utils.cpp @@ -387,11 +387,15 @@ bool isInSysHeader(clang::Decl *D) { std::string getSourceText(const clang::SourceRange &SR, const clang::ASTContext &C) { + return getSourceText(CharSourceRange::getTokenRange(SR), C); +} + +std::string getSourceText(const clang::CharSourceRange &SR, + const clang::ASTContext &C) { assert(SR.isValid() && "Invalid Source Range requested."); auto &SM = C.getSourceManager(); auto LO = C.getLangOpts(); - llvm::StringRef Srctxt = - Lexer::getSourceText(CharSourceRange::getTokenRange(SR), SM, LO); + llvm::StringRef Srctxt = Lexer::getSourceText(SR, SM, LO); return Srctxt.str(); } diff --git a/clang/test/3C/implicit_casts_root_cause.c b/clang/test/3C/implicit_casts_root_cause.c index c3f1451c96d4..e9ebadc1a47a 100644 --- a/clang/test/3C/implicit_casts_root_cause.c +++ b/clang/test/3C/implicit_casts_root_cause.c @@ -11,7 +11,7 @@ void test_no_cause() { has_void(c); } -void has_float(float* v); // expected-warning {{Unchecked pointer in parameter or return of external function has_float}} +void has_float(float* v); // expected-warning {{Unchecked pointer in parameter or return of undefined function has_float}} void test_float_cause() { int *b, *c; has_float(b); // expected-warning {{1 unchecked pointer: Cast from int * to float *}} diff --git a/clang/test/3C/itype_undef.c b/clang/test/3C/itype_undef.c new file mode 100644 index 000000000000..e32006226f91 --- /dev/null +++ b/clang/test/3C/itype_undef.c @@ -0,0 +1,162 @@ +// RUN: rm -rf %t* +// RUN: 3c -base-dir=%S --infer-types-for-undefs -alltypes -addcr %s -- | FileCheck -match-full-lines -check-prefixes="CHECK_ALL","CHECK" %s +// RUN: 3c -base-dir=%S --infer-types-for-undefs -addcr %s -- | FileCheck -match-full-lines -check-prefixes="CHECK_NOALL","CHECK" %s +// RUN: 3c -base-dir=%S --infer-types-for-undefs -alltypes -addcr %s -- | %clang -c -fcheckedc-extension -x c -o /dev/null - +// RUN: 3c -base-dir=%S --infer-types-for-undefs -alltypes -output-dir=%t.checked %s -- +// RUN: 3c -base-dir=%t.checked --infer-types-for-undefs -alltypes %t.checked/itype_undef.c -- | diff %t.checked/itype_undef.c - + +// Basic checks for adding itypes on undefined functions. +void test0(int *a); +//CHECK: void test0(int *a : itype(_Ptr)); + +int *test1(); +//CHECK: int *test1(void) : itype(_Ptr); + +void caller() { +//CHECK: void caller() _Checked { + int *a = 0; + //CHECK: _Ptr a = 0; + test0(a); + + // This block is left unchecked because it calls a function without either a + // defintion or a prototype even though a `void` prototype is added durring + // conversion. Checked region insertion could be improved to recognize this + // as safe. + { + // CHECK: _Unchecked { + int *b = test1(); + //CHECK: _Ptr b = test1(); + } +} + +// Check for undef functions with existing checked types/itypes. +void test2(int* a : itype(_Ptr)); +int *test3(void) : itype(_Ptr); +void test4(_Ptr a); +_Ptr test5(void); +int *test6(void) : count(10); +void test7(int * : count(10)); +//CHECK: void test2(int* a : itype(_Ptr)); +//CHECK: int *test3(void) : itype(_Ptr); +//CHECK: void test4(_Ptr a); +//CHECK: _Ptr test5(void); +//CHECK: int *test6(void) : count(10); +//CHECK: void test7(int * : count(10)); + +void checked_caller() { +//CHECK_NOALL: void checked_caller() { +//CHECK_ALL: void checked_caller() _Checked { + int *a; + //CHECK: _Ptr a = ((void *)0); + test2(a); + + int *b = test3(); + //CHECK: _Ptr b = test3(); + + int *c; + //CHECK: _Ptr c = ((void *)0); + test4(c); + + int *d = test5(); + //CHECK: _Ptr d = test5(); + + int *e = test6(); + //CHECK_NOALL: int *e = test6(); + //CHECK_ALL: _Array_ptr e : count(10) = test6(); + + int *f; + //CHECK_NOALL: int *f; + //CHECK_ALL: _Array_ptr f : count(10) = ((void *)0); + test7(f); + + // Get 3C to infer the correct length for f and e. + for(int i = 0; i < 10; i++) { + f[i]; + e[i]; + } +} + +// Void pointers should still be fully unchecked unless there is an existing +// checked type/itype. +void test_void0(void *); +void *test_void1(); +//CHECK: void test_void0(void *); +//CHECK: void *test_void1(); +void void_caller() { + void * a = 0; + test_void0(a); + void *b = test_void1(); +} + +void test_void_itype(void * : itype(_Array_ptr)); +void test_void_count(void * : byte_count(0)); +void test_void_checked(_Array_ptr); +//CHECK: void test_void_itype(void * : itype(_Array_ptr)); +//CHECK: void test_void_count(void * : byte_count(0)); +//CHECK: void test_void_checked(_Array_ptr); + +// Pointer type and bounds inference should still work. +int *test_arr0(int n); +//CHECK_ALL: int *test_arr0(int n) : itype(_Array_ptr) count(n); +//CHECK_NOALL: int *test_arr0(int n) : itype(_Ptr); + +void test_arr1(int *a, int n); +//CHECK: void test_arr1(int *a : itype(_Ptr), int n); + +void arr_caller(int s) { +//CHECK_ALL: void arr_caller(int s) _Checked { + // Returns variables use the least solution, so this will cause test_arr0 to + // solve to _Array_ptr. + int *b = test_arr0(s); + //CHECK_ALL: _Array_ptr b : count(s) = test_arr0(s); + //CHECK_NOALL: int *b = test_arr0(s); + for (int i = 0; i < s; i++) + b[i]; + + // Parameter variables use the greatest solution, so test_arr1 will still + // solve to _Ptr. It might be a good idea to change the solution used for + // undefined functions so that we can infer _Array_ptr here and infer it a + // bound. + int *c = 0; + //CHECK_ALL: _Array_ptr c : count(s) = 0; + //CHECK_NOALL: int *c = 0; + test_arr1(c, s); + for (int i = 0; i < s; i++) + c[i]; +} + +// If all uses of the typedef are checked, the unchecked internal constraint +// variable of the undefined function should not make the typedef unchecked. + +typedef int *td0; +void typedef0(td0 a); +void typedef_caller0() { +//CHECK: typedef _Ptr td0; +//CHECK: void typedef0(int *a : itype(td0)); +//CHECK: void typedef_caller0() _Checked { + td0 b = 0; + typedef0(b); +} + +// If the typedef is otherwise unchecked, we should still give a useful +// checked itype to the typedef'ed parameter. + +typedef int *td1; +void typedef1(td1 a); +void typedef_caller1() { +//CHECK: typedef int *td1; +//CHECK: void typedef1(td1 a : itype(_Ptr)); +//CHECK: void typedef_caller1() { + td1 b = 1; + typedef1(b); +} + +// As expected, function typedefs aren't handled well, but they should generate +// valid code. +typedef void fn_typedef0(int *); +fn_typedef0 fntd_decl0; +//CHECK: void fntd_decl0(int * : itype(_Ptr)); + +typedef int *fn_typedef1(); +fn_typedef1 fntd_decl1; +//CHECK: int *fntd_decl1(void) : itype(_Ptr); diff --git a/clang/test/3C/root_cause.c b/clang/test/3C/root_cause.c index cbe244955108..318c1f94bbdc 100644 --- a/clang/test/3C/root_cause.c +++ b/clang/test/3C/root_cause.c @@ -6,7 +6,7 @@ // as including stdlib will create numerous root-cause warnings we don't want to deal with // unwritable-expected-warning@+2 {{0 unchecked pointers: Source code in non-writable file}} -// expected-warning@+1 {{Unchecked pointer in parameter or return of external function my_malloc}} +// expected-warning@+1 {{Unchecked pointer in parameter or return of undefined function my_malloc}} _Itype_for_any(T) void *my_malloc(unsigned long size) : itype(_Array_ptr) byte_count(size); @@ -64,7 +64,7 @@ void test1() { extern int *glob; // unwritable-expected-warning@+2 {{0 unchecked pointers: Source code in non-writable file}} -// expected-warning@+1 {{1 unchecked pointer: Unchecked pointer in parameter or return of external function glob_f}} +// expected-warning@+1 {{1 unchecked pointer: Unchecked pointer in parameter or return of undefined function glob_f}} int *glob_f(void); // unwritable-expected-warning@+1 {{0 unchecked pointers: Source code in non-writable file}} diff --git a/clang/tools/3c/3CStandalone.cpp b/clang/tools/3c/3CStandalone.cpp index 164a18749114..08a63c4d4979 100644 --- a/clang/tools/3c/3CStandalone.cpp +++ b/clang/tools/3c/3CStandalone.cpp @@ -236,6 +236,24 @@ static cl::opt OptItypesForExtern( "unsafe."), cl::init(false), cl::cat(_3CCategory)); +static cl::opt OptInferTypesForUndef( + "infer-types-for-undefs", + cl::desc("Enable type inference for undefined functions. Under this flag, " + "types for undefined functions are inferred according to the same " + "rules as defined functions with the caveat that an undefined " + "function will only solve to an itype and not a fully checked type. " + "Because 3c is not able to examine the body of the function, the " + "inferred pointer types (and array bounds) may not be consistent " + "with the actual implementation. By default, the Checked C compiler " + "trusts the declared itypes and will not detect a spatial memory " + "safety violation if the function is used in a way that is " + "consistent with the itypes but not the assumptions actually made " + "by the implementation. Thus, if you want to guarantee spatial " + "memory safety, you must manually check the inferred types against " + "your understanding of what the function actually does (or any " + "available documentation)."), + cl::init(false), cl::cat(_3CCategory)); + #ifdef FIVE_C static cl::opt OptRemoveItypes( "remove-itypes", @@ -302,6 +320,7 @@ int main(int argc, const char **argv) { CcOptions.AllowUnwritableChanges = OptAllowUnwritableChanges; CcOptions.AllowRewriteFailures = OptAllowRewriteFailures; CcOptions.ItypesForExtern = OptItypesForExtern; + CcOptions.InferTypesForUndefs = OptInferTypesForUndef; #ifdef FIVE_C CcOptions.RemoveItypes = OptRemoveItypes; From 5f700355aa57475ef556d338572138f268864f1a Mon Sep 17 00:00:00 2001 From: John Kastner Date: Fri, 10 Sep 2021 16:09:35 -0400 Subject: [PATCH 26/38] Insert itypes correctly for constant sized arrays (#702) Given the declaration of a global variable `int foo[10]` converted with `3c -itypes-for-extern -alltypes` the resulting declaration incorrectly moved the length of the array before identifier. This changes fixes the rewriting to correctly be `int foo[10] : itype(int _Checked[10])`. This also fixes itype insertion for pointers to constant size arrays. Another change is included to pass idempotence checks for the new tests added. This change expands the source range replaced for global variable declarations to include the itype or bounds declarations if present. --- clang/include/clang/3C/ConstraintVariables.h | 43 ++++++-- clang/include/clang/3C/RewriteUtils.h | 4 +- clang/include/clang/3C/Utils.h | 2 + clang/lib/3C/ConstraintVariables.cpp | 62 +++++++----- clang/lib/3C/DeclRewriter.cpp | 100 +++++++++---------- clang/lib/3C/RewriteUtils.cpp | 35 ++++--- clang/lib/3C/Utils.cpp | 25 +++++ clang/test/3C/generalize.c | 2 +- clang/test/3C/itypes_for_extern.c | 46 +++++++++ 9 files changed, 212 insertions(+), 107 deletions(-) diff --git a/clang/include/clang/3C/ConstraintVariables.h b/clang/include/clang/3C/ConstraintVariables.h index 71b8b109d0ea..9c1f9e36dbd2 100644 --- a/clang/include/clang/3C/ConstraintVariables.h +++ b/clang/include/clang/3C/ConstraintVariables.h @@ -52,6 +52,9 @@ struct MkStringOpts { }; #define MKSTRING_OPTS(...) PACK_OPTS(MkStringOpts, __VA_ARGS__) +// Name for function return, for debugging +#define RETVAR "$ret" + // Base class for ConstraintVariables. A ConstraintVariable can either be a // PointerVariableConstraint or a FunctionVariableConstraint. The difference // is that FunctionVariableConstraints have constraints on the return value @@ -66,9 +69,23 @@ class ConstraintVariable { ConstraintVariableKind Kind; protected: + // A string representation for the type of this variable. Note that for + // complex types (e.g., function pointer, constant sized arrays), you cannot + // concatenate the type string with an identifier and expect to obtain a valid + // variable declaration. std::string OriginalType; - // Underlying name of the C variable this ConstraintVariable represents. + // Underlying name of the C variable this ConstraintVariable represents. This + // is not always a valid C identifier. It will be empty if no name was given + // (e.g., some parameter declarations). It will be the predefined string + // "$ret" when the ConstraintVariable represents a function return. It may + // take other values if the ConstraintVariable does not represent a C + // variable (e.g., explict casts and compound literals) . std::string Name; + // The combination of the type and name of the represented C variable. The + // combination is handled by clang library routines, so complex types + // like function pointers and constant size are handled correctly. See + // comments on Name for when name should be a valid identifier. + std::string OriginalTypeWithName; // Set of constraint variables that have been constrained due to a // bounds-safe interface (itype). They are remembered as being constrained // so that later on we do not introduce a spurious constraint @@ -85,9 +102,15 @@ class ConstraintVariable { bool IsForDecl; // Only subclasses should call this - ConstraintVariable(ConstraintVariableKind K, std::string T, std::string N) - : Kind(K), OriginalType(T), Name(N), HasEqArgumentConstraints(false), - ValidBoundsKey(false), IsForDecl(false) {} + ConstraintVariable(ConstraintVariableKind K, std::string T, std::string N, + std::string TN) + : Kind(K), OriginalType(T), Name(N), OriginalTypeWithName(TN), + HasEqArgumentConstraints(false), ValidBoundsKey(false), + IsForDecl(false) {} + + ConstraintVariable(ConstraintVariableKind K, QualType QT, std::string N) + : ConstraintVariable(K, qtyToStr(QT), N, + qtyToStr(QT, N == RETVAR ? "" : N)) {} public: // Create a "for-rewriting" representation of this ConstraintVariable. @@ -166,6 +189,7 @@ class ConstraintVariable { // Get the original type string that can be directly // used for rewriting. std::string getRewritableOriginalTy() const; + std::string getOriginalTypeWithName() const; std::string getName() const { return Name; } void setValidDecl() { IsForDecl = true; } @@ -378,7 +402,7 @@ class PointerVariableConstraint : public ConstraintVariable { // other fields are initialized to default values. This is used to construct // variables for non-pointer expressions. PointerVariableConstraint(std::string Name) : - ConstraintVariable(PointerVariable, "", Name), FV(nullptr), + ConstraintVariable(PointerVariable, "", Name, ""), FV(nullptr), SrcHasItype(false), PartOfFuncPrototype(false), Parent(nullptr), SourceGenericIndex(-1), InferredGenericIndex(-1), IsZeroWidthArray(false), IsTypedef(false), @@ -530,8 +554,6 @@ class PointerVariableConstraint : public ConstraintVariable { }; typedef PointerVariableConstraint PVConstraint; -// Name for function return, for debugging -#define RETVAR "$ret" // This class contains a pair of PVConstraints that represent an internal and // external view of a variable for use as the parameter and return constraints @@ -622,9 +644,10 @@ class FunctionVariableConstraint : public ConstraintVariable { const clang::ASTContext &C); FunctionVariableConstraint(clang::TypedefDecl *D, ProgramInfo &I, const clang::ASTContext &C); - FunctionVariableConstraint(const clang::Type *Ty, clang::DeclaratorDecl *D, - std::string N, ProgramInfo &I, - const clang::ASTContext &C, + + FunctionVariableConstraint(const clang::QualType Ty, + clang::DeclaratorDecl *D, std::string N, + ProgramInfo &I, const clang::ASTContext &C, TypeSourceInfo *TSI = nullptr); PVConstraint *getExternalReturn() const { diff --git a/clang/include/clang/3C/RewriteUtils.h b/clang/include/clang/3C/RewriteUtils.h index 8a86efc5c69a..a571ba27e026 100644 --- a/clang/include/clang/3C/RewriteUtils.h +++ b/clang/include/clang/3C/RewriteUtils.h @@ -28,9 +28,7 @@ class DeclReplacement { std::string getReplacement() const { return Replacement; } - virtual SourceRange getSourceRange(SourceManager &SM) const { - return getDecl()->getSourceRange(); - } + virtual SourceRange getSourceRange(SourceManager &SM) const; // Discriminator for LLVM-style RTTI (dyn_cast<> et al.). enum DRKind { diff --git a/clang/include/clang/3C/Utils.h b/clang/include/clang/3C/Utils.h index 8bcccc7596f5..b14041381605 100644 --- a/clang/include/clang/3C/Utils.h +++ b/clang/include/clang/3C/Utils.h @@ -228,6 +228,8 @@ void getPrintfStringArgIndices(const clang::CallExpr *CE, int64_t getStmtIdWorkaround(const clang::Stmt *St, const clang::ASTContext &Context); +clang::SourceLocation getCheckedCAnnotationsEnd(const clang::Decl *D); + // Shortcut for the getCustomDiagID + Report sequence to report a custom // diagnostic as we currently do in 3C. // diff --git a/clang/lib/3C/ConstraintVariables.cpp b/clang/lib/3C/ConstraintVariables.cpp index 47e408ee4603..e288fb6aed28 100644 --- a/clang/lib/3C/ConstraintVariables.cpp +++ b/clang/lib/3C/ConstraintVariables.cpp @@ -46,6 +46,12 @@ std::string ConstraintVariable::getRewritableOriginalTy() const { return OrigTyString; } +std::string ConstraintVariable::getOriginalTypeWithName() const { + if (Name == RETVAR) + return getRewritableOriginalTy(); + return OriginalTypeWithName; +} + PointerVariableConstraint *PointerVariableConstraint::getWildPVConstraint( Constraints &CS, const std::string &Rsn, PersistentSourceLoc *PSL) { auto *WildPVC = new PointerVariableConstraint("wildvar"); @@ -121,9 +127,9 @@ PointerVariableConstraint *PointerVariableConstraint::addAtomPVConstraint( PointerVariableConstraint::PointerVariableConstraint( PointerVariableConstraint *Ot) : ConstraintVariable(ConstraintVariable::PointerVariable, Ot->OriginalType, - Ot->Name), BaseType(Ot->BaseType), Vars(Ot->Vars), - SrcVars(Ot->SrcVars), FV(Ot->FV), QualMap(Ot->QualMap), - ArrSizes(Ot->ArrSizes), ArrSizeStrs(Ot->ArrSizeStrs), + Ot->Name, Ot->OriginalTypeWithName), + BaseType(Ot->BaseType), Vars(Ot->Vars), SrcVars(Ot->SrcVars), FV(Ot->FV), + QualMap(Ot->QualMap), ArrSizes(Ot->ArrSizes), ArrSizeStrs(Ot->ArrSizeStrs), SrcHasItype(Ot->SrcHasItype), ItypeStr(Ot->ItypeStr), PartOfFuncPrototype(Ot->PartOfFuncPrototype), Parent(Ot), BoundsAnnotationStr(Ot->BoundsAnnotationStr), @@ -206,7 +212,7 @@ PointerVariableConstraint::PointerVariableConstraint( const ASTContext &C, std::string *InFunc, int ForceGenericIndex, bool PotentialGeneric, bool VarAtomForChecked, TypeSourceInfo *TSInfo, const QualType &ITypeT) - : ConstraintVariable(ConstraintVariable::PointerVariable, qtyToStr(QT), N), + : ConstraintVariable(ConstraintVariable::PointerVariable, QT, N), FV(nullptr), SrcHasItype(false), PartOfFuncPrototype(InFunc != nullptr), Parent(nullptr) { QualType QTy = QT; @@ -512,7 +518,7 @@ PointerVariableConstraint::PointerVariableConstraint( // tn fname = ..., // where tn is the typedef'ed type name. // There is possibly something more elegant to do in the code here. - FV = new FVConstraint(Ty, IsDeclTy ? D : nullptr, IsTypedef ? "" : N, I, C, + FV = new FVConstraint(QTy, IsDeclTy ? D : nullptr, IsTypedef ? "" : N, I, C, TSInfo); // Get a string representing the type without pointer and array indirection. @@ -965,8 +971,11 @@ PointerVariableConstraint::mkString(Constraints &CS, } // No space after itype. - if (!EmittedName && !UseName.empty()) - Ss << " " << UseName; + if (!EmittedName && !UseName.empty()) { + if (!StringRef(Ss.str()).endswith("*")) + Ss << " "; + Ss << UseName; + } // Final array dropping. if (!ConstArrs.empty()) { @@ -1004,10 +1013,10 @@ const CVarSet &PVConstraint::getArgumentConstraints() const { FunctionVariableConstraint::FunctionVariableConstraint(FVConstraint *Ot) : ConstraintVariable(ConstraintVariable::FunctionVariable, Ot->OriginalType, - Ot->getName()), ReturnVar(Ot->ReturnVar), - ParamVars(Ot->ParamVars), FileName(Ot->FileName), Hasproto(Ot->Hasproto), - Hasbody(Ot->Hasbody), IsStatic(Ot->IsStatic), Parent(Ot), - IsFunctionPtr(Ot->IsFunctionPtr), TypeParams(Ot->TypeParams) { + Ot->getName(), Ot->OriginalTypeWithName), + ReturnVar(Ot->ReturnVar), ParamVars(Ot->ParamVars), FileName(Ot->FileName), + Hasproto(Ot->Hasproto), Hasbody(Ot->Hasbody), IsStatic(Ot->IsStatic), + Parent(Ot), IsFunctionPtr(Ot->IsFunctionPtr), TypeParams(Ot->TypeParams) { this->HasEqArgumentConstraints = Ot->HasEqArgumentConstraints; } @@ -1019,22 +1028,23 @@ FunctionVariableConstraint::FunctionVariableConstraint(DeclaratorDecl *D, ProgramInfo &I, const ASTContext &C) : FunctionVariableConstraint( - D->getType().getTypePtr(), D, + D->getType(), D, D->getDeclName().isIdentifier() ? std::string(D->getName()) : "", I, C, D->getTypeSourceInfo()) {} FunctionVariableConstraint::FunctionVariableConstraint(TypedefDecl *D, ProgramInfo &I, const ASTContext &C) - : FunctionVariableConstraint(D->getUnderlyingType().getTypePtr(), nullptr, + : FunctionVariableConstraint(D->getUnderlyingType(), nullptr, D->getNameAsString(), I, C, D->getTypeSourceInfo()) {} FunctionVariableConstraint::FunctionVariableConstraint( - const Type *Ty, DeclaratorDecl *D, std::string N, ProgramInfo &I, + const QualType QT, DeclaratorDecl *D, std::string N, ProgramInfo &I, const ASTContext &Ctx, TypeSourceInfo *TSInfo) - : ConstraintVariable(ConstraintVariable::FunctionVariable, tyToStr(Ty), N), + : ConstraintVariable(ConstraintVariable::FunctionVariable, QT, N), Parent(nullptr) { + const Type *Ty = QT.getTypePtr(); QualType RT, RTIType; Hasproto = false; Hasbody = false; @@ -2004,8 +2014,10 @@ void PointerVariableConstraint::mergeDeclaration(ConstraintVariable *FromCV, "Merging error, pointer depth change"); Vars = NewVAtoms; SrcVars = NewSrcAtoms; - if (Name.empty()) + if (Name.empty()) { Name = From->Name; + OriginalTypeWithName = From->OriginalTypeWithName; + } SrcHasItype = SrcHasItype || From->SrcHasItype; if (!From->ItypeStr.empty()) ItypeStr = From->ItypeStr; @@ -2265,16 +2277,20 @@ void FVComponentVariable::equateWithItype(ProgramInfo &I, ? "Internal constraint for generic function declaration, " "for which 3C currently does not support re-solving." : ReasonUnchangeable; - bool HasBounds = ExternalConstraint->srcHasBounds(); bool HasItype = ExternalConstraint->srcHasItype(); // If the type cannot change at all (ReasonUnchangeable2 is set), then we - // constrain both the external and internal types to not change. Otherwise, if - // the variable has bounds, then we don't want the checked (external) portion - // of the type to change because that could blow away the bounds, but we still - // allow the internal type to change so that the type can change from an itype - // to fully checked. + // constrain both the external and internal types to not change. bool MustConstrainInternalType = !ReasonUnchangeable2.empty(); - if (HasItype && (MustConstrainInternalType || HasBounds)) { + // Otherwise, if a pointer is an array pointer with declared bounds or is a + // constant size array, then we want to ensure the external type continues to + // solve to ARR or NTARR; see the comment on + // ConstraintVariable::equateWithItype re how this is achieved. This avoids + // losing bounds on array pointers, and converting constant sized arrays into + // pointers. We still allow the internal type to change so that the type can + // change from an itype to fully checked. + bool MustBeArray = + ExternalConstraint->srcHasBounds() || ExternalConstraint->hasSomeSizedArr(); + if (HasItype && (MustConstrainInternalType || MustBeArray)) { ExternalConstraint->equateWithItype(I, ReasonUnchangeable2, PSL); if (ExternalConstraint != InternalConstraint) linkInternalExternal(I, false); diff --git a/clang/lib/3C/DeclRewriter.cpp b/clang/lib/3C/DeclRewriter.cpp index 6645b50d5f80..72ae4289df83 100644 --- a/clang/lib/3C/DeclRewriter.cpp +++ b/clang/lib/3C/DeclRewriter.cpp @@ -36,62 +36,59 @@ void DeclRewriter::buildItypeDecl(PVConstraint *Defn, DeclaratorDecl *Decl, std::string &Type, std::string &IType, ProgramInfo &Info, ArrayBoundsRewriter &ABR) { const EnvironmentMap &Env = Info.getConstraints().getVariables(); - bool IsTypedefVarUnchecked = + // True when the type of this variable is defined by a typedef, and the + // constraint variable representing the typedef solved to an unchecked type. + // In these cases, the typedef should be used in the unchecked part of the + // itype. The typedef is expanded using checked pointer types for the checked + // portion. In ItypesForExtern mode, typedefs are treated as unchecked because + // 3C will not rewrite the typedef to a checked type. Even if it solves to a + // checked type, it is not rewritten, so it remains unchecked in the converted + // code. + bool IsUncheckedTypedef = Defn->isTypedef() && (_3COpts.ItypesForExtern || !Defn->getTypedefVar()->isSolutionChecked(Env)); - if (Defn->getFV()) { - // This declaration is for a function pointer. Writing itypes on function - // pointers is a little bit harder since the original type string will not - // work for the unchecked portion of the itype. We need to generate the - // unchecked type from the PVConstraint. The last argument of this call - // tells mkString to generate a string using unchecked types instead of - // checked types. - if (Defn->isTypedef() && !IsTypedefVarUnchecked) - Type = Defn->mkString(Info.getConstraints(), - MKSTRING_OPTS(UnmaskTypedef = true, - ForItypeBase = true)); - else - Type = Defn->mkString(Info.getConstraints(), - MKSTRING_OPTS(ForItypeBase = true)); + // True if this variable is defined by a typedef, and the constraint variable + // representing the typedef solves to a checked type. Notably not the negation + // of IsUncheckedTypedef because both require the variable type be defined + // by a typedef. The checked typedef is expanded using unchecked types in the + // unchecked portion of the itype. The typedef is used directly in the checked + // portion of the itype. + bool IsCheckedTypedef = Defn->isTypedef() && !IsUncheckedTypedef; + + // It should in principle be possible to always generate the unchecked portion + // of the itype by going through mkString. For practical reason, this doesn't + // always work, so we instead use the original type string as generated by + // clang so that we emit valid syntax in more cases. For examples and + // discussion refer to issue correctcomputation/checkedc-clang#703. + if (IsCheckedTypedef || Defn->getFV()) { + // Generate the type string from PVC if we need to unmask a typedef, this is + // a function pointer, or this is a constant size array. When unmasking a + // typedef, the expansion of the typedef does not exist in the original + // source, so it must be constructed. For function pointers, a function + // pointer appearing in the unchecked portion of an itype must contain an + // extra set of parenthesis (e.g. `void ((*f)())` instead of `void (f*)()`) + // for the declaration to parse correctly. qtyToStr (which is used by + // getOriginalTypeWithName) does not support adding these parentheses. + Type = Defn->mkString(Info.getConstraints(), + MKSTRING_OPTS(UnmaskTypedef = IsCheckedTypedef, + ForItypeBase = true)); } else { - if (Defn->isTypedef() && !IsTypedefVarUnchecked) - Type = Defn->mkString(Info.getConstraints(), - MKSTRING_OPTS(UnmaskTypedef = true, - ForItypeBase = true, - EmitName = false)); + // In the remaining cases, the unchecked portion of the itype is just the + // original type of the pointer. The first branch tries to generate the type + // using the type and name for this specific declaration. This is important + // because it avoids changing parameter names, particularly in cases where + // multiple functions sharing the same name are defined in different + // translation units. + if (isa_and_nonnull(Decl) && !Decl->getName().empty()) + Type = qtyToStr(Decl->getType(), Decl->getNameAsString()); else - Type = Defn->getRewritableOriginalTy(); - - if (isa_and_nonnull(Decl)) { - if (Decl->getName().empty()) - Type += Defn->getName(); - else - Type += Decl->getNameAsString(); - } else { - std::string Name = Defn->getName(); - if (Name != RETVAR) - Type += Name; - } + Type = Defn->getOriginalTypeWithName(); } IType = " : itype("; - - if (IsTypedefVarUnchecked) { - // In -itypes-for-extern mode we do not rewrite typedefs to checked types. - // They are given a checked itype instead. The unchecked portion of the - // itype continues to use the original typedef, but the typedef in the - // checked portion is expanded and rewritten to use a checked type. This - // lets the typedef be used in unchecked code while still giving a checked - // type to the declaration so that it can be used in checked code. - // TODO: This could potentially be applied to typedef types even when the - // flag is not passed to limit spread of wildness through typedefs. - IType += Defn->mkString(Info.getConstraints(), - MKSTRING_OPTS(EmitName = false, ForItype = true, - UnmaskTypedef = true)); - } else { - IType += Defn->mkString(Info.getConstraints(), - MKSTRING_OPTS(EmitName = false, ForItype = true)); - } + IType += Defn->mkString(Info.getConstraints(), + MKSTRING_OPTS(EmitName = false, ForItype = true, + UnmaskTypedef = IsUncheckedTypedef)); IType += ")" + ABR.getBoundsString(Defn, Decl, true); } @@ -323,9 +320,8 @@ void DeclRewriter::rewriteSingleDecl(DeclReplacement *N, RSet &ToRewrite) { dyn_cast(N->getDecl()) || isSingleDeclaration(N); assert("Declaration is not a single declaration." && IsSingleDecl); // This is the easy case, we can rewrite it locally, at the declaration. - // TODO why do we call getDecl() and getSourceRange() directly, - // TODO as opposed to getSourceRange()? - SourceRange TR = N->getDecl()->getSourceRange(); + SourceManager &SM = N->getDecl()->getASTContext().getSourceManager(); + SourceRange TR = N->getSourceRange(SM); doDeclRewrite(TR, N); } diff --git a/clang/lib/3C/RewriteUtils.cpp b/clang/lib/3C/RewriteUtils.cpp index 128ef1ce060f..31bbc31e0422 100644 --- a/clang/lib/3C/RewriteUtils.cpp +++ b/clang/lib/3C/RewriteUtils.cpp @@ -428,6 +428,17 @@ class TypeArgumentAdder : public clang::RecursiveASTVisitor { } }; +SourceRange DeclReplacement::getSourceRange(SourceManager &SM) const { + SourceRange SR = getDecl()->getSourceRange(); + SourceLocation OldEnd = SR.getEnd(); + SourceLocation NewEnd = getCheckedCAnnotationsEnd(getDecl()); + if (NewEnd.isValid() && + (!OldEnd.isValid() || SM.isBeforeInTranslationUnit(OldEnd, NewEnd))) + SR.setEnd(NewEnd); + + return SR; +} + SourceRange FunctionDeclReplacement::getSourceRange(SourceManager &SM) const { SourceLocation Begin = RewriteGeneric ? getDeclBegin(SM) : (RewriteReturn ? getReturnBegin(SM) : getParamBegin(SM)); @@ -503,24 +514,12 @@ SourceLocation FunctionDeclReplacement::getDeclEnd(SourceManager &SM) const { } } - // If there's a bounds expression, this comes after the right paren of the - // function declaration parameter list. - if (auto *BoundsE = Decl->getBoundsExpr()) { - SourceLocation BoundsEnd = BoundsE->getEndLoc(); - if (BoundsEnd.isValid() && - (!End.isValid() || SM.isBeforeInTranslationUnit(End, BoundsEnd))) - End = BoundsEnd; - } - - // If there's an itype, this also comes after the right paren. In the case - // that there is both a bounds expression and an itype, we need check - // which is later in the file and use that as the declaration end. - if (auto *InteropE = Decl->getInteropTypeExpr()) { - SourceLocation InteropEnd = InteropE->getEndLoc(); - if (InteropEnd.isValid() && - (!End.isValid() || SM.isBeforeInTranslationUnit(End, InteropEnd))) - End = InteropEnd; - } + // If there's a bounds or interop type expression, this will come after the + // right paren of the function declaration parameter list. + SourceLocation AnnotationsEnd = getCheckedCAnnotationsEnd(Decl); + if (AnnotationsEnd.isValid() && + (!End.isValid() || SM.isBeforeInTranslationUnit(End, AnnotationsEnd))) + End = AnnotationsEnd; // SourceLocations are weird and turn up invalid for reasons I don't // understand. Fallback to extracting r paren location from source diff --git a/clang/lib/3C/Utils.cpp b/clang/lib/3C/Utils.cpp index 09208228ad9a..662cdd4c38ac 100644 --- a/clang/lib/3C/Utils.cpp +++ b/clang/lib/3C/Utils.cpp @@ -570,3 +570,28 @@ int64_t getStmtIdWorkaround(const Stmt *St, const ASTContext &Context) { // (alignof(Stmt) - 1) before dividing. return Context.getAllocator().identifyKnownObject(St); } + +// Get the SourceLocation for the end of any Checked C bounds or interop type +// annotations on a declaration. Returns an invalid source location if no +// Checked C annotations are present. +SourceLocation getCheckedCAnnotationsEnd(const Decl *D) { + SourceManager &SM = D->getASTContext().getSourceManager(); + SourceLocation End; + + // Update the current end SourceLocation to the new SourceLocation if the new + // location is valid and comes after the current end location. + auto UpdateEnd = [&SM, &End](SourceLocation SL) { + if (SL.isValid() && + (!End.isValid() || SM.isBeforeInTranslationUnit(End, SL))) + End = SL; + }; + + if (auto *DD = dyn_cast(D)) { + if (auto *InteropE = DD->getInteropTypeExpr()) + UpdateEnd(InteropE->getEndLoc()); + if (auto *BoundsE = DD->getBoundsExpr()) + UpdateEnd(BoundsE->getEndLoc()); + } + + return End; +} diff --git a/clang/test/3C/generalize.c b/clang/test/3C/generalize.c index 9a9badc7fc3a..2f48e88769ed 100644 --- a/clang/test/3C/generalize.c +++ b/clang/test/3C/generalize.c @@ -48,7 +48,7 @@ void nameless(void *a, char *b) { a = 1; // make it unsafe } -// CHECK: void nameless(void * a, _Ptr b); +// CHECK: void nameless(void *a, _Ptr b); // CHECK: void nameless(void *a, _Ptr b) // Safe functions should be upgraded from "_Itype_for_any" to "_For_any" diff --git a/clang/test/3C/itypes_for_extern.c b/clang/test/3C/itypes_for_extern.c index ee5dede2c5f9..46edee5f0649 100644 --- a/clang/test/3C/itypes_for_extern.c +++ b/clang/test/3C/itypes_for_extern.c @@ -92,3 +92,49 @@ void has_itype0(int *a : itype(_Ptr)) { a = 1; } void has_itype1(int *a : itype(_Ptr)) { a = 0; } //CHECK: void has_itype1(int *a : itype(_Ptr)) _Checked { a = 0; } + +// Test rewriting itypes for constant sized arrays. As with function pointers, +// part of the type (the array size) occurs after the name of the variable +// being declared. This complicates rewriting. These examples caused errors in +// libjpeg. + +int const_arr0[10]; +//CHECK_ALL: int const_arr0[10] : itype(int _Checked[10]); +//CHECK_NOALL: int const_arr0[10]; + +int *const_arr1[10]; +//CHECK_ALL: int *const_arr1[10] : itype(_Ptr _Checked[10]) = {((void *)0)}; +//CHECK_NOALL: int *const_arr1[10]; + +int (*const_arr2)[10]; +//CHECK_ALL: int (*const_arr2)[10] : itype(_Ptr) = ((void *)0); +//CHECK_NOALL: int (*const_arr2)[10] : itype(_Ptr) = ((void *)0); + +// Itypes for constants sized arrays when there is a declaration with and +// without a parameter list take slightly different paths that need to be +// tested. If there is no parameter list, then the unchecked component of the +// itype can't be copied from the declaration, and it instead must be generated +// from the constraint variable. + +void const_arr_fn(); +void const_arr_fn(int a[10]) {} +//CHECK_ALL: void const_arr_fn(int *a : itype(int _Checked[10])); +//CHECK_ALL: void const_arr_fn(int *a : itype(int _Checked[10])) _Checked {} + +// Rewriting an existing itype or bounds expression on a global variable. Doing +// this correctly requires replacing text until the end of the Checked C +// annotation expression. +int *a : itype(_Ptr); +int **b : itype(_Ptr); +int *c : count(2); +int **d : count(2); +int **e : itype(_Array_ptr) count(2); +int **f : count(2) itype(_Array_ptr); +int **g : count(2) itype(_Array_ptr) = 0; +//CHECK: int *a : itype(_Ptr); +//CHECK: int **b : itype(_Ptr<_Ptr>) = ((void *)0); +//CHECK: int *c : count(2); +//CHECK: int **d : itype(_Array_ptr<_Ptr>) count(2) = ((void *)0); +//CHECK: int **e : itype(_Array_ptr<_Ptr>) count(2) = ((void *)0); +//CHECK: int **f : itype(_Array_ptr<_Ptr>) count(2) = ((void *)0); +//CHECK: int **g : itype(_Array_ptr<_Ptr>) count(2) = 0; From 50e0a25c1130a1937d44b2c7fdbc5958a0ca547b Mon Sep 17 00:00:00 2001 From: "Matt McCutchen (Correct Computation)" Date: Mon, 13 Sep 2021 13:45:48 -0400 Subject: [PATCH 27/38] Add support for `-Xclang -ast-dump` to facilitate debugging. (#706) Add documentation for the feature, but first, split out a "developer guide" from CONTRIBUTING.md to have a good place to put the documentation and make some other minor revisions. --- clang/docs/checkedc/3C/CONTRIBUTING.md | 140 +++--------------------- clang/docs/checkedc/3C/development.md | 144 +++++++++++++++++++++++++ clang/lib/3C/3C.cpp | 49 +++++++++ clang/test/3C/ast_dump.c | 11 ++ 4 files changed, 219 insertions(+), 125 deletions(-) create mode 100644 clang/docs/checkedc/3C/development.md create mode 100644 clang/test/3C/ast_dump.c diff --git a/clang/docs/checkedc/3C/CONTRIBUTING.md b/clang/docs/checkedc/3C/CONTRIBUTING.md index 8443c201b5b9..ab534c153dd2 100644 --- a/clang/docs/checkedc/3C/CONTRIBUTING.md +++ b/clang/docs/checkedc/3C/CONTRIBUTING.md @@ -49,128 +49,18 @@ working, so you may have to wait for us to address 5C-specific problems arising from your 3C pull request and/or we may ask you to make specific changes to your pull request to accommodate 5C's code. -## Testing - -3C has a regression test suite located in `clang/test/3C`. At the -appropriate time during development of a pull request, please run it -and correct any failures. (For example, it may not make sense to run -it on a draft pull request containing an unfinished demonstration of -an idea.) The easiest way to run it is to run the following in your -build directory: - -``` -ninja check-3c -``` - -This command will build everything needed that hasn't already been -built, run the test suite, report success or failure (exit 0 or 1, so -you can use it in scripts), and display some information about any -failures, which may or may not be enough for you to understand what -went wrong. - -For deeper troubleshooting, run the following in your build directory -to build all dependencies of the test suite: - -``` -ninja check-3c-deps -``` - -Then run the following in the `clang/test/3C` directory: - -``` -llvm-lit -vv TEST.c -``` - -where `TEST.c` is the path of the test you want to run (you can also -specify more than one test). This assumes you've put the `bin` -subdirectory of your build directory on your `$PATH` or arranged some -other means of running `llvm-lit` from there. The first `-v` makes -`llvm-lit` display the stdout and stderr of failed tests; the second -makes it display the `RUN` commands as they execute so you can tell -which one failed. - -Every `.c` file under `clang/test/3C` is a test file. There are a few -in subdirectories, so `*.c` will not pick up all of them; instead you -can use `llvm-lit -vv .` to specify all test files under the current -directory. - -### Diagnostic verification - -3C supports the standard Clang diagnostic verifier -([`VerifyDiagnosticConsumer`](https://clang.llvm.org/doxygen/classclang_1_1VerifyDiagnosticConsumer.html#details)) -for testing errors and warnings reported by 3C via its main `DiagnosticsEngine`. -(Some 3C errors and warnings are reported via other means and cannot be tested -this way; the best solution we have for them right now is to `grep` the stderr -of 3C.) Diagnostic verification can be enabled via the usual `-Xclang -verify` -compiler option; other diagnostic verification options (`-Xclang --verify=PREFIX`, etc.) should also work as normal. These must be passed as -_compiler_ options, not `3c` options; for example, if you are using `--` on the -`3c` command line, these options must be passed _after_ the `--`. - -Some notes about diagnostic verification in the context of 3C: - -* Parsing of the source files uses some of the compiler logic and thus may - generate compiler warnings, just as if you ran `clang` on the code. These are - sent to the diagnostic verifier along with diagnostics generated by 3C's - analysis. If you find it distracting to have to include the compiler warnings - in the set of expected diagnostics for a test, you can turn them off via the - `-Wno-everything` compiler option (which does not affect diagnostics generated - by 3C's analysis). - -* The `3c` tool works in several passes, where each pass runs on all translation - units: first `3c` parses the source files, then it runs several passes of - analysis. If a pass encounters at least one error, `3c` exits at the end of - that pass. Diagnostic verification does not change the _point_ at which `3c` - exits, but it changes the exit _code_ to indicate the result of verification - rather than the presence of errors. The verification includes the diagnostics - from all passes up to the point at which `3c` exits (i.e., the same - diagnostics that would be displayed if verification were not used). However, - an error that doesn't go via the main `DiagnosticsEngine` will cause an - unsuccessful exit code regardless of diagnostic verification. (This is - typically the behavior you want for a test.) - -* Diagnostic verification is independent for each translation unit, so in tests - with multiple translation units, you'll have to be careful that preprocessing - of each translation unit sees the correct set of `expected-*` directives for - the diagnostics generated for that translation unit (or an - `expected-no-diagnostics` directive if that translation unit generates no - diagnostics, even if other translation units do generate diagnostics). Be - warned that since which translation unit generated a given diagnostic isn't - visible to a normal user, we don't put much work into coming up with sensible - rules for this, but it should at least be deterministic for testing. - -Note that some 3C tests use diagnostic verification on calls to `clang` rather -than `3c`, so if you see `expected-*` directives in a test, you can look at the -`RUN` commands to see which command has `-Xclang -verify` and is using the -directives. If you want to verify diagnostics of more than one `RUN` command in -the same test, you can use different directive prefixes (`-Xclang --verify=PREFIX`). - -## Coding guidelines - -Please follow [LLVM coding -standards](https://llvm.org/docs/CodingStandards.html#name-types-functions-variables-and-enumerators-properly) -in your code. Specifically: - -* The maximum length of a line: 80 chars - -* All comments should start with a Capital letter. - -* All Local variables, including fields and parameters, should start - with a Capital letter (similar to PascalCase). Short names are - preferred. - -* A space between the conditional keyword and `(` i.e., `if (`, - `while (`, ``for (` etc. - -* Space after the type name, i.e., `Type *K` _not_ `Type* K`. - -* Space before and after `:` in iterators, i.e., `for (auto &k : List)` - -Our goal is that all files should be formatted with `clang-format` and -pass `clang-tidy` ([more information](clang-tidy.md)), and nonempty -files should have a final newline (surprisingly, `clang-format` cannot -enforce this). However, until we have better automation, we decided it -isn't reasonable to require contributors to manually run these tools -and fix style nits in each change; instead, we periodically run the -tools on the entire 3C codebase. +At the appropriate time during development of a pull request, please +run the [regression tests](development.md#regression-tests) and +correct any failures. (For example, it may not make sense to do this +on a draft pull request containing an unfinished demonstration of an +idea.) All regression tests must pass (or be disabled if appropriate) +before your pull request can be merged. If you're changing behavior +(as opposed to just cleaning up the code), we'll typically require you +to add or update enough tests to exercise the important behavior +changes (i.e., those tests fail before your code change and pass after +it). If there's a concern that your change might affect other cases +that are not adequately tested yet, we may ask you to add tests for +those cases as well. + +See the [developer's guide](development.md) for additional information +that may be helpful as you work on 3C. diff --git a/clang/docs/checkedc/3C/development.md b/clang/docs/checkedc/3C/development.md new file mode 100644 index 000000000000..97f899760910 --- /dev/null +++ b/clang/docs/checkedc/3C/development.md @@ -0,0 +1,144 @@ +# 3C Developer's Guide + +This file collects information that is important or useful to know +when developing 3C as well as some standards we generally expect the +code to follow. (Some other standards are covered in the [contribution +guide](CONTRIBUTING.md)). If you could use help with something that's +not documented here, feel free to ask. + +## Regression tests + +3C has a regression test suite located in `clang/test/3C`. The easiest +way to run it is to run the following in your build directory: + +``` +ninja check-3c +``` + +This command will build everything needed that hasn't already been +built, run the test suite, report success or failure (exit 0 or 1, so +you can use it in scripts), and display some information about any +failures, which may or may not be enough for you to understand what +went wrong. + +For deeper troubleshooting, run the following in your build directory +to build all dependencies of the test suite: + +``` +ninja check-3c-deps +``` + +Then run the following in the `clang/test/3C` directory: + +``` +llvm-lit -vv TEST.c +``` + +where `TEST.c` is the path of the test you want to run (you can also +specify more than one test). This assumes you've put the `bin` +subdirectory of your build directory on your `$PATH` or arranged some +other means of running `llvm-lit` from there. The first `-v` makes +`llvm-lit` display the stdout and stderr of failed tests; the second +makes it display the `RUN` commands as they execute so you can tell +which one failed. + +Every `.c` file under `clang/test/3C` is a test file. There are a few +in subdirectories, so `*.c` will not pick up all of them; instead you +can use `llvm-lit -vv .` to specify all test files under the current +directory. + +### Diagnostic verification + +3C supports the standard Clang diagnostic verifier +([`VerifyDiagnosticConsumer`](https://clang.llvm.org/doxygen/classclang_1_1VerifyDiagnosticConsumer.html#details)) +for testing errors and warnings reported by 3C via its main `DiagnosticsEngine`. +(Some 3C errors and warnings are reported via other means and cannot be tested +this way; the best solution we have for them right now is to `grep` the stderr +of 3C.) Diagnostic verification can be enabled via the usual `-Xclang -verify` +compiler option; other diagnostic verification options (`-Xclang +-verify=PREFIX`, etc.) should also work as normal. These must be passed as +_compiler_ options, not `3c` options; for example, if you are using `--` on the +`3c` command line, these options must be passed _after_ the `--`. + +Some notes about diagnostic verification in the context of 3C: + +* Parsing of the source files uses some of the compiler logic and thus may + generate compiler warnings, just as if you ran `clang` on the code. These are + sent to the diagnostic verifier along with diagnostics generated by 3C's + analysis. If you find it distracting to have to include the compiler warnings + in the set of expected diagnostics for a test, you can turn them off via the + `-Wno-everything` compiler option (which does not affect diagnostics generated + by 3C's analysis). + +* The `3c` tool works in several passes, where each pass runs on all translation + units: first `3c` parses the source files, then it runs several passes of + analysis. If a pass encounters at least one error, `3c` exits at the end of + that pass. Diagnostic verification does not change the _point_ at which `3c` + exits, but it changes the exit _code_ to indicate the result of verification + rather than the presence of errors. The verification includes the diagnostics + from all passes up to the point at which `3c` exits (i.e., the same + diagnostics that would be displayed if verification were not used). However, + an error that doesn't go via the main `DiagnosticsEngine` will cause an + unsuccessful exit code regardless of diagnostic verification. (This is + typically the behavior you want for a test.) + +* Diagnostic verification is independent for each translation unit, so in tests + with multiple translation units, you'll have to be careful that preprocessing + of each translation unit sees the correct set of `expected-*` directives for + the diagnostics generated for that translation unit (or an + `expected-no-diagnostics` directive if that translation unit generates no + diagnostics, even if other translation units do generate diagnostics). Be + warned that since which translation unit generated a given diagnostic isn't + visible to a normal user, we don't put much work into coming up with sensible + rules for this, but it should at least be deterministic for testing. + +Note that some 3C tests use diagnostic verification on calls to `clang` rather +than `3c`, so if you see `expected-*` directives in a test, you can look at the +`RUN` commands to see which command has `-Xclang -verify` and is using the +directives. If you want to verify diagnostics of more than one `RUN` command in +the same test, you can use different directive prefixes (`-Xclang +-verify=PREFIX`). + +## Debugging + +### AST dumping + +`3c` honors the usual `-Xclang -ast-dump` compiler option to dump the +AST to stdout before running its analysis. (This must be passed as a +compiler option, not a `3c` option, e.g., _after_ the `--` on the +command line if applicable.) The dump includes the AST node memory +addresses (e.g., `VarDecl 0x5569e5ef3988 ...`), so if you see in the +debugger that (for example) a `Decl *` variable in 3C has the value +`0x5569e5ef3988`, you can look for that memory address in the AST dump +to quickly see what AST node it is (modulo pointer adjustments for +multiple inheritance; adding a debugger watch with a cast to the +pointer type used in the AST dump can help with this). + +## Coding guidelines + +Please follow [LLVM coding +standards](https://llvm.org/docs/CodingStandards.html#name-types-functions-variables-and-enumerators-properly) +in your code. Specifically: + +* The maximum length of a line: 80 chars + +* All comments should start with a Capital letter. + +* All Local variables, including fields and parameters, should start + with a Capital letter (similar to PascalCase). Short names are + preferred. + +* A space between the conditional keyword and `(` i.e., `if (`, + `while (`, ``for (` etc. + +* Space after the type name, i.e., `Type *K` _not_ `Type* K`. + +* Space before and after `:` in iterators, i.e., `for (auto &k : List)` + +Our goal is that all files should be formatted with `clang-format` and +pass `clang-tidy` ([more information](clang-tidy.md)), and nonempty +files should have a final newline (surprisingly, `clang-format` cannot +enforce this). However, until we have better automation, we decided it +isn't reasonable to require contributors to manually run these tools +and fix style nits in each change; instead, we periodically run the +tools on the entire 3C codebase. diff --git a/clang/lib/3C/3C.cpp b/clang/lib/3C/3C.cpp index 8dee4ce82679..f0859d8c76af 100644 --- a/clang/lib/3C/3C.cpp +++ b/clang/lib/3C/3C.cpp @@ -16,6 +16,7 @@ #include "clang/3C/ConstraintBuilder.h" #include "clang/3C/IntermediateToolHook.h" #include "clang/3C/RewriteUtils.h" +#include "clang/Frontend/ASTConsumers.h" #include "clang/Frontend/VerifyDiagnosticConsumer.h" #include "clang/Tooling/ArgumentsAdjusters.h" #include "llvm/Support/TargetSelect.h" @@ -251,9 +252,57 @@ class _3CASTBuilderAction : public ToolAction { if (!AST) return false; + handleExtraProgramAction(Invocation->getFrontendOpts(), + AST->getASTContext()); + ASTs.push_back(std::move(AST)); return true; } + +private: + void handleExtraProgramAction(FrontendOptions &Opts, + ASTContext &C) { + // The Opts.ProgramAction field is normally used only by `clang -cc1` to + // select a FrontendAction (see CreateFrontendBaseAction in + // ExecuteCompilerInvocation.cpp) and is ignored by LibTooling tools, which + // perform a custom FrontendAction. But we want to support at least AST + // dumping (as an addition to 3C's normal workflow) since it's useful for + // debugging 3C, and we prefer to honor the standard `-Xclang -ast-dump` + // option rather than define our own tool-level option like clang-check + // does. We could add support for more `-ast-*` options here if desired. + switch (Opts.ProgramAction) { + case frontend::ParseSyntaxOnly: + // Nothing extra to do. + break; + case frontend::ASTDump: { + // Code copied from ASTDumpAction::CreateASTConsumer since we don't have a + // good way to actually use ASTDumpAction from here. :/ + // + // XXX: Maybe we'd prefer to output this somewhere other than stdout to + // separate it from the updated main file written to stdout? This doesn't + // look trivial because ASTPrinter requires ownership of the output + // stream, and it probably isn't important for the intended debugging use + // case. + std::unique_ptr Dumper = + CreateASTDumper(nullptr /*Dump to stdout.*/, Opts.ASTDumpFilter, + Opts.ASTDumpDecls, Opts.ASTDumpAll, + Opts.ASTDumpLookups, Opts.ASTDumpDeclTypes, + Opts.ASTDumpFormat); + // In principle, we should call all the ASTConsumer methods the same way + // the normal AST parsing process would, but there isn't an obvious way to + // do that when using ASTUnit. Instead, we rely on the assumption + // (apparently valid as of this writing) that the only ASTConsumer method + // that has a nonempty implementation in ASTPrinter is + // HandleTranslationUnit, and we just call HandleTranslationUnit manually. + Dumper->HandleTranslationUnit(C); + break; + } + default: + llvm::errs() << "Warning: The requested ProgramAction is not implemented " + "by 3C and will be ignored.\n"; + break; + } + } }; void dumpConstraintOutputJson(const std::string &PostfixStr, diff --git a/clang/test/3C/ast_dump.c b/clang/test/3C/ast_dump.c new file mode 100644 index 000000000000..3e5ae92d57c3 --- /dev/null +++ b/clang/test/3C/ast_dump.c @@ -0,0 +1,11 @@ +// RUN: rm -rf %t* + +// Turn off color in the AST dump so that we can more easily match it +// (https://stackoverflow.com/questions/32447542/how-do-i-get-clang-to-dump-the-ast-without-color). +// RUN: 3c -base-dir=%S %s -- -Xclang -ast-dump -fno-color-diagnostics | FileCheck -match-full-lines %s + +// RUN: 3c -base-dir=%S %s -- -Xclang -ast-list 2>%t.stderr +// RUN: grep 'The requested ProgramAction is not implemented by 3C' %t.stderr + +int *p; +// CHECK: `-VarDecl {{.*}} p 'int *' From f9d1cbb06cddd1b21f5c342f797d5e853ee2d106 Mon Sep 17 00:00:00 2001 From: John Kastner Date: Tue, 21 Sep 2021 12:53:56 -0400 Subject: [PATCH 28/38] Collection of code cleanup changes made to bounds inference (#650) * Cleanup ScopeVisitor: - Better naming for fields - Make references to AVarBoundsInfo fields constant - Expose result of visitor through constant set reference instead of by mutating sets passed as arguments. - Remove loop of singleton set. * Cleanup getReachableBoundKeys - Rename SBVar -> FromProgramVar - Get rid of unnecessary set in constant finding loop * Cleanup StructAccessVisitor - Make two fields private. One of these was never used publicly, the other is exposed through a getter. - Slightly simpler logic for handling variable declarations. `isLocalVarDecl` returns false for ParmVarDecls, so it's OK to update IsGlobal with the result of this function for ParmVarDecl and VarDecl. - Added note about a bug on structure access such as `( 0 ? global_struct_var : local_struct_var).array`. * Cleanup CollectDeclsVisitor - Make set fields private and expose through const reference. - Update comments * Make some methods of ConstraintsGraph const - getNeighbors, getSuccessors, getPredecessors, and findNode are trivially const. - visitBreadthFirst mutates the BFSCache map, but this acceptable because it is cache for the actual graph data, and the underlying data is not mutated. To let this method be checked `BFSCache` is explicitly qualified with mutable. * Cleanup performFlowAnalysis - Avoid re-using AvarBoundsInference instance. - Avoid re-using set in inner most inference loop - Move some code into performWorkListInference since it was always called before/after this function. * Cleanup performWorkListInference - Remove `Changed` loop variable and instead terminate outer loop when `WorkList` is empty. - Replace inner while loop with a for loop to make it clearer that it the loop is just iterating over each element of `WorkList`. * Cleanup predictBounds - Move variable declarations closer to use. - Declare variables with explicit types where this helps me understand the code. - Add some comments. * Cleanup inferFromPotentialBounds - rename Handled to HasInferredBound - Use llvm::any_of instead of loop * Cleanup computeArrPointers - Avoid duplicating code in main for loop - Add comment expressing concern about special-casing on return-type null terminated arrays. - Delete some intermediate sets that didn't need to exist. * Delete commented out method tryGetBoundsKeyVar - A nearly identical un-commented version of this method still exists. The only difference is that getVariable is used to get a ConstraintVariable instead of getExprConstraintVars. * Cleanup handleAllocatorCall - remove unused Fname parameter from isAllocatorCall - getCalledFunctionName can only be called on CallExprs, so the type of its parameter can be changed from Expr to CallExpr. - Introduce function hasValidBoundsKey that can be used instead of calling tryGetValidBoundsKey with unused BoundsKey reference. * Cleanup LocalVarABVisitor - Rename addUsedParmVarDecl -> addNonLengthParameter - Delete some unused local variables - Add a comment about some odd code * Cleanup ComparisonVisitor - Fix typo in class name - Remove unused field - Expose result through const reference to field instead of mutating constructor argument. * Cleanup LengthVarInference - Remove unused fields - Avoid needing to `new` and `delete` some fields. * Add some array bounds debugging utilities - Dump context sensitive and reverse context sensitive graphs in addition to standard graph. - Add dumpBounds method to write current bounds solution state to standard error. This can be called from the debugger while stepping through execution. * ProgramVar cleanup - Store constant value of variable explicitly - Assertions to check that non-constant variable is not used as constant --- clang/include/clang/3C/AVarBoundsInfo.h | 36 +- .../clang/3C/ArrayBoundsInferenceConsumer.h | 10 +- clang/include/clang/3C/ConstraintVariables.h | 2 +- clang/include/clang/3C/Constraints.h | 4 +- clang/include/clang/3C/ConstraintsGraph.h | 16 +- clang/include/clang/3C/ProgramInfo.h | 1 + clang/include/clang/3C/ProgramVar.h | 54 +- clang/lib/3C/ABounds.cpp | 6 +- clang/lib/3C/AVarBoundsInfo.cpp | 780 +++++++++--------- clang/lib/3C/ArrayBoundsInferenceConsumer.cpp | 158 ++-- clang/lib/3C/ConstraintVariables.cpp | 2 +- clang/lib/3C/Constraints.cpp | 4 +- clang/lib/3C/CtxSensAVarBounds.cpp | 42 +- clang/lib/3C/ProgramVar.cpp | 37 +- 14 files changed, 550 insertions(+), 602 deletions(-) diff --git a/clang/include/clang/3C/AVarBoundsInfo.h b/clang/include/clang/3C/AVarBoundsInfo.h index 1786fd01af6a..81a004734905 100644 --- a/clang/include/clang/3C/AVarBoundsInfo.h +++ b/clang/include/clang/3C/AVarBoundsInfo.h @@ -106,18 +106,18 @@ class AvarBoundsInference { // Infer bounds for the given key from the set of given ARR atoms. // The flag FromPB requests the inference to use potential length variables. - bool inferBounds(BoundsKey K, AVarGraph &BKGraph, bool FromPB = false); + bool inferBounds(BoundsKey K, const AVarGraph &BKGraph, bool FromPB = false); - // Get a consistent bound for all the arrays whose bounds have been - // inferred. - bool convergeInferredBounds(); + // Get a consistent bound for all the arrays whose bounds have been inferred. + void convergeInferredBounds(); private: // Find all the reachable variables form FromVarK that are visible // in DstScope bool getReachableBoundKeys(const ProgramVarScope *DstScope, BoundsKey FromVarK, std::set &PotK, - AVarGraph &BKGraph, bool CheckImmediate = false); + const AVarGraph &BKGraph, + bool CheckImmediate = false); // Check if bounds specified by Bnds are declared bounds of K. bool areDeclaredBounds( @@ -125,12 +125,12 @@ class AvarBoundsInference { const std::pair> &Bnds); // Get all the bounds of the given array i.e., BK - bool getRelevantBounds(BoundsKey BK, BndsKindMap &ResBounds); + void getRelevantBounds(BoundsKey BK, BndsKindMap &ResBounds); // Predict possible bounds for DstArrK from the bounds of Neighbours. // Return true if there is any change in the captured bounds information. - bool predictBounds(BoundsKey DstArrK, std::set &Neighbours, - AVarGraph &BKGraph); + bool predictBounds(BoundsKey DstArrK, const std::set &Neighbours, + const AVarGraph &BKGraph); void mergeReachableProgramVars(BoundsKey TarBK, std::set &AllVars); @@ -139,7 +139,7 @@ class AvarBoundsInference { // Set the given pointer to have impossible bounds. void setImpossibleBounds(BoundsKey BK); // Infer bounds of the given pointer key from potential bounds. - bool inferFromPotentialBounds(BoundsKey BK, AVarGraph &BKGraph); + bool inferFromPotentialBounds(BoundsKey BK, const AVarGraph &BKGraph); AVarBoundsInfo *BI; @@ -147,6 +147,8 @@ class AvarBoundsInference { std::map CurrIterInferBounds; // BoundsKey that failed the flow inference. std::set BKsFailedFlowInference; + + static ABounds *getPreferredBound(const BndsKindMap &BKindMap); }; // Class that maintains information about potential bounds for @@ -255,7 +257,7 @@ class AVarBoundsInfo { ProgramVar *getProgramVar(BoundsKey VK); // Propagate the array bounds information for all array ptrs. - bool performFlowAnalysis(ProgramInfo *PI); + void performFlowAnalysis(ProgramInfo *PI); // Get the context sensitive BoundsKey for the given key at CallSite // located at PSL. @@ -366,23 +368,23 @@ class AVarBoundsInfo { bool isFunctionReturn(BoundsKey BK); // Of all the pointer bounds key, find arr pointers. - void computerArrPointers(ProgramInfo *PI, std::set &Ret); + void computeArrPointers(const ProgramInfo *PI); // Get all the array pointers that need bounds. - void getBoundsNeededArrPointers(const std::set &ArrPtrs, - std::set &AB); + void getBoundsNeededArrPointers(std::set &AB) const; // Keep only highest priority bounds for all the provided BoundsKeys // returns true if any thing changed, else false. - bool keepHighestPriorityBounds(std::set &ArrPtrs); + bool keepHighestPriorityBounds(); // Perform worklist based inference on the requested array variables using // the provided graph and potential length variables. - bool performWorkListInference(const std::set &ArrNeededBounds, - AVarGraph &BKGraph, AvarBoundsInference &BI, - bool FromPB); + void performWorkListInference(const AVarGraph &BKGraph, + AvarBoundsInference &BI, bool FromPB); void insertParamKey(ParamDeclType ParamDecl, BoundsKey NK); + + void dumpBounds(); }; #endif // LLVM_CLANG_3C_AVARBOUNDSINFO_H diff --git a/clang/include/clang/3C/ArrayBoundsInferenceConsumer.h b/clang/include/clang/3C/ArrayBoundsInferenceConsumer.h index f5a857f71d20..8d45045cf48b 100644 --- a/clang/include/clang/3C/ArrayBoundsInferenceConsumer.h +++ b/clang/include/clang/3C/ArrayBoundsInferenceConsumer.h @@ -69,11 +69,11 @@ class LocalVarABVisitor : public clang::RecursiveASTVisitor { bool VisitSwitchStmt(SwitchStmt *S); bool VisitBinaryOperator(BinaryOperator *O); bool VisitArraySubscriptExpr(ArraySubscriptExpr *E); - bool isNonLengthParameter(ParmVarDecl *PVD); + bool isNonLengthParameter(ParmVarDecl *PVD) const; private: void handleAssignment(BoundsKey LK, QualType LHSType, Expr *RHS); - void addUsedParmVarDecl(Expr *CE); + void addNonLengthParameter(Expr *CE); std::set NonLengthParameters; ASTContext *Context; ProgramInfo &Info; @@ -88,8 +88,6 @@ class LengthVarInference : public StmtVisitor { public: LengthVarInference(ProgramInfo &In, ASTContext *AC, FunctionDecl *F); - virtual ~LengthVarInference(); - void VisitStmt(Stmt *St); void VisitArraySubscriptExpr(ArraySubscriptExpr *ASE); @@ -98,11 +96,9 @@ class LengthVarInference : public StmtVisitor { std::map StMap; ProgramInfo &I; ASTContext *C; - FunctionDecl *FD; CFGBlock *CurBB; - ControlDependencyCalculator *CDG; - ConstraintResolver *CR; std::unique_ptr Cfg; + ControlDependencyCalculator CDG; }; void handleArrayVariablesBoundsDetection(ASTContext *C, ProgramInfo &I, diff --git a/clang/include/clang/3C/ConstraintVariables.h b/clang/include/clang/3C/ConstraintVariables.h index 9c1f9e36dbd2..b75c8a294e96 100644 --- a/clang/include/clang/3C/ConstraintVariables.h +++ b/clang/include/clang/3C/ConstraintVariables.h @@ -412,7 +412,7 @@ class PointerVariableConstraint : public ConstraintVariable { std::string getTy() const { return BaseType; } bool getArrPresent() const; // Check if the outermost pointer is an unsized array. - bool isTopCvarUnsizedArr() const; + bool isTopAtomUnsizedArr() const; // Check if any of the pointers is either a sized or unsized arr. bool hasSomeSizedArr() const; diff --git a/clang/include/clang/3C/Constraints.h b/clang/include/clang/3C/Constraints.h index 38ca8a49ddc0..dbe0e3db23c4 100644 --- a/clang/include/clang/3C/Constraints.h +++ b/clang/include/clang/3C/Constraints.h @@ -488,8 +488,8 @@ class Constraints { NTArrAtom *getNTArr() const; WildAtom *getWild() const; ConstAtom *getAssignment(Atom *A); - ConstraintsGraph &getChkCG(); - ConstraintsGraph &getPtrTypCG(); + const ConstraintsGraph &getChkCG() const; + const ConstraintsGraph &getPtrTypCG() const; void resetEnvironment(); bool checkInitialEnvSanity(); diff --git a/clang/include/clang/3C/ConstraintsGraph.h b/clang/include/clang/3C/ConstraintsGraph.h index 5b376e1b8490..684fa8427267 100644 --- a/clang/include/clang/3C/ConstraintsGraph.h +++ b/clang/include/clang/3C/ConstraintsGraph.h @@ -153,7 +153,7 @@ class DataGraph } bool getNeighbors(Data D, std::set &DataSet, bool Succ, - bool Append = false, bool IgnoreSoftEdges = false) { + bool Append = false, bool IgnoreSoftEdges = false) const { NodeType *N = this->findNode(D); if (N == nullptr) return false; @@ -170,21 +170,23 @@ class DataGraph return !DataSet.empty(); } - bool getSuccessors(Data D, std::set &DataSet, bool Append = false) { + bool + getSuccessors(Data D, std::set &DataSet, bool Append = false) const { return getNeighbors(D, DataSet, true, Append); } - bool getPredecessors(Data D, std::set &DataSet, bool Append = false) { + bool + getPredecessors(Data D, std::set &DataSet, bool Append = false) const { return getNeighbors(D, DataSet, false, Append); } - NodeType *findNode(Data D) { + NodeType *findNode(Data D) const { if (NodeSet.find(D) != NodeSet.end()) - return NodeSet[D]; + return NodeSet.at(D); return nullptr; } - void visitBreadthFirst(Data Start, llvm::function_ref Fn) { + void visitBreadthFirst(Data Start, llvm::function_ref Fn) const { NodeType *N = this->findNode(Start); if (N == nullptr) return; @@ -217,7 +219,7 @@ class DataGraph private: template friend struct llvm::GraphTraits; friend class GraphVizOutputGraph; - std::map> BFSCache; + mutable std::map> BFSCache; std::map NodeSet; void invalidateBFSCache() { BFSCache.clear(); } diff --git a/clang/include/clang/3C/ProgramInfo.h b/clang/include/clang/3C/ProgramInfo.h index 167b1f8244a6..ae851421da84 100644 --- a/clang/include/clang/3C/ProgramInfo.h +++ b/clang/include/clang/3C/ProgramInfo.h @@ -134,6 +134,7 @@ class ProgramInfo : public ProgramVariableAdder { const VariableMap &getVarMap() const { return Variables; } Constraints &getConstraints() { return CS; } + const Constraints &getConstraints() const { return CS; } AVarBoundsInfo &getABoundsInfo() override { return ArrBInfo; } PerformanceStats &getPerfStats() { return PerfS; } diff --git a/clang/include/clang/3C/ProgramVar.h b/clang/include/clang/3C/ProgramVar.h index 009af2837d84..5742b95c0d75 100644 --- a/clang/include/clang/3C/ProgramVar.h +++ b/clang/include/clang/3C/ProgramVar.h @@ -406,35 +406,55 @@ class FunctionScope : public ProgramVarScope { // Class that represents a program variable along with its scope. class ProgramVar { public: - const ProgramVarScope *getScope() { return VScope; } + const ProgramVarScope *getScope() const { return VScope; } void setScope(const ProgramVarScope *PVS) { this->VScope = PVS; } - BoundsKey getKey() { return K; } - bool isNumConstant() { return IsConstant; } - std::string mkString(bool GetKey = false); - std::string getVarName() { return VarName; } - std::string verboseStr(); - ProgramVar *makeCopy(BoundsKey NK); - virtual ~ProgramVar() {} + BoundsKey getKey() const { return K; } + const std::string &getVarName() const { return VarName; } + std::string verboseStr() const; + ProgramVar *makeCopy(BoundsKey NK) const; + + bool isNumConstant() const {return IsConstant; } + uint64_t getConstantVal() const { + assert("Can't get constant value for non-constant var." && IsConstant); + return ConstantVal; + } static ProgramVar *createNewProgramVar(BoundsKey VK, std::string VName, - const ProgramVarScope *PVS, - bool IsCons = false); + const ProgramVarScope *PVS); + + static ProgramVar *createNewConstantVar(BoundsKey VK, uint64_t Value); private: BoundsKey K; std::string VarName; const ProgramVarScope *VScope; - bool IsConstant; // is a literal integer, not a variable + + // Is a literal integer, not a variable. + bool IsConstant; + uint64_t ConstantVal; + // TODO: All the ProgramVars may not be used. We should try to figure out // a way to free unused program vars. - static std::set AllProgramVars; - - ProgramVar(BoundsKey VK, std::string VName, const ProgramVarScope *PVS, - bool IsCons) - : K(VK), VarName(VName), VScope(PVS), IsConstant(IsCons) {} + static std::set AllProgramVars; + + ProgramVar(BoundsKey K, const std::string &VarName, + const ProgramVarScope *VScope, bool IsConstant, + uint32_t ConstantVal) + : K(K), VarName(VarName), VScope(VScope), IsConstant(IsConstant), + ConstantVal(ConstantVal) { + // Constant variables should be a subclass of ProgramVariable. Until that + // change happens this should sanity check how ProgramVars are constructed. + assert("Constant value should not be set for non-constant variables." && + (IsConstant || ConstantVal == 0)); + AllProgramVars.insert(this); + } ProgramVar(BoundsKey VK, std::string VName, const ProgramVarScope *PVS) - : ProgramVar(VK, VName, PVS, false) {} + : ProgramVar(VK, VName, PVS, false, 0) {} + + ProgramVar(BoundsKey VK, uint32_t CVal) + : ProgramVar(VK, std::to_string(CVal), GlobalScope::getGlobalScope(), true, + CVal) {} }; #endif // LLVM_CLANG_3C_PROGRAMVAR_H diff --git a/clang/lib/3C/ABounds.cpp b/clang/lib/3C/ABounds.cpp index 87198d59a215..293be644490b 100644 --- a/clang/lib/3C/ABounds.cpp +++ b/clang/lib/3C/ABounds.cpp @@ -30,7 +30,7 @@ ABounds *ABounds::getBoundsInfo(AVarBoundsInfo *ABInfo, BoundsExpr *BExpr, if (ABInfo->tryGetVariable(CBE->getCountExpr()->IgnoreParenCasts(), C, VK)) { ProgramVar *PV = ABInfo->getProgramVar(VK); - if (PV->isNumConstant() && PV->getVarName() == "0") { + if (PV->isNumConstant() && PV->getConstantVal() == 0) { // Invalid bounds. This is for functions like free. // Where the bounds is 0. Ret = nullptr; @@ -60,7 +60,7 @@ std::string ABounds::getBoundsKeyStr(BoundsKey BK, AVarBoundsInfo *ABI, Decl *D) { ProgramVar *PV = ABI->getProgramVar(BK); assert(PV != nullptr && "No Valid program var"); - std::string BKStr = PV->mkString(); + std::string BKStr = PV->getVarName(); unsigned PIdx = 0; auto *PVD = dyn_cast_or_null(D); // Does this belong to a function parameter? @@ -74,7 +74,7 @@ std::string ABounds::getBoundsKeyStr(BoundsKey BK, AVarBoundsInfo *ABI, // If the parameter in the new declaration does not have a name? // then use the old name. if (BKStr.empty()) - BKStr = PV->mkString(); + BKStr = PV->getVarName(); } } return BKStr; diff --git a/clang/lib/3C/AVarBoundsInfo.cpp b/clang/lib/3C/AVarBoundsInfo.cpp index a13ff98cde12..94721e7d5617 100644 --- a/clang/lib/3C/AVarBoundsInfo.cpp +++ b/clang/lib/3C/AVarBoundsInfo.cpp @@ -65,9 +65,9 @@ void AVarBoundsStats::print(llvm::raw_ostream &O, } } -bool hasArray(ConstraintVariable *CK, Constraints &CS) { +bool hasArray(const ConstraintVariable *CK, const Constraints &CS) { auto &E = CS.getVariables(); - if (PVConstraint *PV = dyn_cast(CK)) { + if (const auto *PV = dyn_cast(CK)) { if (PV->hasArr(E, 0) || PV->hasNtArr(E, 0)) { return true; } @@ -75,9 +75,9 @@ bool hasArray(ConstraintVariable *CK, Constraints &CS) { return false; } -bool hasOnlyNtArray(ConstraintVariable *CK, Constraints &CS) { +bool hasOnlyNtArray(const ConstraintVariable *CK, const Constraints &CS) { auto &E = CS.getVariables(); - if (PVConstraint *PV = dyn_cast(CK)) { + if (const auto *PV = dyn_cast(CK)) { if (PV->hasNtArr(E, 0)) { return true; } @@ -85,9 +85,9 @@ bool hasOnlyNtArray(ConstraintVariable *CK, Constraints &CS) { return false; } -bool isInSrcArray(ConstraintVariable *CK, Constraints &CS) { +bool isInSrcArray(const ConstraintVariable *CK, const Constraints &CS) { auto &E = CS.getVariables(); - if (PVConstraint *PV = dyn_cast(CK)) { + if (const auto *PV = dyn_cast(CK)) { if ((PV->hasArr(E, 0) || PV->hasNtArr(E, 0)) && PV->isForValidDecl()) { return true; } @@ -98,58 +98,76 @@ bool isInSrcArray(ConstraintVariable *CK, Constraints &CS) { // This class picks variables that are in the same scope as the provided scope. class ScopeVisitor { public: - ScopeVisitor(const ProgramVarScope *S, std::set &R, - std::set &VK, std::map &VarM, - std::set &P) - : TS(S), Res(R), VisibleKeys(VK), VM(VarM), PtrAtoms(P) {} - void visitBoundsKey(BoundsKey V) const { + ScopeVisitor(const ProgramVarScope *S, + const std::map &VM, + const std::set &P) + : Scope(S), InScopeKeys(), VisibleKeys(), PVarInfo(VM), + PointerBoundsKey(P) {} + void visitBoundsKey(BoundsKey V) { // If the variable is non-pointer? - if (VM.find(V) != VM.end() && PtrAtoms.find(V) == PtrAtoms.end()) { - auto *S = VM[V]; + if (PVarInfo.find(V) != PVarInfo.end() && + PointerBoundsKey.find(V) == PointerBoundsKey.end()) { + ProgramVar *S = PVarInfo.at(V); // If the variable is constant or in the same scope? - if (S->isNumConstant() || (*(TS) == *(S->getScope()))) { - Res.insert(V); + if (S->isNumConstant() || (*Scope == *(S->getScope()))) { + InScopeKeys.insert(V); VisibleKeys.insert(V); - } else if (TS->isInInnerScope(*(S->getScope()))) { + } else if (Scope->isInInnerScope(*(S->getScope()))) { VisibleKeys.insert(V); } } } - const ProgramVarScope *TS; - std::set &Res; - std::set &VisibleKeys; - std::map &VM; - std::set &PtrAtoms; + const std::set &getInScopeKeys() const { return InScopeKeys; } + + const std::set &getVisibleKeys() const { return VisibleKeys; } + +private: + const ProgramVarScope *Scope; + + // Contains high priority bounds keys. These are either directly in the scope + // for this visitor, or they are constant bounds keys. + std::set InScopeKeys; + + // This set contains all keys in InScopeKeys, but also contains non-constant + // bounds keys from scopes where this scope is an inner scope. + std::set VisibleKeys; + + // A constant reference to PVarInfo frm the AVarBoundsInfo instance. This set + // maps each bounds key to variable. BoundsKeys are just a uint_32, so a + // corresponding ProgramVar is required find the scope of a key. + const std::map &PVarInfo; + + // A constant reference to the field PointerBoundsKey from the AVarBoundsInfo + // instance. This set contains the bounds keys that correspond to pointers. + // Used to verify that a visited bounds key is not a pointer. + const std::set &PointerBoundsKey; }; void AvarBoundsInference::mergeReachableProgramVars( BoundsKey TarBK, std::set &AllVars) { if (AllVars.size() > 1) { - ProgramVar *BVar = nullptr; bool IsTarNTArr = BI->NtArrPointerBoundsKey.find(TarBK) != BI->NtArrPointerBoundsKey.end(); // First, find all variables that are in the SAME scope as TarBK. // If there is only one? Then use it. - std::set SameScopeVars; - ProgramVar *TarBVar = BI->getProgramVar(TarBK); - if (TarBVar != nullptr) { - for (auto TB : AllVars) { - if (*(BI->getProgramVar(TB)->getScope()) == *(TarBVar->getScope())) { + if (ProgramVar *TarBVar = BI->getProgramVar(TarBK)) { + std::set SameScopeVars; + for (auto TB : AllVars) + if (*(BI->getProgramVar(TB)->getScope()) == *(TarBVar->getScope())) SameScopeVars.insert(TB); - } - } - // There is only one same scope variable. - // Consider only that. + + // There is only one same scope variable. Consider only that. if (SameScopeVars.size() == 1) { - AllVars.clear(); - AllVars.insert(*SameScopeVars.begin()); + AllVars = SameScopeVars; return; } } + // We want to merge all bounds vars. We give preference to // non-constants if there are multiple non-constant variables, // we give up. + ProgramVar *BVar = nullptr; for (auto TmpBKey : AllVars) { // Convert the bounds key to corresponding program var. auto *TmpB = BI->getProgramVar(TmpBKey); @@ -163,8 +181,8 @@ void AvarBoundsInference::mergeReachableProgramVars( BVar = TmpB; } else { // If we need to merge two constants? - int CVal = std::stoi(BVar->getVarName()); - int TmpVal = std::stoi(TmpB->getVarName()); + uint64_t CVal = BVar->getConstantVal(); + uint64_t TmpVal = TmpB->getConstantVal(); if (IsTarNTArr) { // If this is an NTarr then the values should be same. if (TmpVal != CVal) { @@ -204,45 +222,57 @@ void AvarBoundsInference::mergeReachableProgramVars( } } -// Consider all pointers, each of which may have multiple bounds, -// and intersect these. If they all converge to one possibility, -// use that. If not, give up (no bounds). -bool AvarBoundsInference::convergeInferredBounds() { - bool FoundSome = false; +// Consider all pointers, each of which may have multiple bounds, and intersect +// these. If they all converge to one possibility, use that. If not, give up and +// don't assign any bounds to the pointer. +void AvarBoundsInference::convergeInferredBounds() { for (auto &CInfABnds : CurrIterInferBounds) { - auto *AB = BI->getBounds(CInfABnds.first); + BoundsKey PtrBoundsKey = CInfABnds.first; // If there are no bounds? - if (AB == nullptr) { - auto BTypeMap = CInfABnds.second; - for (auto &TySet : BTypeMap) { - mergeReachableProgramVars(CInfABnds.first, TySet.second); - } - // Order of preference: Count and Byte - if (BTypeMap.find(ABounds::CountBoundKind) != BTypeMap.end() && - !BTypeMap[ABounds::CountBoundKind].empty()) { - AB = new CountBound(*BTypeMap[ABounds::CountBoundKind].begin()); - } else if (BTypeMap.find(ABounds::ByteBoundKind) != BTypeMap.end() && - !BTypeMap[ABounds::ByteBoundKind].empty()) { - AB = new ByteBound(*BTypeMap[ABounds::ByteBoundKind].begin()); - } else if (BTypeMap.find(ABounds::CountPlusOneBoundKind) != - BTypeMap.end() && - !BTypeMap[ABounds::CountPlusOneBoundKind].empty()) { - AB = new CountPlusOneBound( - *BTypeMap[ABounds::CountPlusOneBoundKind].begin()); - } - + if (BI->getBounds(PtrBoundsKey) == nullptr) { + // Maps ABounds::BoundsKind to the set of possible bounds of this kind for + // the current PtrBoundsKey. + auto BKindMap = CInfABnds.second; + for (auto &TySet : BKindMap) + mergeReachableProgramVars(PtrBoundsKey, TySet.second); + + ABounds *NewBound = getPreferredBound(BKindMap); // If we found any bounds? - if (AB != nullptr) { + if (NewBound != nullptr) { // Record that we inferred bounds using data-flow. - BI->BoundsInferStats.DataflowMatch.insert(CInfABnds.first); - BI->replaceBounds(CInfABnds.first, BoundsPriority::FlowInferred, AB); - FoundSome = true; + BI->BoundsInferStats.DataflowMatch.insert(PtrBoundsKey); + BI->replaceBounds(PtrBoundsKey, BoundsPriority::FlowInferred, NewBound); } else { - BKsFailedFlowInference.insert(CInfABnds.first); + BKsFailedFlowInference.insert(PtrBoundsKey); } } } - return FoundSome; +} + +// Construct an array bound with the most preferred kind from the bounds kind +// map. Count bounds have the highest priority, followed by byte count and then +// count-plus-one bounds. This function assumes that the BoundsKey sets in the +// map contain either zero or one BoundsKey. This is be achieved by first +// passing the sets to `mergeReachableProgramVars`. +ABounds *AvarBoundsInference::getPreferredBound(const BndsKindMap &BKindMap) { + // Utility to check if the map contains a non-empty set of bounds for a + // particular kind. This makes the following if statements much cleaner. + auto HasBoundKind = [&BKindMap](ABounds::BoundsKind Kind) { + return BKindMap.find(Kind) != BKindMap.end() && !BKindMap.at(Kind).empty(); + }; + + // Order of preference: Count, Byte, Count-plus-one + if (HasBoundKind(ABounds::CountBoundKind)) + return new CountBound(getOnly(BKindMap.at(ABounds::CountBoundKind))); + + if (HasBoundKind(ABounds::ByteBoundKind)) + return new ByteBound(getOnly(BKindMap.at(ABounds::ByteBoundKind))); + + if (HasBoundKind(ABounds::CountPlusOneBoundKind)) + return new CountPlusOneBound( + getOnly(BKindMap.at(ABounds::CountPlusOneBoundKind))); + + return nullptr; } bool AvarBoundsInference::hasImpossibleBounds(BoundsKey BK) { @@ -261,14 +291,14 @@ void AvarBoundsInference::setImpossibleBounds(BoundsKey BK) { bool AvarBoundsInference::getReachableBoundKeys(const ProgramVarScope *DstScope, BoundsKey FromVarK, std::set &PotK, - AVarGraph &BKGraph, + const AVarGraph &BKGraph, bool CheckImmediate) { // First, find all the in-scope variable to which the SBKey flow to. - auto *SBVar = BI->getProgramVar(FromVarK); + auto *FromProgramVar = BI->getProgramVar(FromVarK); // If both are in the same scope? - if (*DstScope == *SBVar->getScope()) { + if (*DstScope == *FromProgramVar->getScope()) { PotK.insert(FromVarK); if (CheckImmediate) { return true; @@ -276,78 +306,63 @@ bool AvarBoundsInference::getReachableBoundKeys(const ProgramVarScope *DstScope, } // All constants are reachable! - if (SBVar->isNumConstant()) { + if (FromProgramVar->isNumConstant()) { PotK.insert(FromVarK); } - // Get all bounds key that are equivalent to FromVarK - std::set AllFKeys; - AllFKeys.clear(); - AllFKeys.insert(FromVarK); - - for (auto CurrVarK : AllFKeys) { - // Find all the in scope variables reachable from the CurrVarK - // bounds variable. - std::set InScopeKeys; - std::set VisibleKeys; - if (DstScope->isInInnerScope(*BI->getProgramVar(CurrVarK)->getScope())) - VisibleKeys.insert(CurrVarK); - ScopeVisitor TV(DstScope, InScopeKeys, VisibleKeys, BI->PVarInfo, - BI->PointerBoundsKey); - BKGraph.visitBreadthFirst(CurrVarK, - [&TV](BoundsKey BK) { TV.visitBoundsKey(BK); }); - // Prioritize in scope keys. - if (!InScopeKeys.empty()) { - PotK.insert(InScopeKeys.begin(), InScopeKeys.end()); - } else { - PotK.insert(VisibleKeys.begin(), VisibleKeys.end()); - } + // Find all in scope variables reachable from the FromVarK bounds variable. + ScopeVisitor TV(DstScope, BI->PVarInfo, BI->PointerBoundsKey); + BKGraph.visitBreadthFirst(FromVarK, + [&TV](BoundsKey BK) { TV.visitBoundsKey(BK); }); + // Prioritize in scope keys. + if (!TV.getInScopeKeys().empty()) { + PotK.insert(TV.getInScopeKeys().begin(), TV.getInScopeKeys().end()); + } else { + PotK.insert(TV.getVisibleKeys().begin(), TV.getVisibleKeys().end()); + + // This condition is necessary for array bounds using global variables. + // The bounds keys for global variable do not appear in the BKGraph array + // bounds graph, so breadth first search finds visits an empty set of nodes, + // not even visiting the initial bounds key. This ensures the global + // variable is added to the set of potential keys. + if (DstScope->isInInnerScope(*BI->getProgramVar(FromVarK)->getScope())) + PotK.insert(FromVarK); } // This is to get all the constants that are assigned to the variables // reachable from FromVarK. - if (!SBVar->isNumConstant()) { - std::set ReachableCons; - std::set Pre, CurrBK; + if (!FromProgramVar->isNumConstant()) { + std::set CurrBK; CurrBK.insert(PotK.begin(), PotK.end()); CurrBK.insert(FromVarK); for (auto CK : CurrBK) { - Pre.clear(); + std::set Pre; BKGraph.getPredecessors(CK, Pre); for (auto T : Pre) { auto *TVar = BI->getProgramVar(T); if (TVar != nullptr && TVar->isNumConstant()) { - ReachableCons.insert(T); + PotK.insert(T); } } } - PotK.insert(ReachableCons.begin(), ReachableCons.end()); } return !PotK.empty(); } -bool AvarBoundsInference::getRelevantBounds(BoundsKey BK, +void AvarBoundsInference::getRelevantBounds(BoundsKey BK, BndsKindMap &ResBounds) { // Try to get the bounds of all RBKeys. - bool HasBounds = false; // If this pointer is used in pointer arithmetic then there // are no relevant bounds for this pointer. if (!BI->hasPointerArithmetic(BK)) { if (CurrIterInferBounds.find(BK) != CurrIterInferBounds.end()) { // get the bounds inferred from the current iteration ResBounds = CurrIterInferBounds[BK]; - HasBounds = true; - } else { - auto *PrevBounds = BI->getBounds(BK); - // Does the parent arr has bounds? - if (PrevBounds != nullptr) { - ResBounds[PrevBounds->getKind()].insert(PrevBounds->getBKey()); - HasBounds = true; - } + } else if (ABounds *PrevBounds = BI->getBounds(BK)) { + ResBounds[PrevBounds->getKind()].insert(PrevBounds->getBKey()); } } - return HasBounds; } bool AvarBoundsInference::areDeclaredBounds( @@ -370,30 +385,30 @@ bool AvarBoundsInference::areDeclaredBounds( } bool AvarBoundsInference::predictBounds(BoundsKey K, - std::set &Neighbours, - AVarGraph &BKGraph) { - BndsKindMap NeighboursBnds, InferredKBnds; - // Bounds inferred from each of the neighbours. - std::map InferredNBnds; - bool IsChanged = false; + const std::set &Neighbours, + const AVarGraph &BKGraph) { bool ErrorOccurred = false; bool IsFuncRet = BI->isFunctionReturn(K); ProgramVar *KVar = this->BI->getProgramVar(K); - InferredNBnds.clear(); - // For reach of the Neighbour, try to infer possible bounds. + // Bounds inferred from each of the neighbours. + std::map InferredNBnds; + // For each of the Neighbour, try to infer possible bounds. for (auto NBK : Neighbours) { - NeighboursBnds.clear(); ErrorOccurred = false; - if (getRelevantBounds(NBK, NeighboursBnds) && !NeighboursBnds.empty()) { - std::set InfBK; + BndsKindMap NeighboursBnds; + getRelevantBounds(NBK, NeighboursBnds); + if (!NeighboursBnds.empty()) { for (auto &NKBChoice : NeighboursBnds) { - InfBK.clear(); - for (auto TmpNBK : NKBChoice.second) { - getReachableBoundKeys(KVar->getScope(), TmpNBK, InfBK, BKGraph); - } + ABounds::BoundsKind NeighbourKind = NKBChoice.first; + std::set NeighbourSet = NKBChoice.second; + + std::set InfBK; + for (BoundsKey NeighborBK : NeighbourSet) + getReachableBoundKeys(KVar->getScope(), NeighborBK, InfBK, BKGraph); + if (!InfBK.empty()) { - InferredNBnds[NBK][NKBChoice.first] = InfBK; + InferredNBnds[NBK][NeighbourKind] = InfBK; } else { bool IsDeclaredB = areDeclaredBounds(NBK, NKBChoice); @@ -426,38 +441,37 @@ bool AvarBoundsInference::predictBounds(BoundsKey K, } } + bool IsChanged = false; if (!InferredNBnds.empty()) { // All the possible inferred bounds for K. - InferredKBnds.clear(); - std::set TmpBKeys, AllKeys; + BndsKindMap InferredKBnds; // TODO: Figure out if there is a discrepancy and try to implement // root-cause analysis. // Find intersection of all bounds from neighbours. for (auto &IN : InferredNBnds) { for (auto &INB : IN.second) { - if (InferredKBnds.find(INB.first) == InferredKBnds.end()) { - InferredKBnds[INB.first] = INB.second; + ABounds::BoundsKind NeighbourKind = INB.first; + std::set NeighbourSet = INB.second; + if (InferredKBnds.find(NeighbourKind) == InferredKBnds.end()) { + InferredKBnds[NeighbourKind] = NeighbourSet; } else { - TmpBKeys.clear(); - AllKeys.clear(); - // Find intersection between the current bounds and the - // bounds propagated from current neighbour, i.e., INB.first. - auto &S1 = InferredKBnds[INB.first]; - auto &S2 = INB.second; - // Find intersection of bounds propagated from all neighbours. - findIntersection(S1, S2, TmpBKeys); - - AllKeys = S1; - AllKeys.insert(S2.begin(), S2.end()); - // Also, add all constants as potential bounds so that we can pick - // a constant with least value later. - for (auto CK : AllKeys) { + std::set KBoundsOfKind = InferredKBnds[NeighbourKind]; + // Keep the bounds in the intersection between the current bounds and + // the bounds from the neighbor. + std::set SharedBounds; + findIntersection(KBoundsOfKind, NeighbourSet, SharedBounds); + + // Also keep all constant bounds. Later on we will keep only the + // constant bound with the lowest value. + std::set AllBounds = KBoundsOfKind; + AllBounds.insert(NeighbourSet.begin(), NeighbourSet.end()); + for (auto CK : AllBounds) { auto *CKVar = this->BI->getProgramVar(CK); if (CKVar != nullptr && CKVar->isNumConstant()) - TmpBKeys.insert(CK); + SharedBounds.insert(CK); } - InferredKBnds[INB.first] = TmpBKeys; + InferredKBnds[NeighbourKind] = SharedBounds; } } } @@ -465,23 +479,25 @@ bool AvarBoundsInference::predictBounds(BoundsKey K, // Now from the newly inferred bounds i.e., InferredKBnds, check // if is is different from previously known bounds of K for (auto &IKB : InferredKBnds) { + ABounds::BoundsKind InferredKind = IKB.first; + std::set InferredSet = IKB.second; bool Handled = false; if (CurrIterInferBounds.find(K) != CurrIterInferBounds.end()) { - auto &BM = CurrIterInferBounds[K]; - if (BM.find(IKB.first) != BM.end()) { + BndsKindMap &CurrentBoundsMap = CurrIterInferBounds[K]; + if (CurrentBoundsMap.find(InferredKind) != CurrentBoundsMap.end()) { Handled = true; - if (BM[IKB.first] != IKB.second) { - BM[IKB.first] = IKB.second; - if (IKB.second.empty()) - BM.erase(IKB.first); + if (CurrentBoundsMap[InferredKind] != InferredSet) { + CurrentBoundsMap[InferredKind] = InferredSet; + if (InferredSet.empty()) + CurrentBoundsMap.erase(InferredKind); IsChanged = true; } } } if (!Handled) { - CurrIterInferBounds[K][IKB.first] = IKB.second; - if (IKB.second.empty()) { - CurrIterInferBounds[K].erase(IKB.first); + CurrIterInferBounds[K][InferredKind] = InferredSet; + if (InferredSet.empty()) { + CurrIterInferBounds[K].erase(InferredKind); } else { IsChanged = true; } @@ -494,7 +510,8 @@ bool AvarBoundsInference::predictBounds(BoundsKey K, } return IsChanged; } -bool AvarBoundsInference::inferBounds(BoundsKey K, AVarGraph &BKGraph, + +bool AvarBoundsInference::inferBounds(BoundsKey K, const AVarGraph &BKGraph, bool FromPB) { bool IsChanged = false; @@ -504,32 +521,27 @@ bool AvarBoundsInference::inferBounds(BoundsKey K, AVarGraph &BKGraph, IsChanged = inferFromPotentialBounds(K, BKGraph); } else { // Infer from the flow-graph. - std::set TmpBkeys; // Try to predict bounds from predecessors. - BKGraph.getPredecessors(K, TmpBkeys); - IsChanged = predictBounds(K, TmpBkeys, BKGraph); + std::set PredKeys; + BKGraph.getPredecessors(K, PredKeys); + IsChanged = predictBounds(K, PredKeys, BKGraph); } } return IsChanged; } bool AvarBoundsInference::inferFromPotentialBounds(BoundsKey BK, - AVarGraph &BKGraph) { - bool IsChanged = false; - bool Handled = false; + const AVarGraph &BKGraph) { + // If we have any inferred bounds for K then ignore potential bounds. + bool HasInferredBound = false; if (CurrIterInferBounds.find(BK) != CurrIterInferBounds.end()) { auto &BM = CurrIterInferBounds[BK]; - // If we have any inferred bounds for K then ignore potential - // bounds. - for (auto &PosB : BM) { - if (!PosB.second.empty()) { - Handled = true; - break; - } - } + HasInferredBound = llvm::any_of(BM, [](auto InfB) { + return !InfB.second.empty(); + }); } - if (!Handled) { + if (!HasInferredBound) { auto &PotBDs = BI->PotBoundsInfo; // Here, the logic is: // We first try potential bounds and if there are no potential bounds? @@ -550,10 +562,10 @@ bool AvarBoundsInference::inferFromPotentialBounds(BoundsKey BK, } if (!PotentialB.empty()) { CurrIterInferBounds[BK][PotKind] = PotentialB; - IsChanged = true; + return true; } } - return IsChanged; + return false; } bool PotentialBoundsInfo::hasPotentialCountBounds(BoundsKey PtrBK) { @@ -954,26 +966,18 @@ bool AVarBoundsInfo::addAssignment(BoundsKey L, BoundsKey R) { return true; } -// Visitor to collect all the variables that are used during the life-time -// of the visitor. -// This class also has a flag that gets set when a variable is observed -// more than once. +// Visitor to collect all the variables and structure member access that are +// used during the life-time of the visitor. class CollectDeclsVisitor : public RecursiveASTVisitor { public: - std::set ObservedDecls; - std::set StructAccess; + explicit CollectDeclsVisitor(ASTContext *Ctx) + : ObservedDecls(), StructAccess(), C(Ctx) {} - explicit CollectDeclsVisitor(ASTContext *Ctx) : C(Ctx) { - ObservedDecls.clear(); - StructAccess.clear(); - } - virtual ~CollectDeclsVisitor() { ObservedDecls.clear(); } + virtual ~CollectDeclsVisitor() {} bool VisitDeclRefExpr(DeclRefExpr *DRE) { - VarDecl *VD = dyn_cast_or_null(DRE->getDecl()); - if (VD != nullptr) { + if (auto *VD = dyn_cast_or_null(DRE->getDecl())) ObservedDecls.insert(VD); - } return true; } @@ -986,7 +990,17 @@ class CollectDeclsVisitor : public RecursiveASTVisitor { return false; } + const std::set &getObservedDecls() { return ObservedDecls; } + const std::set &getStructAccess() { return StructAccess; } + private: + // Contains all VarDecls seen by this visitor + std::set ObservedDecls; + + // Contains the source representation of all record access (MemberExpression) + // seen by this visitor. + std::set StructAccess; + ASTContext *C; }; @@ -1001,8 +1015,10 @@ bool AVarBoundsInfo::handlePointerAssignment(clang::Stmt *St, clang::Expr *L, std::set CommonVars; std::set CommonStVars; - findIntersection(LVarVis.ObservedDecls, RVarVis.ObservedDecls, CommonVars); - findIntersection(LVarVis.StructAccess, RVarVis.StructAccess, CommonStVars); + findIntersection(LVarVis.getObservedDecls(), RVarVis.getObservedDecls(), + CommonVars); + findIntersection(LVarVis.getStructAccess(), RVarVis.getStructAccess(), + CommonStVars); if (!CommonVars.empty() || CommonStVars.empty()) { for (auto *LHSCVar : CR->getExprConstraintVarsSet(L)) { @@ -1054,9 +1070,7 @@ BoundsKey AVarBoundsInfo::getVarKey(PersistentSourceLoc &PSL) { BoundsKey AVarBoundsInfo::getConstKey(uint64_t Value) { if (ConstVarKeys.find(Value) == ConstVarKeys.end()) { BoundsKey NK = ++BCount; - std::string ConsString = std::to_string(Value); - ProgramVar *NPV = ProgramVar::createNewProgramVar( - NK, ConsString, GlobalScope::getGlobalScope(), true); + ProgramVar *NPV = ProgramVar::createNewConstantVar(NK, Value); insertProgramVar(NK, NPV); ConstVarKeys[Value] = NK; } @@ -1080,36 +1094,45 @@ void AVarBoundsInfo::insertProgramVar(BoundsKey NK, ProgramVar *PV) { PVarInfo[NK] = PV; } -bool AVarBoundsInfo::performWorkListInference( - const std::set &ArrNeededBounds, AVarGraph &BKGraph, - AvarBoundsInference &BI, bool FromPB) { - bool RetVal = false; - std::set WorkList; - std::set NextIterArrs; - WorkList.clear(); - WorkList.insert(ArrNeededBounds.begin(), ArrNeededBounds.end()); - bool Changed = true; - while (Changed) { - Changed = false; - NextIterArrs.clear(); - // Are there any ARR atoms that need bounds? - while (!WorkList.empty()) { - BoundsKey CurrArrKey = *WorkList.begin(); - // Remove the bounds key from the worklist. - WorkList.erase(CurrArrKey); - // Can we find bounds for this Arr? +void AVarBoundsInfo::performWorkListInference(const AVarGraph &BKGraph, + AvarBoundsInference &BI, + bool FromPB) { + + // BoundsKeys corresponding to array pointers that need bounds. This will seed + // the initial WorkList, and be used to ensure that only BoundsKeys needing + // bounds are added to the list in subsequent iterations. + std::set ArrNeededBounds; + getBoundsNeededArrPointers(ArrNeededBounds); + + std::set WorkList(ArrNeededBounds); + while (!WorkList.empty()) { + // This set will collect BoundsKeys which are successors of a BoundsKey that + // was assigned a bound in this iteration. These subset of these that need + // bounds will be used as the worklist in the next iteration. + std::set NextIterArrs; + + for (BoundsKey CurrArrKey : WorkList) { + // inferBounds will return true if a bound was found for CurrArrKey. If a + // bound can be found, queue the successor nodes for bounds inferences in + // the next iteration of the outer loop. if (BI.inferBounds(CurrArrKey, BKGraph, FromPB)) { - RetVal = true; - Changed = true; // Get all the successors of the ARR whose bounds we just found. + // Successor BoundsKeys are added into NextIterArrs without clearing the + // current contents. BKGraph.getSuccessors(CurrArrKey, NextIterArrs); } } - if (Changed) { - findIntersection(ArrNeededBounds, NextIterArrs, WorkList); - } + + // WorkList will be cleared by findIntersection before it is used to store + // the intersection of ArrNeededBounds and NextIterArrs. If NextIterArrs is + // empty, then the intersection will also be empty, and the loop will + // terminate. + findIntersection(ArrNeededBounds, NextIterArrs, WorkList); } - return RetVal; + + // From all the sets of bounds computed for various array variables. Intersect + // them and find the common bound variable. + BI.convergeInferredBounds(); } BoundsKey AVarBoundsInfo::getCtxSensCEBoundsKey(const PersistentSourceLoc &PSL, @@ -1117,99 +1140,72 @@ BoundsKey AVarBoundsInfo::getCtxSensCEBoundsKey(const PersistentSourceLoc &PSL, return CSBKeyHandler.getCtxSensCEBoundsKey(PSL, BK); } -void AVarBoundsInfo::computerArrPointers(ProgramInfo *PI, - std::set &ArrPointers) { - auto &CS = PI->getConstraints(); - for (auto Bkey : PointerBoundsKey) { - // Regular variables. - auto &BkeyToPSL = DeclVarMap.right(); - if (BkeyToPSL.find(Bkey) != BkeyToPSL.end()) { - auto &PSL = BkeyToPSL.at(Bkey); - if (hasArray(PI->getVarMap().at(PSL), CS)) { - ArrPointers.insert(Bkey); - } - // Does this array belong to a valid program variable? - if (isInSrcArray(PI->getVarMap().at(PSL), CS)) { - InProgramArrPtrBoundsKeys.insert(Bkey); - } - - if (hasOnlyNtArray(PI->getVarMap().at(PSL), CS)) { - NtArrPointerBoundsKey.insert(Bkey); - } +void AVarBoundsInfo::computeArrPointers(const ProgramInfo *PI) { + NtArrPointerBoundsKey.clear(); + ArrPointerBoundsKey.clear(); - continue; + // Called in following loop to add a BoundsKey to the appropriate sets based + // on the pointer type of a corresponding ConstraintVariable. + auto AddToArrSets = [this, PI](BoundsKey BK, const ConstraintVariable *CV) { + if (hasArray(CV, PI->getConstraints())) + ArrPointerBoundsKey.insert(BK); + + // Does this array belong to a valid program variable? + if (isInSrcArray(CV, PI->getConstraints())) + InProgramArrPtrBoundsKeys.insert(BK); + + if (hasOnlyNtArray(CV, PI->getConstraints())) { + NtArrPointerBoundsKey.insert(BK); + // If the return value is an nt array pointer and there are no declared + // bounds? Then, we cannot find bounds for this pointer. This avoids + // placing incorrect bounds on null terminated arrays as discussed in + // https://github.com/correctcomputation/checkedc-clang/issues/553 + if (CV->getName() == RETVAR && getBounds(BK) == nullptr) + PointersWithImpossibleBounds.insert(BK); } - - // Function parameters - auto &ParmBkeyToPSL = ParamDeclVarMap.right(); - if (ParmBkeyToPSL.find(Bkey) != ParmBkeyToPSL.end()) { - auto &ParmTup = ParmBkeyToPSL.at(Bkey); + }; + + // Find a FVConstraint in the ProgramInfo function definition maps given a + // function name and filename. + auto LookupFVCons = [PI](const std::string &FuncName, + const std::string &FileName, bool IsStatic) { + if (IsStatic || !PI->getExtFuncDefnConstraint(FuncName)) + return PI->getStaticFuncConstraint(FuncName, FileName); + return PI->getExtFuncDefnConstraint(FuncName); + }; + + auto &VariableMap = DeclVarMap.right(); + auto &ParamMap = ParamDeclVarMap.right(); + auto &ReturnMap = FuncDeclVarMap.right(); + for (auto Bkey : PointerBoundsKey) { + if (VariableMap.find(Bkey) != VariableMap.end()) { + // Regular variables. + const PersistentSourceLoc &PSL = VariableMap.at(Bkey); + const ConstraintVariable *BkeyCV = PI->getVarMap().at(PSL); + AddToArrSets(Bkey, BkeyCV); + + } else if (ParamMap.find(Bkey) != ParamMap.end()) { + // Function parameters + auto &ParmTup = ParamMap.at(Bkey); std::string FuncName = std::get<0>(ParmTup); std::string FileName = std::get<1>(ParmTup); bool IsStatic = std::get<2>(ParmTup); unsigned ParmNum = std::get<3>(ParmTup); - FVConstraint *FV = nullptr; - if (IsStatic || !PI->getExtFuncDefnConstraint(FuncName)) { - FV = PI->getStaticFuncConstraint(FuncName, FileName); - } else { - FV = PI->getExtFuncDefnConstraint(FuncName); - } - - if (hasArray(FV->getExternalParam(ParmNum), CS) || - hasArray(FV->getInternalParam(ParmNum), CS)) { - ArrPointers.insert(Bkey); - } - // Does this array belong to a valid program variable? - if (isInSrcArray(FV->getExternalParam(ParmNum), CS) || - isInSrcArray(FV->getInternalParam(ParmNum), CS)) { - InProgramArrPtrBoundsKeys.insert(Bkey); - } - if (hasOnlyNtArray(FV->getExternalParam(ParmNum), CS) || - hasOnlyNtArray(FV->getInternalParam(ParmNum), CS)) { - NtArrPointerBoundsKey.insert(Bkey); - } + FVConstraint *FV = LookupFVCons(FuncName, FileName, IsStatic); + PVConstraint *ParamPVC = FV->getExternalParam(ParmNum); + AddToArrSets(Bkey, ParamPVC); - continue; - } - // Function returns. - auto &FuncKeyToPSL = FuncDeclVarMap.right(); - if (FuncKeyToPSL.find(Bkey) != FuncKeyToPSL.end()) { - auto &FuncRet = FuncKeyToPSL.at(Bkey); + } else if (ReturnMap.find(Bkey) != ReturnMap.end()) { + // Function returns. + auto &FuncRet = ReturnMap.at(Bkey); std::string FuncName = std::get<0>(FuncRet); std::string FileName = std::get<1>(FuncRet); bool IsStatic = std::get<2>(FuncRet); - const FVConstraint *FV = nullptr; - std::set Tmp; - Tmp.clear(); - if (IsStatic || !PI->getExtFuncDefnConstraint(FuncName)) { - Tmp.insert(PI->getStaticFuncConstraint(FuncName, FileName)); - FV = getOnly(Tmp); - } else { - Tmp.insert(PI->getExtFuncDefnConstraint(FuncName)); - FV = getOnly(Tmp); - } - - if (hasArray(FV->getExternalReturn(), CS) || - hasArray(FV->getInternalReturn(), CS)) { - ArrPointers.insert(Bkey); - } - // Does this array belongs to a valid program variable? - if (isInSrcArray(FV->getExternalReturn(), CS) || - isInSrcArray(FV->getInternalReturn(), CS)) { - InProgramArrPtrBoundsKeys.insert(Bkey); - } - if (hasOnlyNtArray(FV->getExternalReturn(), CS) || - hasOnlyNtArray(FV->getInternalReturn(), CS)) { - NtArrPointerBoundsKey.insert(Bkey); - // If the return value is an nt array pointer - // and there are no declared bounds? Then, we cannot - // find bounds for this pointer. - if (getBounds(Bkey) == nullptr) - PointersWithImpossibleBounds.insert(Bkey); - } - continue; + FVConstraint *FV = LookupFVCons(FuncName, FileName, IsStatic); + PVConstraint *RetPVC = FV->getExternalReturn(); + AddToArrSets(Bkey, RetPVC); } } @@ -1219,10 +1215,8 @@ void AVarBoundsInfo::computerArrPointers(ProgramInfo *PI, // of the regular bounds key, we just get the neighbours (predecessors // and successors) of the regular bounds key to get the context-sensitive // counterparts. - std::set CtxSensBKeys; - CtxSensBKeys.clear(); std::set TmpBKeys; - for (auto BK : ArrPointers) { + for (auto BK : ArrPointerBoundsKey) { CtxSensProgVarGraph.getSuccessors(BK, TmpBKeys, true); CtxSensProgVarGraph.getPredecessors(BK, TmpBKeys, true); RevCtxSensProgVarGraph.getSuccessors(BK, TmpBKeys, true); @@ -1230,24 +1224,24 @@ void AVarBoundsInfo::computerArrPointers(ProgramInfo *PI, } for (auto TBK : TmpBKeys) { - ProgramVar *TmpPVar = getProgramVar(TBK); - if (TmpPVar != nullptr) { - if (isa(TmpPVar->getScope())) { - CtxSensBKeys.insert(TBK); - } - if (isa(TmpPVar->getScope())) { - CtxSensBKeys.insert(TBK); - } + if (ProgramVar *TmpPVar = getProgramVar(TBK)) { + if (isa(TmpPVar->getScope())) + ArrPointerBoundsKey.insert(TBK); + if (isa(TmpPVar->getScope())) + ArrPointerBoundsKey.insert(TBK); } } - ArrPointers.insert(CtxSensBKeys.begin(), CtxSensBKeys.end()); + // All BoundsKey that have bounds are also array pointers. + for (auto &T : this->BInfo) + ArrPointerBoundsKey.insert(T.first); } -void AVarBoundsInfo::getBoundsNeededArrPointers( - const std::set &ArrPtrs, std::set &AB) { - // Next, get the ARR pointers that has bounds. - // These are pointers with bounds. +// Find the set of array pointers that need bounds. This is computed as all +// array pointers that do not currently have a bound, have an invalid bound, +// or have an impossible bound. +void AVarBoundsInfo::getBoundsNeededArrPointers(std::set &AB) const { + // Get the ARR pointers that have bounds. std::set ArrWithBounds; for (auto &T : BInfo) { ArrWithBounds.insert(T.first); @@ -1258,10 +1252,12 @@ void AVarBoundsInfo::getBoundsNeededArrPointers( ArrWithBounds.insert(PointersWithImpossibleBounds.begin(), PointersWithImpossibleBounds.end()); - // This are the array atoms that need bounds. - // i.e., AB = ArrPtrs - ArrPtrsWithBounds. - std::set_difference(ArrPtrs.begin(), ArrPtrs.end(), ArrWithBounds.begin(), - ArrWithBounds.end(), std::inserter(AB, AB.end())); + // Remove the above set of array pointers with bounds from the set of all + // array pointers to get the set of array pointers that need bounds. + // i.e., AB = ArrPointerBoundsKey - ArrPtrsWithBounds. + std::set_difference(ArrPointerBoundsKey.begin(), ArrPointerBoundsKey.end(), + ArrWithBounds.begin(), ArrWithBounds.end(), + std::inserter(AB, AB.end())); } // We first propagate all the bounds information from explicit @@ -1277,94 +1273,61 @@ void AVarBoundsInfo::getBoundsNeededArrPointers( // In the above case, we use n as a potential count bounds for arr. // Note: we only use potential bounds for a variable when none of its // predecessors have bounds. -bool AVarBoundsInfo::performFlowAnalysis(ProgramInfo *PI) { - bool RetVal = false; +void AVarBoundsInfo::performFlowAnalysis(ProgramInfo *PI) { auto &PStats = PI->getPerfStats(); - PStats.startArrayBoundsInferenceTime(); - AvarBoundsInference ABI(this); - // First get all the pointer vars which are ARRs - std::set ArrPointers; - NtArrPointerBoundsKey.clear(); - computerArrPointers(PI, ArrPointers); - // Repopulate array bounds key. - ArrPointerBoundsKey.clear(); - ArrPointerBoundsKey.insert(ArrPointers.begin(), ArrPointers.end()); - // All BoundsKey that has bounds are also array pointers. - for (auto &T : this->BInfo) { - ArrPointerBoundsKey.insert(T.first); - } + // First get all the pointer vars which are ARRs. Results is stored in the + // field ArrPointerBoundsKey. This also populates some other sets that seem to + // only be used for gather statistics. + computeArrPointers(PI); // Keep only highest priority bounds. - // Any thing changed? which means bounds of a variable changed - // Which means we need to recompute the flow based bounds for - // all arrays that have flow based bounds. - keepHighestPriorityBounds(ArrPointerBoundsKey); + keepHighestPriorityBounds(); + // Remove flow inferred bounds, if exist for all the array pointers. for (auto TBK : ArrPointerBoundsKey) removeBounds(TBK, FlowInferred); - std::set ArrNeededBounds, ArrNeededBoundsNew, TmpArrNeededBounds; - ArrNeededBounds.clear(); - - getBoundsNeededArrPointers(ArrPointers, ArrNeededBounds); - - bool OuterChanged, InnerChanged; - std::vector FromBVals; - // We first infer with using only flow information - // i.e., without using any potential bounds. - FromBVals.push_back(false); - // Next, we try using potential bounds. - FromBVals.push_back(true); + std::set ArrNeededBounds; + getBoundsNeededArrPointers(ArrNeededBounds); // Now compute the bounds information of all the ARR pointers that need it. // We iterate until there are no new array variables whose bounds are found. // The expectation is every iteration we will find bounds for at least one // array variable. - TmpArrNeededBounds = ArrNeededBounds; - OuterChanged = !ArrNeededBounds.empty(); + bool OuterChanged = !ArrNeededBounds.empty(); while (OuterChanged) { - TmpArrNeededBounds = ArrNeededBounds; - for (auto FromPB : FromBVals) { - InnerChanged = !ArrNeededBounds.empty(); + std::set TmpArrNeededBounds = ArrNeededBounds; + // We first infer with using only flow information i.e., without using any + // potential bounds. Next, we try using potential bounds. + // TODO: Doing this with a loop feels kind of silly. I should pull the while + // loop into a new function that takes a bool parameter and just call + // it twice. I'll do this if I can think of a meaningful name for the + // new function. + for (bool FromPB : std::vector({false, true})) { + bool InnerChanged = !ArrNeededBounds.empty(); while (InnerChanged) { - // Clear all inferred bounds. - ABI.clearInferredBounds(); + AvarBoundsInference ABI(this); // Regular flow inference (with no edges between callers and callees). - performWorkListInference(ArrNeededBounds, this->ProgVarGraph, ABI, - FromPB); - - // Converge using local bounds (i.e., within each function). - // From all the sets of bounds computed for various array variables. - // Intersect them and find the common bound variable. - ABI.convergeInferredBounds(); - - ArrNeededBoundsNew.clear(); - getBoundsNeededArrPointers(ArrPointers, ArrNeededBoundsNew); - // Now propagate the bounds information from context-sensitive keys - // to original keys (i.e., edges from callers to callees are present, - // but no local edges) - performWorkListInference(ArrNeededBoundsNew, this->CtxSensProgVarGraph, - ABI, FromPB); - - ABI.convergeInferredBounds(); + performWorkListInference(this->ProgVarGraph, ABI, FromPB); + + // Now propagate the bounds information from context-sensitive keys to + // original keys (i.e., edges from callers to callees are present, but no + // local edges). + performWorkListInference(this->CtxSensProgVarGraph, ABI, FromPB); + // Now clear all inferred bounds so that context-sensitive nodes do not // interfere with each other. ABI.clearInferredBounds(); - ArrNeededBoundsNew.clear(); - // Get array variables that still need bounds. - getBoundsNeededArrPointers(ArrPointers, ArrNeededBoundsNew); // Now propagate the bounds information from normal keys to // context-sensitive keys. - performWorkListInference(ArrNeededBoundsNew, - this->RevCtxSensProgVarGraph, ABI, FromPB); + performWorkListInference(this->RevCtxSensProgVarGraph, ABI, FromPB); - ABI.convergeInferredBounds(); - ArrNeededBoundsNew.clear(); // Get array variables that still need bounds. - getBoundsNeededArrPointers(ArrPointers, ArrNeededBoundsNew); + std::set ArrNeededBoundsNew; + getBoundsNeededArrPointers(ArrNeededBoundsNew); // Did we find bounds for new array variables? InnerChanged = (ArrNeededBounds != ArrNeededBoundsNew); @@ -1381,14 +1344,12 @@ bool AVarBoundsInfo::performFlowAnalysis(ProgramInfo *PI) { } PStats.endArrayBoundsInferenceTime(); - return RetVal; } -bool AVarBoundsInfo::keepHighestPriorityBounds(std::set &ArrPtrs) { - bool FoundBounds = false; +bool AVarBoundsInfo::keepHighestPriorityBounds() { bool HasChanged = false; - for (auto BK : ArrPtrs) { - FoundBounds = false; + for (auto BK : ArrPointerBoundsKey) { + bool FoundBounds = false; for (BoundsPriority P : PrioList) { if (FoundBounds) { // We already found bounds. So delete these bounds. @@ -1401,11 +1362,28 @@ bool AVarBoundsInfo::keepHighestPriorityBounds(std::set &ArrPtrs) { return HasChanged; } +void AVarBoundsInfo::dumpBounds() { + llvm::errs() << "Current Array Bounds: \n"; + for (auto BK : ArrPointerBoundsKey) { + ProgramVar *PV = getProgramVar(BK); + ABounds *B = getBounds(BK); + std::string Name = PV ? PV->verboseStr() : "TMP"; + std::string Bounds = B ? B->mkString(this) : "NO_BOUNDS"; + llvm::errs() << Name << " " << Bounds << "\n"; + } + llvm::errs() << "\n"; +} + void AVarBoundsInfo::dumpAVarGraph(const std::string &DFPath) { - std::error_code Err; - llvm::raw_fd_ostream DotFile(DFPath, Err); - llvm::WriteGraph(DotFile, ProgVarGraph); - DotFile.close(); + auto DumpGraph = [DFPath](AVarGraph &G, std::string N) { + std::error_code Err; + llvm::raw_fd_ostream DotFile(N + "_" + DFPath, Err); + llvm::WriteGraph(DotFile, G); + DotFile.close(); + }; + DumpGraph(ProgVarGraph, "ProgVar"); + DumpGraph(CtxSensProgVarGraph, "CtxSen"); + DumpGraph(RevCtxSensProgVarGraph, "RevCtxSen"); } bool AVarBoundsInfo::isFunctionReturn(BoundsKey BK) { @@ -1414,38 +1392,30 @@ bool AVarBoundsInfo::isFunctionReturn(BoundsKey BK) { void AVarBoundsInfo::printStats(llvm::raw_ostream &O, const CVarSet &SrcCVarSet, bool JsonFormat) const { - std::set InSrcBKeys, InSrcArrBKeys, Tmp; + std::set InSrcBKeys; for (auto *C : SrcCVarSet) { if (C->isForValidDecl() && C->hasBoundsKey()) InSrcBKeys.insert(C->getBoundsKey()); } std::set NTArraysReqBnds; - NTArraysReqBnds.clear(); - auto &NTA = NtArrPointerBoundsKey; - auto &APTRS = ArrPointerBoundsKey; - for (auto NTBK : NtArrPointerBoundsKey) { - - auto *PVG = const_cast(&ProgVarGraph); - - (*PVG).visitBreadthFirst( - NTBK, [NTBK, &NTA, &NTArraysReqBnds, &APTRS](BoundsKey BK) { - if (NTA.find(BK) == NTA.end() && APTRS.find(BK) != APTRS.end()) { - NTArraysReqBnds.insert(NTBK); - } - }); + ProgVarGraph.visitBreadthFirst(NTBK, [this, NTBK, &NTArraysReqBnds](BoundsKey BK) { + if (NtArrPointerBoundsKey.find(BK) == NtArrPointerBoundsKey.end() && + ArrPointerBoundsKey.find(BK) != ArrPointerBoundsKey.end()) + NTArraysReqBnds.insert(NTBK); + }); } std::set NTArrayReqNoBounds; - NTArrayReqNoBounds.clear(); - std::set_difference( NtArrPointerBoundsKey.begin(), NtArrPointerBoundsKey.end(), NTArraysReqBnds.begin(), NTArraysReqBnds.end(), std::inserter(NTArrayReqNoBounds, NTArrayReqNoBounds.begin())); + std::set InSrcArrBKeys; findIntersection(InProgramArrPtrBoundsKeys, InSrcBKeys, InSrcArrBKeys); + std::set Tmp; if (!JsonFormat) { findIntersection(ArrPointerBoundsKey, InSrcArrBKeys, Tmp); O << "NumPointersNeedBounds:" << Tmp.size() << ",\n"; @@ -1475,7 +1445,7 @@ bool AVarBoundsInfo::areSameProgramVar(BoundsKey B1, BoundsKey B2) { ProgramVar *P1 = getProgramVar(B1); ProgramVar *P2 = getProgramVar(B2); return P1->isNumConstant() && P2->isNumConstant() && - P1->getVarName() == P2->getVarName(); + P1->getConstantVal() == P2->getConstantVal(); } return B1 == B2; } diff --git a/clang/lib/3C/ArrayBoundsInferenceConsumer.cpp b/clang/lib/3C/ArrayBoundsInferenceConsumer.cpp index 278aac39effe..95ac3123c0cd 100644 --- a/clang/lib/3C/ArrayBoundsInferenceConsumer.cpp +++ b/clang/lib/3C/ArrayBoundsInferenceConsumer.cpp @@ -91,7 +91,7 @@ static bool needArrayBounds(const ConstraintVariable *CV, const EnvironmentMap &E) { if (CV->hasArr(E, 0)) { const PVConstraint *PV = dyn_cast(CV); - return !PV || PV->isTopCvarUnsizedArr(); + return !PV || PV->isTopAtomUnsizedArr(); } return false; } @@ -100,7 +100,7 @@ static bool needNTArrayBounds(const ConstraintVariable *CV, const EnvironmentMap &E) { if (CV->hasNtArr(E, 0)) { const PVConstraint *PV = dyn_cast(CV); - return !PV || PV->isTopCvarUnsizedArr(); + return !PV || PV->isTopAtomUnsizedArr(); } return false; } @@ -143,25 +143,13 @@ static std::map> AllocatorSizeAssoc = { {"malloc", {0}}, {"calloc", {0, 1}}}; // Get the name of the function called by this call expression. -static std::string getCalledFunctionName(const Expr *E) { - const CallExpr *CE = dyn_cast(E); - assert(CE && "The provided expression should be a call expression."); +static std::string getCalledFunctionName(const CallExpr *CE) { const FunctionDecl *CalleeDecl = dyn_cast(CE->getCalleeDecl()); if (CalleeDecl && CalleeDecl->getDeclName().isIdentifier()) return std::string(CalleeDecl->getName()); return ""; } -/*bool tryGetBoundsKeyVar(Expr *E, BoundsKey &BK, ProgramInfo &Info, - ASTContext *Context) { - ConstraintResolver CR(Info, Context); - CVarSet CVs = CR.getExprConstraintVarsSet(E); - auto &ABInfo = Info.getABoundsInfo(); - return CR.resolveBoundsKey(CVs, BK) || - ABInfo.tryGetVariable(E, *Context, BK); - -}*/ - bool tryGetBoundsKeyVar(Decl *D, BoundsKey &BK, ProgramInfo &Info, ASTContext *Context) { ConstraintResolver CR(Info, Context); @@ -189,24 +177,27 @@ bool tryGetValidBoundsKey(Expr *E, BoundsKey &BK, ProgramInfo &I, return Ret; } +bool hasValidBoundsKey(Expr *E, ProgramInfo &I, ASTContext *C) { + BoundsKey Unused; + return tryGetValidBoundsKey(E, Unused, I, C); +} + // Check if the provided expression E is a call to one of the known // memory allocators. Will only return true if the argument to the call // is a simple expression, and then organizes the ArgVals for determining // a possible bound -static bool isAllocatorCall(Expr *E, std::string &FName, ProgramInfo &I, - ASTContext *C, std::vector &ArgVals) { +static bool isAllocatorCall(Expr *E, ProgramInfo &I, ASTContext *C, + std::vector &ArgVals) { bool RetVal = false; if (CallExpr *CE = dyn_cast(removeAuxillaryCasts(E))) if (CE->getCalleeDecl() != nullptr) { // Is this a call to a named function? - FName = getCalledFunctionName(CE); + std::string FName = getCalledFunctionName(CE); // check if the called function is a known allocator? if (AllocatorSizeAssoc.find(FName) != AllocatorSizeAssoc.end()) { RetVal = true; - BoundsKey Tmp; // First get all base expressions. std::vector BaseExprs; - BaseExprs.clear(); for (auto Pidx : AllocatorSizeAssoc[FName]) { Expr *PExpr = CE->getArg(Pidx)->IgnoreParenCasts(); BinaryOperator *BO = dyn_cast(PExpr); @@ -217,7 +208,7 @@ static bool isAllocatorCall(Expr *E, std::string &FName, ProgramInfo &I, BaseExprs.push_back(BO->getRHS()); } else if (UExpr && UExpr->getKind() == UETT_SizeOf) { BaseExprs.push_back(UExpr); - } else if (tryGetValidBoundsKey(PExpr, Tmp, I, C)) { + } else if (hasValidBoundsKey(PExpr, I, C)) { BaseExprs.push_back(PExpr); } else { RetVal = false; @@ -227,13 +218,13 @@ static bool isAllocatorCall(Expr *E, std::string &FName, ProgramInfo &I, // Check if each of the expression is either sizeof or a DeclRefExpr if (RetVal && !BaseExprs.empty()) { - for (auto *TmpE : BaseExprs) { - TmpE = TmpE->IgnoreParenCasts(); + for (auto *BaseE : BaseExprs) { + BaseE = BaseE->IgnoreParenCasts(); UnaryExprOrTypeTraitExpr *UExpr = - dyn_cast(TmpE); + dyn_cast(BaseE); if ((UExpr && UExpr->getKind() == UETT_SizeOf) || - tryGetValidBoundsKey(TmpE, Tmp, I, C)) { - ArgVals.push_back(TmpE); + hasValidBoundsKey(BaseE, I, C)) { + ArgVals.push_back(BaseE); } else { RetVal = false; break; @@ -250,17 +241,18 @@ static void handleAllocatorCall(QualType LHSType, BoundsKey LK, Expr *E, auto &AVarBInfo = Info.getABoundsInfo(); auto &ABStats = AVarBInfo.getBStats(); ConstraintResolver CR(Info, Context); - std::string FnName; std::vector ArgVals; - // is the RHS expression a call to allocator function? - if (isAllocatorCall(E, FnName, Info, Context, ArgVals)) { + // Is the RHS expression a call to allocator function? isAllocatorCall + // mutates ArgVals, populating it with the argument expressions for the + // allocator call. + if (isAllocatorCall(E, Info, Context, ArgVals)) { BoundsKey RK; bool FoundSingleKeyInAllocExpr = false; // We consider everything as byte_count unless we see a sizeof // expression in which case if the type matches we use count bounds. bool IsByteBound = true; - for (auto *TmpE : ArgVals) { - UnaryExprOrTypeTraitExpr *Arg = dyn_cast(TmpE); + for (auto *ArgE : ArgVals) { + UnaryExprOrTypeTraitExpr *Arg = dyn_cast(ArgE); if (Arg && Arg->getKind() == UETT_SizeOf) { QualType STy = Context->getPointerType(Arg->getTypeOfArgument()); // This is a count bound. @@ -270,7 +262,7 @@ static void handleAllocatorCall(QualType LHSType, BoundsKey LK, Expr *E, FoundSingleKeyInAllocExpr = false; break; } - } else if (tryGetValidBoundsKey(TmpE, RK, Info, Context)) { + } else if (tryGetValidBoundsKey(ArgE, RK, Info, Context)) { // Is this variable? if (!FoundSingleKeyInAllocExpr) { FoundSingleKeyInAllocExpr = true; @@ -570,9 +562,6 @@ void LocalVarABVisitor::handleAssignment(BoundsKey LK, QualType LHSType, bool LocalVarABVisitor::handleBinAssign(BinaryOperator *O) { Expr *LHS = O->getLHS()->IgnoreParenCasts(); Expr *RHS = O->getRHS()->IgnoreParenCasts(); - ConstraintResolver CR(Info, Context); - std::string FnName; - std::vector ArgVals; BoundsKey LK; // is the RHS expression a call to allocator function? if (needArrayBounds(LHS, Info, Context) && @@ -580,15 +569,17 @@ bool LocalVarABVisitor::handleBinAssign(BinaryOperator *O) { handleAssignment(LK, LHS->getType(), RHS); } + // TODO: Why is this done only for the RHS of assignment? Why not place this + // in a VisitConditionalOperator function? // Any parameter directly used as a condition in ternary expression // cannot be length. if (ConditionalOperator *CO = dyn_cast(RHS)) - addUsedParmVarDecl(CO->getCond()); + addNonLengthParameter(CO->getCond()); return true; } -void LocalVarABVisitor::addUsedParmVarDecl(Expr *CE) { +void LocalVarABVisitor::addNonLengthParameter(Expr *CE) { if (DeclRefExpr *DRE = dyn_cast(CE->IgnoreParenCasts())) if (ParmVarDecl *PVD = dyn_cast(DRE->getDecl())) NonLengthParameters.insert(PVD); @@ -622,8 +613,8 @@ bool LocalVarABVisitor::VisitBinaryOperator(BinaryOperator *BO) { BinaryOperator::Opcode BOpcode = BO->getOpcode(); // Is this not a valid bin op for a potential length parameter? if (!isValidBinOpForLen(BOpcode)) { - addUsedParmVarDecl(BO->getLHS()); - addUsedParmVarDecl(BO->getRHS()); + addNonLengthParameter(BO->getLHS()); + addNonLengthParameter(BO->getRHS()); } if (BOpcode == BinaryOperator::Opcode::BO_Assign) { handleBinAssign(BO); @@ -632,7 +623,7 @@ bool LocalVarABVisitor::VisitBinaryOperator(BinaryOperator *BO) { } bool LocalVarABVisitor::VisitArraySubscriptExpr(ArraySubscriptExpr *E) { - addUsedParmVarDecl(E->getIdx()); + addNonLengthParameter(E->getIdx()); return true; } @@ -663,7 +654,7 @@ bool LocalVarABVisitor::VisitSwitchStmt(SwitchStmt *S) { } // Check if the provided parameter cannot be a length of an array. -bool LocalVarABVisitor::isNonLengthParameter(ParmVarDecl *PVD) { +bool LocalVarABVisitor::isNonLengthParameter(ParmVarDecl *PVD) const { if (PVD->getType().getTypePtr()->isEnumeralType()) return true; return NonLengthParameters.find(PVD) != NonLengthParameters.end(); @@ -698,19 +689,10 @@ void addMainFuncHeuristic(ASTContext *C, ProgramInfo &I, FunctionDecl *FD) { // Given a variable I, this visitor collects all the variables that are used as // RHS operand of < and I >= expression. // i.e., for all I < X expressions, it collects X. -class ComparisionVisitor : public RecursiveASTVisitor { +class ComparisonVisitor : public RecursiveASTVisitor { public: - explicit ComparisionVisitor(ProgramInfo &In, ASTContext *AC, BoundsKey I, - std::set &PossB) - : I(In), C(AC), IndxBKey(I), PB(PossB), CurrStmt(nullptr) { - CR = new ConstraintResolver(In, AC); - } - virtual ~ComparisionVisitor() { - if (CR != nullptr) { - delete (CR); - CR = nullptr; - } - } + explicit ComparisonVisitor(ProgramInfo &In, ASTContext *AC, BoundsKey I) + : I(In), C(AC), IndxBKey(I), PossibleBounds(), CurrStmt(nullptr) {} // Here, we save the most recent statement we have visited. // This is a way to keep track of the statement to which currently @@ -749,35 +731,36 @@ class ComparisionVisitor : public RecursiveASTVisitor { // return -1; // } // arr[i] = .. - IsRKeyBound &= (CurrStmt != nullptr && isa(CurrStmt)); + IsRKeyBound &= isa_and_nonnull(CurrStmt); } if (IsRKeyBound) - PB.insert(RKey); + PossibleBounds.insert(RKey); } } return true; } + const std::set &getPossibleBounds() const { + return PossibleBounds; + } + private: ProgramInfo &I; ASTContext *C; // Index variable used in dereference. - BoundsKey IndxBKey; - // Possible Bounds. - std::set &PB; - // Helper objects. - ConstraintResolver *CR; + const BoundsKey IndxBKey; + std::set PossibleBounds; // Current statement: The statement to which the processing // node belongs. This is to avoid walking the AST. Stmt *CurrStmt; }; LengthVarInference::LengthVarInference(ProgramInfo &In, ASTContext *AC, - FunctionDecl *F) - : I(In), C(AC), FD(F), CurBB(nullptr) { - - Cfg = CFG::buildCFG(nullptr, FD->getBody(), AC, CFG::BuildOptions()); + FunctionDecl *F) : + I(In), C(AC), CurBB(nullptr), + Cfg(CFG::buildCFG(nullptr, F->getBody(), AC, CFG::BuildOptions())), + CDG(Cfg.get()) { for (auto *CBlock : *(Cfg.get())) { for (auto &CfgElem : *CBlock) { if (CfgElem.getKind() == clang::CFGElement::Statement) { @@ -786,21 +769,6 @@ LengthVarInference::LengthVarInference(ProgramInfo &In, ASTContext *AC, } } } - - CDG = new ControlDependencyCalculator(Cfg.get()); - - CR = new ConstraintResolver(I, C); -} - -LengthVarInference::~LengthVarInference() { - if (CDG != nullptr) { - delete (CDG); - CDG = nullptr; - } - if (CR != nullptr) { - delete (CR); - CR = nullptr; - } } void LengthVarInference::VisitStmt(Stmt *St) { @@ -861,30 +829,27 @@ void LengthVarInference::VisitArraySubscriptExpr(ArraySubscriptExpr *ASE) { VisitArraySubscriptExpr(SubASE); return; } - //auto BaseCVars = CR->getExprConstraintVars(BE); + // Next get the index used. Expr *IdxExpr = ASE->getIdx()->IgnoreParenCasts(); - //auto IdxCVars = CR->getExprConstraintVars(IdxExpr); - BoundsKey BasePtr, IdxKey; - auto &ABI = I.getABoundsInfo(); // Get the bounds key of the base and index. + BoundsKey BasePtr, IdxKey; if (tryGetValidBoundsKey(BE, BasePtr, I, C) && tryGetValidBoundsKey(IdxExpr, IdxKey, I, C)) { - std::set PossibleLens; - PossibleLens.clear(); - ComparisionVisitor CV(I, C, IdxKey, PossibleLens); - auto &CDNodes = CDG->getControlDependencies(CurBB); + auto &ABI = I.getABoundsInfo(); + auto &CDNodes = CDG.getControlDependencies(CurBB); if (!CDNodes.empty()) { // Next try to find all the nodes that the CurBB is // control dependent on. // For each of the control dependent node, check if we are comparing the // index variable with another variable. + ComparisonVisitor CV(I, C, IdxKey); for (auto &CDGNode : CDNodes) { // Collect the possible length bounds keys. CV.TraverseStmt(CDGNode->getTerminatorStmt()); } - ABI.updatePotentialCountBounds(BasePtr, PossibleLens); + ABI.updatePotentialCountBounds(BasePtr, CV.getPossibleBounds()); } else { ABI.updatePotentialCountPOneBounds(BasePtr, {IdxKey}); } @@ -893,26 +858,27 @@ void LengthVarInference::VisitArraySubscriptExpr(ArraySubscriptExpr *ASE) { void handleArrayVariablesBoundsDetection(ASTContext *C, ProgramInfo &I, bool UseHeuristics) { - // Run array bounds - for (auto FuncName : _3COpts.AllocatorFunctions) { + // This is adding function names provided to --use-malloc to the set of + // allocator functions. It assumes that the first argument is always the size, + // which should be correct if the function have the same interface as malloc. + for (auto FuncName : _3COpts.AllocatorFunctions) AllocatorSizeAssoc[FuncName] = {0}; - } + GlobalABVisitor GlobABV(C, I); + LocalVarABVisitor LocABV(C, I); TranslationUnitDecl *TUD = C->getTranslationUnitDecl(); - LocalVarABVisitor LFV = LocalVarABVisitor(C, I); - bool GlobalTraversed; // First visit all the structure members. for (const auto &D : TUD->decls()) { - GlobalTraversed = false; + bool GlobalTraversed = false; if (FunctionDecl *FD = dyn_cast(D)) { if (FD->hasBody() && FD->isThisDeclarationADefinition()) { // Try to guess the bounds information for function locals. Stmt *Body = FD->getBody(); - LFV.TraverseStmt(Body); + LocABV.TraverseStmt(Body); if (UseHeuristics) { // Set information collected after analyzing the function body. - GlobABV.setParamHeuristicInfo(&LFV); + GlobABV.setParamHeuristicInfo(&LocABV); GlobABV.TraverseDecl(D); } addMainFuncHeuristic(C, I, FD); diff --git a/clang/lib/3C/ConstraintVariables.cpp b/clang/lib/3C/ConstraintVariables.cpp index e288fb6aed28..b396e90bde16 100644 --- a/clang/lib/3C/ConstraintVariables.cpp +++ b/clang/lib/3C/ConstraintVariables.cpp @@ -1488,7 +1488,7 @@ bool PointerVariableConstraint::getArrPresent() const { [](auto E) { return E.second.first != O_Pointer; }); } -bool PointerVariableConstraint::isTopCvarUnsizedArr() const { +bool PointerVariableConstraint::isTopAtomUnsizedArr() const { if (ArrSizes.find(0) != ArrSizes.end()) { return ArrSizes.at(0).first != O_SizedArray; } diff --git a/clang/lib/3C/Constraints.cpp b/clang/lib/3C/Constraints.cpp index 436a95c97339..bf396b180be3 100644 --- a/clang/lib/3C/Constraints.cpp +++ b/clang/lib/3C/Constraints.cpp @@ -554,12 +554,12 @@ ConstAtom *Constraints::getAssignment(Atom *A) { return Environment.getAssignment(A); } -ConstraintsGraph &Constraints::getChkCG() { +const ConstraintsGraph &Constraints::getChkCG() const { assert(ChkCG != nullptr && "Checked Constraint graph cannot be nullptr"); return *ChkCG; } -ConstraintsGraph &Constraints::getPtrTypCG() { +const ConstraintsGraph &Constraints::getPtrTypCG() const { assert(PtrTypCG != nullptr && "Pointer type Constraint graph " "cannot be nullptr"); return *PtrTypCG; diff --git a/clang/lib/3C/CtxSensAVarBounds.cpp b/clang/lib/3C/CtxSensAVarBounds.cpp index 9ef28f4a1aaf..1fe6d9ff11c7 100644 --- a/clang/lib/3C/CtxSensAVarBounds.cpp +++ b/clang/lib/3C/CtxSensAVarBounds.cpp @@ -10,7 +10,6 @@ // CtxSensAVarBounds.h // //===----------------------------------------------------------------------===// - #include "clang/3C/CtxSensAVarBounds.h" #include "clang/3C/AVarBoundsInfo.h" #include "clang/3C/ConstraintResolver.h" @@ -23,31 +22,30 @@ // We will ignore array indexing. class StructAccessVisitor : public RecursiveASTVisitor { public: - std::vector StructAccessStr; - bool IsGlobal = false; - - explicit StructAccessVisitor(ASTContext *Ctx) : C(Ctx) { - StructAccessStr.clear(); - } + explicit StructAccessVisitor(ASTContext *Ctx) : C(Ctx), StructAccessStr() {} virtual ~StructAccessVisitor() { StructAccessStr.clear(); } + bool isGlobal() const { return IsGlobal; } + void processVarDecl(VarDecl *VD) { - if (VD != nullptr && - (VD->getType()->isPointerType() || VD->getType()->isStructureType())) { + assert("StructAccessVisitor visiting null VarDecl" && VD != nullptr); + if (VD->getType()->isPointerType() || VD->getType()->isStructureType()) { + // If VD is a ParmVarDecl isLocalVarDecl will return false + // FIXME: This method can be called multiple times, and each time it will + // set the value of IsGlobal. This could be a problem if it is + // called on a global and then a local variable. e.g.: + // ( 0 ? global_struct_var : local_struct_var).array + // IsGlobal might be true or false depending on the order it + // encounters the variables. This would also change the order of + // entries in StructAccessStr. IsGlobal = !VD->isLocalVarDecl(); StructAccessStr.insert(StructAccessStr.begin(), VD->getNameAsString()); } } bool VisitDeclRefExpr(DeclRefExpr *DRE) { - ParmVarDecl *PD = dyn_cast_or_null(DRE->getDecl()); - if (PD != nullptr && - (PD->getType()->isPointerType() || PD->getType()->isStructureType())) { - StructAccessStr.insert(StructAccessStr.begin(), PD->getNameAsString()); - } else { - VarDecl *VD = dyn_cast_or_null(DRE->getDecl()); + if (auto *VD = dyn_cast_or_null(DRE->getDecl())) processVarDecl(VD); - } return true; } @@ -59,7 +57,7 @@ class StructAccessVisitor : public RecursiveASTVisitor { } // This gives us a string serves as a key for a struct member access. - std::string getStructAccessKey() { + std::string getStructAccessKey() const { std::string Ret = ""; for (auto CurrStr : StructAccessStr) { Ret = CurrStr + ":" + Ret; @@ -69,6 +67,8 @@ class StructAccessVisitor : public RecursiveASTVisitor { private: ASTContext *C; + bool IsGlobal = false; + std::vector StructAccessStr; }; void CtxSensitiveBoundsKeyHandler::insertCtxSensBoundsKey( @@ -146,7 +146,7 @@ CtxStKeyMap *CtxSensitiveBoundsKeyHandler::getCtxStKeyMap(MemberExpr *ME, ASTContext *C) { StructAccessVisitor SKV(C); SKV.TraverseStmt(ME->getBase()->getExprStmt()); - CtxStKeyMap *MECSMap = getCtxStKeyMap(SKV.IsGlobal); + CtxStKeyMap *MECSMap = getCtxStKeyMap(SKV.isGlobal()); return MECSMap; } @@ -247,7 +247,7 @@ void CtxSensitiveBoundsKeyHandler::contextualizeCVar(MemberExpr *ME, // Get structure access key. StructAccessVisitor SKV(C); SKV.TraverseStmt(ME->getBase()->getExprStmt()); - contextualizeCVar(RD, SKV.getStructAccessKey(), SKV.IsGlobal, C, I); + contextualizeCVar(RD, SKV.getStructAccessKey(), SKV.isGlobal(), C, I); } } @@ -259,7 +259,7 @@ void CtxSensitiveBoundsKeyHandler::contextualizeCVar(VarDecl *VD, ASTContext *C, // Get structure access key. StructAccessVisitor SKV(C); SKV.processVarDecl(VD); - contextualizeCVar(RD, SKV.getStructAccessKey(), SKV.IsGlobal, C, I); + contextualizeCVar(RD, SKV.getStructAccessKey(), SKV.isGlobal(), C, I); } } @@ -363,7 +363,7 @@ bool ContextSensitiveBoundsKeyVisitor::VisitDeclStmt(DeclStmt *DS) { // to various fields of the structure variable. const RecordDecl *Definition = ILE->getType()->getAsStructureType()->getDecl()->getDefinition(); - auto *CSKeyMap = CSBHandler.getCtxStKeyMap(SAV.IsGlobal); + auto *CSKeyMap = CSBHandler.getCtxStKeyMap(SAV.isGlobal()); unsigned int InitIdx = 0; const auto Fields = Definition->fields(); for (auto It = Fields.begin(); diff --git a/clang/lib/3C/ProgramVar.cpp b/clang/lib/3C/ProgramVar.cpp index fd58f4edcb24..f3df0bdb5c94 100644 --- a/clang/lib/3C/ProgramVar.cpp +++ b/clang/lib/3C/ProgramVar.cpp @@ -94,35 +94,26 @@ const FunctionScope *FunctionScope::getFunctionScope(std::string FnName, return &FS; } -std::set ProgramVar::AllProgramVars; +std::set ProgramVar::AllProgramVars; -std::string ProgramVar::mkString(bool GetKey) { - std::string Ret = ""; - if (GetKey) { - Ret = std::to_string(K) + "_"; - } - if (GetKey && IsConstant) { +std::string ProgramVar::verboseStr() const { + std::string Ret = std::to_string(K) + "_"; + if (IsConstant) Ret += "Cons:"; - } - Ret += VarName; - return Ret; + return Ret + VarName + "(" + VScope->getStr() + ")"; } -ProgramVar *ProgramVar::makeCopy(BoundsKey NK) { - ProgramVar *NewPVar = - new ProgramVar(NK, this->VarName, this->VScope, this->IsConstant); - return NewPVar; +ProgramVar *ProgramVar::makeCopy(BoundsKey NK) const { + return new ProgramVar(NK, this->VarName, this->VScope, this->IsConstant, + this->ConstantVal); } -std::string ProgramVar::verboseStr() { - std::string Ret = mkString(true) + "(" + VScope->getStr() + ")"; - return Ret; +ProgramVar *ProgramVar::createNewConstantVar(BoundsKey VK, + uint64_t Value) { + return new ProgramVar(VK, Value); } ProgramVar *ProgramVar::createNewProgramVar(BoundsKey VK, std::string VName, - const ProgramVarScope *PVS, - bool IsCons) { - ProgramVar *NewPV = new ProgramVar(VK, VName, PVS, IsCons); - AllProgramVars.insert(NewPV); - return NewPV; -} + const ProgramVarScope *PVS) { + return new ProgramVar(VK, VName, PVS); +} \ No newline at end of file From 12fd2112cd71a379ab630e80dd638a3ab268bf74 Mon Sep 17 00:00:00 2001 From: John Kastner Date: Tue, 21 Sep 2021 14:30:52 -0400 Subject: [PATCH 29/38] 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 correctcomputation/checkedc-clang#396 --- clang/include/clang/3C/AVarBoundsInfo.h | 4 ++ clang/include/clang/3C/ConstraintVariables.h | 5 +- clang/lib/3C/3C.cpp | 6 ++ clang/lib/3C/AVarBoundsInfo.cpp | 44 ++++++++++++-- clang/lib/3C/ConstraintResolver.cpp | 2 +- clang/lib/3C/ConstraintVariables.cpp | 64 +++++++++++--------- clang/lib/3C/Utils.cpp | 7 ++- clang/test/3C/canonical_type_cast.c | 6 +- clang/test/3C/cant_be_nt.c | 24 ++++++++ clang/test/3C/nt_const_arr.c | 61 +++++++++++++++++++ 10 files changed, 183 insertions(+), 40 deletions(-) create mode 100644 clang/test/3C/nt_const_arr.c diff --git a/clang/include/clang/3C/AVarBoundsInfo.h b/clang/include/clang/3C/AVarBoundsInfo.h index 81a004734905..bba1ee384d1f 100644 --- a/clang/include/clang/3C/AVarBoundsInfo.h +++ b/clang/include/clang/3C/AVarBoundsInfo.h @@ -197,6 +197,8 @@ class AVarBoundsInfo { bool isValidBoundVariable(clang::Decl *D); void insertDeclaredBounds(clang::Decl *D, ABounds *B); + void insertDeclaredBounds(BoundsKey BK, ABounds *B); + bool mergeBounds(BoundsKey L, BoundsPriority P, ABounds *B); bool removeBounds(BoundsKey L, BoundsPriority P = Invalid); bool replaceBounds(BoundsKey L, BoundsPriority P, ABounds *B); @@ -290,6 +292,8 @@ class AVarBoundsInfo { // If yes, provide the index of the parameter. bool isFuncParamBoundsKey(BoundsKey BK, unsigned &PIdx); + void addConstantArrayBounds(ProgramInfo &I); + private: friend class AvarBoundsInference; friend class CtxSensitiveBoundsKeyHandler; diff --git a/clang/include/clang/3C/ConstraintVariables.h b/clang/include/clang/3C/ConstraintVariables.h index b75c8a294e96..efdc7054079c 100644 --- a/clang/include/clang/3C/ConstraintVariables.h +++ b/clang/include/clang/3C/ConstraintVariables.h @@ -410,7 +410,6 @@ class PointerVariableConstraint : public ConstraintVariable { public: std::string getTy() const { return BaseType; } - bool getArrPresent() const; // Check if the outermost pointer is an unsized array. bool isTopAtomUnsizedArr() const; // Check if any of the pointers is either a sized or unsized arr. @@ -532,6 +531,10 @@ class PointerVariableConstraint : public ConstraintVariable { bool hasArr(const EnvironmentMap &E, int AIdx = -1) const override; bool hasNtArr(const EnvironmentMap &E, int AIdx = -1) const override; + bool isNtConstantArr(const EnvironmentMap &E) const; + bool isConstantArr() const; + unsigned long getConstantArrSize() const; + void equateArgumentConstraints(ProgramInfo &I) override; bool isPartOfFunctionPrototype() const { return PartOfFuncPrototype; } diff --git a/clang/lib/3C/3C.cpp b/clang/lib/3C/3C.cpp index f0859d8c76af..9cdbabb8781b 100644 --- a/clang/lib/3C/3C.cpp +++ b/clang/lib/3C/3C.cpp @@ -580,6 +580,12 @@ bool _3CInterface::solveConstraints() { dumpConstraintOutputJson(FINAL_OUTPUT_SUFFIX, GlobalProgramInfo); if (_3COpts.AllTypes) { + // Add declared bounds for all constant sized arrays. This needs to happen + // after constraint solving because the bound added depends on whether the + // array is NTARR or ARR. + GlobalProgramInfo.getABoundsInfo().addConstantArrayBounds( + GlobalProgramInfo); + if (DebugArrSolver) GlobalProgramInfo.getABoundsInfo().dumpAVarGraph( "arr_bounds_initial.dot"); diff --git a/clang/lib/3C/AVarBoundsInfo.cpp b/clang/lib/3C/AVarBoundsInfo.cpp index 94721e7d5617..3bb8bbd90745 100644 --- a/clang/lib/3C/AVarBoundsInfo.cpp +++ b/clang/lib/3C/AVarBoundsInfo.cpp @@ -640,10 +640,7 @@ bool AVarBoundsInfo::isValidBoundVariable(clang::Decl *D) { return false; } -void AVarBoundsInfo::insertDeclaredBounds(clang::Decl *D, ABounds *B) { - assert(isValidBoundVariable(D) && "Declaration not a valid bounds variable"); - BoundsKey BK; - tryGetVariable(D, BK); +void AVarBoundsInfo::insertDeclaredBounds(BoundsKey BK, ABounds *B) { if (B != nullptr) { // If there is already bounds information, release it. removeBounds(BK); @@ -656,6 +653,13 @@ void AVarBoundsInfo::insertDeclaredBounds(clang::Decl *D, ABounds *B) { } } +void AVarBoundsInfo::insertDeclaredBounds(clang::Decl *D, ABounds *B) { + assert(isValidBoundVariable(D) && "Declaration not a valid bounds variable"); + BoundsKey BK; + tryGetVariable(D, BK); + insertDeclaredBounds(BK, B); +} + bool AVarBoundsInfo::tryGetVariable(clang::Decl *D, BoundsKey &R) { bool RetVal = false; if (isValidBoundVariable(D)) { @@ -1471,3 +1475,35 @@ std::set AVarBoundsInfo::getCtxSensFieldBoundsKey(Expr *E, } return Ret; } + +// Adds declared bounds for all constant sized arrays. This needs to happen +// after constraint solving because the bounds for a _Nt_checked array and a +// _Checked array are different even if they are written with the same length. +// The Checked C bounds for a _Nt_checked array do not include the null +// terminator, but the length as written in the source code does. +void AVarBoundsInfo::addConstantArrayBounds(ProgramInfo &I) { + for (auto VarEntry : I.getVarMap()) { + if (auto *VarPCV = dyn_cast(VarEntry.second)) { + if (VarPCV->hasBoundsKey() && VarPCV->isConstantArr()) { + // Lookup the declared size of the array. This is known because it is + // written in the source and was stored during constraint generation. + unsigned int ConstantCount = VarPCV->getConstantArrSize(); + + // Check if this array solved to NTARR. If it did, subtract one from the + // length to account for the null terminator. + const EnvironmentMap &Env = I.getConstraints().getVariables(); + if (VarPCV->isNtConstantArr(Env)) { + assert("Size zero constant array should not solve to NTARR" && + ConstantCount != 0); + ConstantCount--; + } + + // Insert this as a declared constant count bound for the constraint + // variable. + BoundsKey CBKey = getConstKey(ConstantCount); + ABounds *NB = new CountBound(CBKey); + insertDeclaredBounds(VarPCV->getBoundsKey(), NB); + } + } + } +} \ No newline at end of file diff --git a/clang/lib/3C/ConstraintResolver.cpp b/clang/lib/3C/ConstraintResolver.cpp index 0964e27e6938..09e23192d907 100644 --- a/clang/lib/3C/ConstraintResolver.cpp +++ b/clang/lib/3C/ConstraintResolver.cpp @@ -382,7 +382,7 @@ CSetBkeyPair ConstraintResolver::getExprConstraintVars(Expr *E) { if (auto *PCV = dyn_cast(CV)) // On the other hand, CheckedC does let you take the address of // constant sized arrays. - if (!PCV->getArrPresent()) + if (!PCV->isConstantArr()) PCV->constrainOuterTo(CS, CS.getPtr(), true); // Add a VarAtom to UOExpr's PVConstraint, for &. Ret = std::make_pair(addAtomAll(T.first, CS.getPtr(), CS), T.second); diff --git a/clang/lib/3C/ConstraintVariables.cpp b/clang/lib/3C/ConstraintVariables.cpp index b396e90bde16..3b559b96eb5f 100644 --- a/clang/lib/3C/ConstraintVariables.cpp +++ b/clang/lib/3C/ConstraintVariables.cpp @@ -330,10 +330,6 @@ PointerVariableConstraint::PointerVariableConstraint( } InferredGenericIndex = SourceGenericIndex; - bool VarCreated = false; - bool IsArr = false; - bool IsIncompleteArr = false; - bool IsTopMost = true; uint32_t TypeIdx = 0; std::string Npre = InFunc ? ((*InFunc) + ":") : ""; VarAtom::VarKind VK = @@ -355,6 +351,8 @@ PointerVariableConstraint::PointerVariableConstraint( TLoc = D->getTypeSourceInfo()->getTypeLoc(); while (Ty->isPointerType() || Ty->isArrayType()) { + bool VarCreated = false; + // Is this a VarArg type? std::string TyName = tyToStr(Ty); if (isVarArgType(TyName)) { @@ -404,9 +402,6 @@ PointerVariableConstraint::PointerVariableConstraint( } if (Ty->isArrayType() || Ty->isIncompleteArrayType()) { - IsArr = true; - IsIncompleteArr = Ty->isIncompleteArrayType(); - // Boil off the typedefs in the array case. // TODO this will need to change to properly account for typedefs bool Boiling = true; @@ -445,13 +440,6 @@ PointerVariableConstraint::PointerVariableConstraint( ArrSizeStrs[TypeIdx] = SizeStr; } } - - // If this is the top-most pointer variable? - if (hasBoundsKey() && IsTopMost) { - BoundsKey CBKey = ABInfo.getConstKey(CAT->getSize().getZExtValue()); - ABounds *NB = new CountBound(CBKey); - ABInfo.insertDeclaredBounds(D, NB); - } } else { ArrSizes[TypeIdx] = std::pair(O_UnSizedArray, 0); @@ -479,28 +467,29 @@ PointerVariableConstraint::PointerVariableConstraint( } // This type is not a constant atom. We need to create a VarAtom for this. - if (!VarCreated) { VarAtom *VA = CS.getFreshVar(Npre + N, VK); Vars.push_back(VA); SrcVars.push_back(CS.getWild()); - // Incomplete arrays are lower bounded to ARR because the transformation - // int[] -> _Ptr is permitted while int[1] -> _Ptr is not. - if (IsIncompleteArr) - CS.addConstraint(CS.createGeq(VA, CS.getArr(), false)); - else if (IsArr) + // Incomplete arrays are not given ARR as an upper bound because the + // transformation int[] -> _Ptr is permitted but int[1] -> _Ptr + // is not. + if (ArrSizes[TypeIdx].first == O_SizedArray) { CS.addConstraint(CS.createGeq(CS.getArr(), VA, false)); + // A constant array declared with size 0 cannot be _Nt_checked. Checked + // C requires that _Nt_checked arrays are not empty since the declared + // size of the array includes the null terminator. + if (ArrSizes[TypeIdx].second == 0) + CS.addConstraint(CS.createGeq(VA, CS.getArr(), false)); + } } // Prepare for next level of pointer - VarCreated = false; - IsArr = false; TypeIdx++; Npre = Npre + "*"; - VK = VarAtom:: - V_Other; // only the outermost pointer considered a param/return - IsTopMost = false; + // Only the outermost pointer considered a param/return + VK = VarAtom::V_Other; if (!TLoc.isNull()) TLoc = TLoc.getNextTypeLoc(); } @@ -1483,9 +1472,28 @@ bool PointerVariableConstraint::hasNtArr(const EnvironmentMap &E, return false; } -bool PointerVariableConstraint::getArrPresent() const { - return llvm::any_of(ArrSizes, - [](auto E) { return E.second.first != O_Pointer; }); +bool PointerVariableConstraint::isNtConstantArr(const EnvironmentMap &E) const { + // First check that this pointer is a constant sized array. This function is + // only looking for constant sized arrays, so other array pointers should + // return false. + if (isConstantArr()) { + assert("Atoms vector for array type can't be empty." && !Vars.empty()); + // This is a constant sized array. It might have solved to WILD, ARR, or + // NTARR, but we should only return true if we know it's NTARR. + const ConstAtom *PtrType = getSolution(Vars[0], E); + return isa(PtrType); + } + return false; +} + +bool PointerVariableConstraint::isConstantArr() const { + return ArrSizes.find(0) != ArrSizes.end() && + ArrSizes.at(0).first == O_SizedArray; +} + +unsigned long PointerVariableConstraint::getConstantArrSize() const { + assert("Pointer must be a constant array to get size." && isConstantArr()); + return ArrSizes.at(0).second; } bool PointerVariableConstraint::isTopAtomUnsizedArr() const { diff --git a/clang/lib/3C/Utils.cpp b/clang/lib/3C/Utils.cpp index 662cdd4c38ac..84ede9a2f98e 100644 --- a/clang/lib/3C/Utils.cpp +++ b/clang/lib/3C/Utils.cpp @@ -209,9 +209,12 @@ bool isNullableType(const clang::QualType &QT) { } bool canBeNtArray(const clang::QualType &QT) { - if (const auto &Ptr = dyn_cast(QT)) + // First get the canonical type so that the following checks will not have to + // account for ParenType, DecayedType, or other variants used by clang. + QualType Canon = QT.getCanonicalType(); + if (const auto &Ptr = dyn_cast(Canon)) return isNullableType(Ptr->getPointeeType()); - if (const auto &Arr = dyn_cast(QT)) + if (const auto &Arr = dyn_cast(Canon)) return isNullableType(Arr->getElementType()); return false; } diff --git a/clang/test/3C/canonical_type_cast.c b/clang/test/3C/canonical_type_cast.c index c14c3d27b906..83fea53a5809 100644 --- a/clang/test/3C/canonical_type_cast.c +++ b/clang/test/3C/canonical_type_cast.c @@ -14,11 +14,9 @@ void f(int *p) { } void g(int p[]) { - //CHECK_NOALL: void g(int p[]) { - //CHECK_ALL: void g(_Ptr p) { + //CHECK: void g(_Ptr p) { int *x = (int *)p; - //CHECK_NOALL: int *x = (int *)p; - //CHECK_ALL: _Ptr x = (_Ptr)p; + //CHECK: _Ptr x = (_Ptr)p; } /* A very similar issue with function pointers */ diff --git a/clang/test/3C/cant_be_nt.c b/clang/test/3C/cant_be_nt.c index 250ed71ef5be..adf450d2d2c1 100644 --- a/clang/test/3C/cant_be_nt.c +++ b/clang/test/3C/cant_be_nt.c @@ -46,3 +46,27 @@ float* dar(struct foobar f) { //CHECK: _Ptr dar(struct foobar f) { return f.ptr; } + +// Test a bug I found in canBeNtArray triggered by some specific types. +// The P >= ARR constraint was incorrectly added to these variables, causing +// them to solve to WILD instead of NTARR. + +void force_nt_arr(char *s : itype(_Nt_array_ptr)); + +void decayed_type(char s[10]) { +//CHECK_NOALL: void decayed_type(char s[10]) { +//CHECK_ALL: void decayed_type(char s _Nt_checked[10]) { + force_nt_arr(s); +} + +void paren_type(char *(s)) { +//CHECK_NOALL: void paren_type(char *(s) : itype(_Ptr)) { +//CHECK_ALL: void paren_type(_Nt_array_ptr s) { + force_nt_arr(s); +} + +void decayed_paren_type(char (s)[10]) { +//CHECK_NOALL: void decayed_paren_type(char (s)[10]) { +//CHECK_ALL: void decayed_paren_type(char s _Nt_checked[10]) { + force_nt_arr(s); +} diff --git a/clang/test/3C/nt_const_arr.c b/clang/test/3C/nt_const_arr.c new file mode 100644 index 000000000000..f00d93496bc0 --- /dev/null +++ b/clang/test/3C/nt_const_arr.c @@ -0,0 +1,61 @@ +// RUN: rm -rf %t* +// RUN: 3c -base-dir=%S -addcr %s -- | FileCheck -match-full-lines -check-prefixes="CHECK,CHECK_NOALL" %s +// RUN: 3c -base-dir=%S -alltypes -addcr %s -- | FileCheck -match-full-lines -check-prefixes="CHECK,CHECK_ALL" %s +// RUN: 3c -base-dir=%S -alltypes -addcr %s -- | %clang -c -x c -o /dev/null - +// RUN: 3c -base-dir=%S -alltypes -output-dir=%t.checked %s -- +// RUN: 3c -base-dir=%t.checked -alltypes %t.checked/nt_const_arr.c -- | diff %t.checked/nt_const_arr.c - + +// A _Nt_checked constant sized array declared with size `n` should be treated +// as having bounds `count(n - 1)` durring bounds inference because the null +// terminator is included in the declared length but not in the Checked C +// bounds. + +unsigned long strlen(const char *s : itype(_Nt_array_ptr)); + +void foo() { + char foo[5] = "test"; + //CHECK_ALL: char foo _Nt_checked[5] = "test"; + //CHECK_NOALL: char foo[5] = "test"; + char *bar = foo; + //CHECK_ALL: _Nt_array_ptr bar : count(4) = foo; + //CHECK_NOALL: char *bar = foo; + strlen(bar); + + char *baz = foo; + //CHECK_ALL: _Array_ptr baz : count(4) = foo; + //CHECK_NOALL: char *baz = foo; + (void) baz[0]; +} + +void arr_param(char foo[10]) { +//CHECK_ALL: void arr_param(char foo _Nt_checked[10]) _Checked { +//CHECK_NOALL: void arr_param(char foo[10]) { + char *bar = foo; + //CHECK_ALL:_Nt_array_ptr bar : count(9) = foo; + //CHECK_NOALL: char *bar = foo; + strlen(bar); +} + +void empty() { + // _Nt_checked can't be empty, so this should solve to WILD + char empty_nt[0]; + char *bar = empty_nt; + //CHECK: char empty_nt[0]; + //CHECK: char *bar = empty_nt; + strlen(bar); + + + // We can have an empty string by declaring arr with size 1 + char empty_str[1] = ""; + char *baz = empty_str; + //CHECK_ALL: char empty_str _Nt_checked[1] = ""; + //CHECK_NOALL: char empty_str[1] = ""; + //CHECK_ALL: _Nt_array_ptr baz : count(0) = empty_str; + //CHECK_NOALL: char *baz = empty_str; + strlen(baz); + + // _Checked can also be empty + char empty_checked[0]; + //CHECK_ALL: char empty_checked _Checked[0]; + //CHECK_NOALL: char empty_checked[0]; +} From d37e5cae2a9604ff3fcca92c69166e47b9aba63e Mon Sep 17 00:00:00 2001 From: John Kastner Date: Wed, 22 Sep 2021 09:59:08 -0400 Subject: [PATCH 30/38] Fix array bounds heuristics on itype parameters (#654) The internal constraint variable for function parameters was used to checked the solved pointer type when adding array bounds heuristics. This made itype parameters look like wild pointers instead of array or nt array pointers, so the heuristics were not applied. Heuristics are now applied based on the external parameter, which solves to a checked type for itype parameters. This resolves the remaining issue in correctcomputation/checkedc-clang#487 --- clang/lib/3C/ArrayBoundsInferenceConsumer.cpp | 31 ++++++++++------ clang/test/3C/arrboundsheuristicsitype.c | 35 +++++++++++++++++++ 2 files changed, 55 insertions(+), 11 deletions(-) create mode 100644 clang/test/3C/arrboundsheuristicsitype.c diff --git a/clang/lib/3C/ArrayBoundsInferenceConsumer.cpp b/clang/lib/3C/ArrayBoundsInferenceConsumer.cpp index 95ac3123c0cd..5f25bc9af9d9 100644 --- a/clang/lib/3C/ArrayBoundsInferenceConsumer.cpp +++ b/clang/lib/3C/ArrayBoundsInferenceConsumer.cpp @@ -118,16 +118,20 @@ static bool needArrayBounds(Expr *E, ProgramInfo &Info, ASTContext *C) { return false; } +static bool +needArrayBounds(ConstraintVariable *CV, ProgramInfo &Info, bool IsNtArr) { + const auto &E = Info.getConstraints().getVariables(); + if (IsNtArr) + return needNTArrayBounds(CV, E); + return needArrayBounds(CV, E); +} + static bool needArrayBounds(Decl *D, ProgramInfo &Info, ASTContext *C, bool IsNtArr) { - const auto &E = Info.getConstraints().getVariables(); CVarOption CVar = Info.getVariable(D, C); if (CVar.hasValue()) { ConstraintVariable &CV = CVar.getValue(); - if ((!IsNtArr && needArrayBounds(&CV, E)) || - (IsNtArr && needNTArrayBounds(&CV, E))) - return true; - return false; + return needArrayBounds(&CV, Info, IsNtArr); } return false; } @@ -440,6 +444,11 @@ bool GlobalABVisitor::VisitFunctionDecl(FunctionDecl *FD) { std::map> ParamNtArrays; std::map> LengthParams; + // The FVConstraint is needed to access the external parameter + // ConstraintVariables. External CVs are required because these + // are there variables can solve to a checked array type when the + // parameter has an itype while the internals will solve to WILD. + FVConstraint *FV = Info.getFuncConstraint(FD, Context); for (unsigned I = 0; I < FT->getNumParams(); I++) { ParmVarDecl *PVD = FD->getParamDecl(I); BoundsKey PK; @@ -451,14 +460,14 @@ bool GlobalABVisitor::VisitFunctionDecl(FunctionDecl *FD) { // Here, we are using heuristics. So we only use heuristics when // there are no bounds already computed. if (!ABInfo.getBounds(PK)) { - if (needArrayBounds(PVD, Info, Context, true)) { - // Is this an NTArray? + PVConstraint *ParamCV = FV->getExternalParam(I); + const EnvironmentMap &Env = Info.getConstraints().getVariables(); + // Is this an NTArray? + if (needNTArrayBounds(ParamCV, Env)) ParamNtArrays[I] = PVal; - } - if (needArrayBounds(PVD, Info, Context, false)) { - // Is this an array? + // Is this an array? + if (needArrayBounds(ParamCV, Env)) ParamArrays[I] = PVal; - } } // If this is a length field? diff --git a/clang/test/3C/arrboundsheuristicsitype.c b/clang/test/3C/arrboundsheuristicsitype.c new file mode 100644 index 000000000000..27e450d6d3a2 --- /dev/null +++ b/clang/test/3C/arrboundsheuristicsitype.c @@ -0,0 +1,35 @@ +// RUN: rm -rf %t* +// RUN: 3c -base-dir=%S -addcr %s -- | FileCheck -match-full-lines -check-prefixes="CHECK,CHECK_NOALL" %s +// RUN: 3c -base-dir=%S -alltypes -addcr %s -- | FileCheck -match-full-lines -check-prefixes="CHECK,CHECK_ALL" %s +// RUN: 3c -base-dir=%S -alltypes -addcr %s -- | %clang -c -x c -o /dev/null - +// RUN: 3c -base-dir=%S -alltypes -output-dir=%t.checked %s -- +// RUN: 3c -base-dir=%t.checked -alltypes %t.checked/arrboundsheuristicsitype.c -- | diff %t.checked/arrboundsheuristicsitype.c - + +// Verify that array bounds heuristics are correctly applied to parameters with +// itypes. + +void foo(void *); + +void test0(int *a, int len) { +//CHECK_ALL: void test0(int *a : itype(_Array_ptr) count(len), int len) { +//CHECK_NOALL: void test0(int *a : itype(_Ptr), int len) { + foo(a); + (void) a[len - 1]; +} + +void test1(int *a : itype(_Array_ptr), int len) { +//CHECK_ALL: void test1(int *a : itype(_Array_ptr) count(len), int len) { +//CHECK_NOALL: void test1(int *a : itype(_Ptr), int len) { + foo(a); + (void) a[len - 1]; +} + +#include + +void test2(int *a, int len) { +//CHECK_ALL: void test2(int *a : itype(_Array_ptr) count(len), int len) { +//CHECK_NOALL: void test2(int *a : itype(_Ptr), int len) { + a = malloc(sizeof(int)*len); + foo(a); + (void) a[len - 1]; +} From 2a0df403e4c803edecbe8ed6019320d7b170708a Mon Sep 17 00:00:00 2001 From: Kyle Headley Date: Tue, 28 Sep 2021 11:27:10 -0400 Subject: [PATCH 31/38] Improve user feedback about conflicting constraints (#708) Co-authored-by: Matt McCutchen (Correct Computation) Co-authored-by: John Kastner --- clang/include/clang/3C/3CInteractiveData.h | 26 +- clang/include/clang/3C/CheckedRegions.h | 4 +- clang/include/clang/3C/ConstraintResolver.h | 3 +- clang/include/clang/3C/ConstraintVariables.h | 79 ++--- clang/include/clang/3C/Constraints.h | 74 +++-- clang/include/clang/3C/ConstraintsGraph.h | 55 +++- clang/include/clang/3C/PersistentSourceLoc.h | 8 +- clang/include/clang/3C/ProgramInfo.h | 4 +- clang/lib/3C/3C.cpp | 78 +---- clang/lib/3C/3CInteractiveData.cpp | 26 +- clang/lib/3C/CheckedRegions.cpp | 8 +- clang/lib/3C/ConstraintBuilder.cpp | 31 +- clang/lib/3C/ConstraintResolver.cpp | 93 +++--- clang/lib/3C/ConstraintVariables.cpp | 297 ++++++++++--------- clang/lib/3C/Constraints.cpp | 100 ++++--- clang/lib/3C/ConstraintsGraph.cpp | 2 +- clang/lib/3C/PersistentSourceLoc.cpp | 14 +- clang/lib/3C/ProgramInfo.cpp | 94 +++--- clang/lib/3C/RewriteUtils.cpp | 23 +- clang/lib/3C/TypeVariableAnalysis.cpp | 6 +- clang/test/3C/canwrite_constraints.h | 2 +- clang/test/3C/json_formatting.c | 9 + clang/test/3C/root_cause.c | 30 +- 23 files changed, 593 insertions(+), 473 deletions(-) diff --git a/clang/include/clang/3C/3CInteractiveData.h b/clang/include/clang/3C/3CInteractiveData.h index 95012f45af11..ec66c5b263ab 100644 --- a/clang/include/clang/3C/3CInteractiveData.h +++ b/clang/include/clang/3C/3CInteractiveData.h @@ -16,18 +16,26 @@ #include "clang/3C/ConstraintVariables.h" #include "clang/3C/PersistentSourceLoc.h" -// Source info and reason for each wild pointer. -class WildPointerInferenceInfo { +// Source info and reason +class RootCauseDiagnostic { public: - WildPointerInferenceInfo(std::string Reason, const PersistentSourceLoc PSL) - : WildPtrReason(Reason), Location(PSL) {} + RootCauseDiagnostic() = default; + explicit RootCauseDiagnostic(ReasonLoc &Rsn) : Main(Rsn) {} - const std::string &getWildPtrReason() const { return WildPtrReason; } - const PersistentSourceLoc &getLocation() const { return Location; } + const std::string &getReason() { return Main.Reason; } + void setReason(const std::string &Rsn) { Main.Reason = Rsn; } + + const PersistentSourceLoc &getLocation() const { return Main.Location; } + + void addReason(const ReasonLoc &Rsn) { + Supplemental.push_back(Rsn); + } + + std::vector &additionalNotes() { return Supplemental; } private: - std::string WildPtrReason = ""; - PersistentSourceLoc Location; + ReasonLoc Main; + std::vector Supplemental; }; // Constraints information. @@ -44,7 +52,7 @@ class ConstraintsInfo { void printRootCauseStats(raw_ostream &O, Constraints &CS); int getNumPtrsAffected(ConstraintKey CK); - std::map RootWildAtomsWithReason; + std::map RootWildAtomsWithReason; CVars AllWildAtoms; CVars InSrcWildAtoms; CVars TotalNonDirectWildAtoms; diff --git a/clang/include/clang/3C/CheckedRegions.h b/clang/include/clang/3C/CheckedRegions.h index ef5316d6693f..04eba6c4ff61 100644 --- a/clang/include/clang/3C/CheckedRegions.h +++ b/clang/include/clang/3C/CheckedRegions.h @@ -81,7 +81,7 @@ class CheckedRegionFinder bool containsUncheckedPtr(clang::QualType Qt); bool containsUncheckedPtrAcc(clang::QualType Qt, std::set &Seen); bool isUncheckedStruct(clang::QualType Qt, std::set &Seen); - void emitCauseDiagnostic(PersistentSourceLoc *); + void emitCauseDiagnostic(PersistentSourceLoc); bool isWild(CVarOption CVar); clang::ASTContext *Context; @@ -89,7 +89,7 @@ class CheckedRegionFinder ProgramInfo &Info; std::set &Seen; std::map ⤅ - std::set Emitted; + std::set Emitted; bool EmitWarnings; }; diff --git a/clang/include/clang/3C/ConstraintResolver.h b/clang/include/clang/3C/ConstraintResolver.h index beeb4fbd2330..8099590cbf80 100644 --- a/clang/include/clang/3C/ConstraintResolver.h +++ b/clang/include/clang/3C/ConstraintResolver.h @@ -71,7 +71,8 @@ class ConstraintResolver { CVarSet getInvalidCastPVCons(CastExpr *E); - CVarSet addAtomAll(CVarSet CVS, ConstAtom *PtrTyp, Constraints &CS); + CVarSet addAtomAll(CVarSet CVS, ConstAtom *PtrTyp, + ReasonLoc &Rsn, Constraints &CS); CVarSet pvConstraintFromType(QualType TypE); CSetBkeyPair getAllSubExprConstraintVars(std::vector &Exprs); diff --git a/clang/include/clang/3C/ConstraintVariables.h b/clang/include/clang/3C/ConstraintVariables.h index efdc7054079c..470363066b11 100644 --- a/clang/include/clang/3C/ConstraintVariables.h +++ b/clang/include/clang/3C/ConstraintVariables.h @@ -142,10 +142,7 @@ class ConstraintVariable { virtual bool solutionEqualTo(Constraints &, const ConstraintVariable *, bool ComparePtyp = true) const = 0; - virtual void constrainToWild(Constraints &CS, - const std::string &Rsn) const = 0; - virtual void constrainToWild(Constraints &CS, const std::string &Rsn, - PersistentSourceLoc *PL) const = 0; + virtual void constrainToWild(Constraints &CS, const ReasonLoc &Rsn) const = 0; // Return true if this variable was checked in the input. Checked variables // might solve to WILD, and unchecked variables might solve to checked. Use @@ -177,7 +174,7 @@ class ConstraintVariable { virtual bool hasNtArr(const EnvironmentMap &E, int AIdx = -1) const = 0; // Force use of equality constraints in function calls for this CV - virtual void equateArgumentConstraints(ProgramInfo &I) = 0; + virtual void equateArgumentConstraints(ProgramInfo &I, ReasonLoc &Rsn) = 0; // Internally combine the constraints and other data from the first parameter // with this constraint variable. Used with redeclarations, especially of @@ -208,17 +205,25 @@ class ConstraintVariable { // achieve that, we additionally constrain the internal variables to not // change. // - // Some cases in which the itype must not change at all are indicated by - // passing a reason for the "root cause of wildness" as ReasonUnchangeable. - // Otherwise ReasonUnchangeable should be set to the empty string. + // Some cases in which the itype needs to be constrained not change at all are + // indicated by passing a non-default reason for the "root cause of wildness" + // in ReasonUnchangeable. If the reason is DEFAULT_REASON, this is a sentinel + // meaning that the caller is not requesting such a constraint. Other cases + // that need the constraint are detected within equateWithItype itself, and + // the appropriate reason is attached there. + // + // TODO: It looks like there may be some unusual cases in which + // equateWithType generates constraints using the reason from + // ReasonUnchangeable even if it is the DEFAULT_REASON sentinel. Rethink the + // equateWithItype design to figure out what reason should actually be used or + // if those constraints should be generated at all. virtual void equateWithItype(ProgramInfo &CS, - const std::string &ReasonUnchangeable, - PersistentSourceLoc *PSL) = 0; + const ReasonLoc &ReasonUnchangeable) = 0; // Copy this variable and replace all VarAtoms with fresh VarAtoms. Using // fresh atoms allows the new variable to solve to different types than the // original. - virtual ConstraintVariable *getCopy(Constraints &CS) = 0; + virtual ConstraintVariable *getCopy(ReasonLoc &Rsn, Constraints &CS) = 0; virtual ~ConstraintVariable(){}; }; @@ -230,15 +235,15 @@ enum ConsAction { Safe_to_Wild, Wild_to_Safe, Same_to_Same }; void constrainConsVarGeq(const std::set &LHS, const std::set &RHS, - Constraints &CS, PersistentSourceLoc *PL, + Constraints &CS, const ReasonLoc &Rsn, ConsAction CA, bool DoEqType, ProgramInfo *Info, bool HandleBoundsKey = true); void constrainConsVarGeq(ConstraintVariable *LHS, const CVarSet &RHS, - Constraints &CS, PersistentSourceLoc *PL, + Constraints &CS, const ReasonLoc &Rsn, ConsAction CA, bool DoEqType, ProgramInfo *Info, bool HandleBoundsKey = true); void constrainConsVarGeq(ConstraintVariable *LHS, ConstraintVariable *RHS, - Constraints &CS, PersistentSourceLoc *PL, + Constraints &CS, const ReasonLoc &Rsn, ConsAction CA, bool DoEqType, ProgramInfo *Info, bool HandleBoundsKey = true); @@ -276,8 +281,7 @@ class PointerVariableConstraint : public ConstraintVariable { // This causes problems if the variable is later used as a deeper // pointer type. See correctcomputation/checkedc-clang#673. static PointerVariableConstraint * - getWildPVConstraint(Constraints &CS, const std::string &Rsn, - PersistentSourceLoc *PSL = nullptr); + getWildPVConstraint(Constraints &CS, const ReasonLoc &Rsn); // Get constraint variables representing values that are not pointers. If a // meaningful name can be assigned to the value, the second method should be @@ -292,7 +296,7 @@ class PointerVariableConstraint : public ConstraintVariable { // by the original constraint variable. static PointerVariableConstraint * addAtomPVConstraint(PointerVariableConstraint *PVC, ConstAtom *PtrTyp, - Constraints &CS); + ReasonLoc &Rsn, Constraints &CS); // Return a new constraint variable representing the result of dereferencing // the input constraint variable. This is accomplished by first copying the @@ -518,12 +522,11 @@ class PointerVariableConstraint : public ConstraintVariable { void dump() const override { print(llvm::errs()); } void dumpJson(llvm::raw_ostream &O) const override; - void constrainToWild(Constraints &CS, const std::string &Rsn) const override; - void constrainToWild(Constraints &CS, const std::string &Rsn, - PersistentSourceLoc *PL) const override; - void constrainOuterTo(Constraints &CS, ConstAtom *C, bool DoLB = false, - bool Soft = false); + void constrainToWild(Constraints &CS, const ReasonLoc &Rsn) const override; + void constrainOuterTo(Constraints &CS, ConstAtom *C, const ReasonLoc &Rsn, + bool DoLB = false, bool Soft = false); void constrainIdxTo(Constraints &CS, ConstAtom *C, unsigned int Idx, + const ReasonLoc &Rsn, bool DoLB = false, bool Soft = false); bool anyChanges(const EnvironmentMap &E) const override; bool anyArgumentIsWild(const EnvironmentMap &E); @@ -535,15 +538,16 @@ class PointerVariableConstraint : public ConstraintVariable { bool isConstantArr() const; unsigned long getConstantArrSize() const; - void equateArgumentConstraints(ProgramInfo &I) override; + void equateArgumentConstraints(ProgramInfo &I, ReasonLoc &Rsn) override; bool isPartOfFunctionPrototype() const { return PartOfFuncPrototype; } // Add the provided constraint variable as an argument constraint. - bool addArgumentConstraint(ConstraintVariable *DstCons, ProgramInfo &Info); + bool addArgumentConstraint(ConstraintVariable *DstCons, + ReasonLoc &Rsn, ProgramInfo &Info); // Get the set of constraint variables corresponding to the arguments. const std::set &getArgumentConstraints() const; - PointerVariableConstraint *getCopy(Constraints &CS) override; + PointerVariableConstraint *getCopy(ReasonLoc &Rsn, Constraints &CS) override; // Retrieve the atom at the specified index. This function includes special // handling for generic constraint variables to create deeper pointers as @@ -552,8 +556,8 @@ class PointerVariableConstraint : public ConstraintVariable { ~PointerVariableConstraint() override{}; - void equateWithItype(ProgramInfo &CS, const std::string &ReasonUnchangeable, - PersistentSourceLoc *PSL) override; + void equateWithItype(ProgramInfo &CS, + const ReasonLoc &ReasonUnchangeable) override; }; typedef PointerVariableConstraint PVConstraint; @@ -582,7 +586,7 @@ class FVComponentVariable { : InternalConstraint(nullptr), ExternalConstraint(nullptr), SourceDeclaration("") {} - FVComponentVariable(FVComponentVariable *Ot, Constraints &CS); + FVComponentVariable(FVComponentVariable *Ot, ReasonLoc &Rsn, Constraints &CS); FVComponentVariable(const clang::QualType &QT, const clang::QualType &ITypeT, clang::DeclaratorDecl *D, std::string N, ProgramInfo &I, @@ -609,8 +613,8 @@ class FVComponentVariable { InternalConstraint->setGenericIndex(idx); } - void equateWithItype(ProgramInfo &CS, const std::string &ReasonUnchangeable, - PersistentSourceLoc *PSL) const; + void equateWithItype(ProgramInfo &CS, + const ReasonLoc &ReasonUnchangeable) const; bool solutionEqualTo(Constraints &CS, const FVComponentVariable *CV, bool ComparePtyp) const; @@ -640,7 +644,8 @@ class FunctionVariableConstraint : public ConstraintVariable { // Count of type parameters (originally from `_Itype_for_any(...)`). int TypeParams; - void equateFVConstraintVars(ConstraintVariable *CV, ProgramInfo &Info) const; + void equateFVConstraintVars(ConstraintVariable *CV, ProgramInfo &Info, + ReasonLoc &Rsn) const; public: FunctionVariableConstraint(clang::DeclaratorDecl *D, ProgramInfo &I, @@ -723,24 +728,22 @@ class FunctionVariableConstraint : public ConstraintVariable { void dump() const override { print(llvm::errs()); } void dumpJson(llvm::raw_ostream &O) const override; - void constrainToWild(Constraints &CS, const std::string &Rsn) const override; - void constrainToWild(Constraints &CS, const std::string &Rsn, - PersistentSourceLoc *PL) const override; + void constrainToWild(Constraints &CS, const ReasonLoc &Rsn) const override; bool anyChanges(const EnvironmentMap &E) const override; bool hasWild(const EnvironmentMap &E, int AIdx = -1) const override; bool hasArr(const EnvironmentMap &E, int AIdx = -1) const override; bool hasNtArr(const EnvironmentMap &E, int AIdx = -1) const override; - void equateArgumentConstraints(ProgramInfo &P) override; + void equateArgumentConstraints(ProgramInfo &P, ReasonLoc &Rsn) override; - FunctionVariableConstraint *getCopy(Constraints &CS) override; + FunctionVariableConstraint *getCopy(ReasonLoc &Rsn, Constraints &CS) override; bool isOriginallyChecked() const override; bool isSolutionChecked(const EnvironmentMap &E) const override; bool isSolutionFullyChecked(const EnvironmentMap &E) const override; - void equateWithItype(ProgramInfo &CS, const std::string &ReasonUnchangeable, - PersistentSourceLoc *PSL) override; + void equateWithItype(ProgramInfo &CS, + const ReasonLoc &ReasonUnchangeable) override; ~FunctionVariableConstraint() override {} }; diff --git a/clang/include/clang/3C/Constraints.h b/clang/include/clang/3C/Constraints.h index dbe0e3db23c4..85f8274b0445 100644 --- a/clang/include/clang/3C/Constraints.h +++ b/clang/include/clang/3C/Constraints.h @@ -30,10 +30,19 @@ class Constraints; class PersistentSourceLoc; class ConstraintsGraph; +#define INTERNAL_USE_REASON "This reason should never be displayed" #define DEFAULT_REASON "UNKNOWN_REASON" #define POINTER_IS_ARRAY_REASON "Pointer is array but alltypes is disabled." #define VOID_TYPE_REASON "Default void* type" #define UNWRITABLE_REASON "Source code in non-writable file." +#define INNER_POINTER_REASON "Pointer is within an outer pointer" +#define ALLOCATOR_REASON "Return type from an allocator" +#define ARRAY_REASON "Lowerbounded to an array type" +#define NT_ARRAY_REASON "Lowerbounded to an nt_array type" +// SPECIAL_REASON("name_of_func") +#define SPECIAL_REASON(spec) (std::string("Special case for ") + (spec)) +#define STRING_LITERAL_REASON "The type of a string literal" +#define MACRO_REASON "Pointer in Macro declaration." template struct PComp { bool operator()(const T Lhs, const T Rhs) const { return *Lhs < *Rhs; } @@ -260,6 +269,17 @@ class WildAtom : public ConstAtom { } }; +// Helper struct requiring locations for every reason, that is, +// Constraints need reasons and locations to provide good user feedback +struct ReasonLoc { + ReasonLoc() : Reason(DEFAULT_REASON), Location(PersistentSourceLoc()) {} + ReasonLoc(std::string Reason, PersistentSourceLoc Loc) + : Reason(Reason), Location(Loc) {} + bool isDefault() const { return Reason == DEFAULT_REASON; } + std::string Reason; + PersistentSourceLoc Location; +}; + // Represents constraints of the form: // - a >= b class Constraint { @@ -268,16 +288,12 @@ class Constraint { private: const ConstraintKind Kind; - PersistentSourceLoc PL; + ReasonLoc Reason; + std::vector ExtraReasons; public: - std::string REASON = DEFAULT_REASON; - Constraint(ConstraintKind K) : Kind(K) {} - Constraint(ConstraintKind K, const std::string &Rsn) : Kind(K) { - REASON = Rsn; - } - Constraint(ConstraintKind K, const std::string &Rsn, PersistentSourceLoc *PL); + Constraint(ConstraintKind K, const ReasonLoc &Rsn) : Kind(K), Reason(Rsn) {} virtual ~Constraint() {} @@ -289,14 +305,24 @@ class Constraint { virtual bool operator==(const Constraint &Other) const = 0; virtual bool operator!=(const Constraint &Other) const = 0; virtual bool operator<(const Constraint &Other) const = 0; - virtual std::string getReason() const { return REASON; } - virtual void setReason(const std::string &Rsn) { REASON = Rsn; } + virtual std::string getReasonText() const { return Reason.Reason; } + virtual const ReasonLoc &getReason() const { return Reason; } + // Alter the internal reason and remove any additional reasons + virtual void setReason(const ReasonLoc &Rsn) { + Reason = Rsn; + ExtraReasons.clear(); + } + virtual std::vector &additionalReasons() { return ExtraReasons; } + // include additional reasons that will appear in output as notes + virtual void addReason(const ReasonLoc &Rsn) { + ExtraReasons.push_back(Rsn); + } bool isUnwritable(void) const { - return getReason() == UNWRITABLE_REASON; + return getReasonText() == UNWRITABLE_REASON; } - const PersistentSourceLoc &getLocation() const { return PL; } + const PersistentSourceLoc &getLocation() const { return Reason.Location; } }; // a >= b @@ -304,20 +330,11 @@ class Geq : public Constraint { friend class VarAtom; public: - Geq(Atom *Lhs, Atom *Rhs, bool IsCC = true, bool Soft = false) - : Constraint(C_Geq), Lhs(Lhs), Rhs(Rhs), IsCheckedConstraint(IsCC), - IsSoft(Soft) {} - - Geq(Atom *Lhs, Atom *Rhs, const std::string &Rsn, bool IsCC = true, + Geq(Atom *Lhs, Atom *Rhs, const ReasonLoc &Rsn, bool IsCC = true, bool Soft = false) : Constraint(C_Geq, Rsn), Lhs(Lhs), Rhs(Rhs), IsCheckedConstraint(IsCC), IsSoft(Soft) {} - Geq(Atom *Lhs, Atom *Rhs, const std::string &Rsn, PersistentSourceLoc *PL, - bool IsCC = true, bool Soft = false) - : Constraint(C_Geq, Rsn, PL), Lhs(Lhs), Rhs(Rhs), - IsCheckedConstraint(IsCC), IsSoft(Soft) {} - static bool classof(const Constraint *C) { return C->getKind() == C_Geq; } void print(llvm::raw_ostream &O) const override { @@ -325,7 +342,7 @@ class Geq : public Constraint { std::string Kind = IsCheckedConstraint ? " (C)>= " : " (P)>= "; O << Kind; Rhs->print(O); - O << ", Reason:" << REASON; + O << ", Reason: " << getReasonText(); } void dump(void) const override { print(llvm::errs()); } @@ -338,7 +355,7 @@ class Geq : public Constraint { O << ", \"isChecked\":"; O << (IsCheckedConstraint ? "true" : "false"); O << ", \"Reason\":"; - llvm::json::Value ReasonVal(REASON); + llvm::json::Value ReasonVal(getReasonText()); O << ReasonVal; O << "}}"; } @@ -468,16 +485,11 @@ class Constraints { void print(llvm::raw_ostream &) const; void dumpJson(llvm::raw_ostream &) const; - Geq *createGeq(Atom *Lhs, Atom *Rhs, bool IsCheckedConstraint = true, - bool Soft = false); - Geq *createGeq(Atom *Lhs, Atom *Rhs, const std::string &Rsn, - bool IsCheckedConstraint = true); - Geq *createGeq(Atom *Lhs, Atom *Rhs, const std::string &Rsn, - PersistentSourceLoc *PL, bool IsCheckedConstraint = true); + Geq *createGeq(Atom *Lhs, Atom *Rhs, ReasonLoc Rsn, + bool IsCheckedConstraint = true, bool Soft = false); VarAtom *createFreshGEQ(std::string Name, VarAtom::VarKind VK, ConstAtom *Con, - std::string Rsn = DEFAULT_REASON, - PersistentSourceLoc *PSL = nullptr); + ReasonLoc Rsn = ReasonLoc()); VarAtom *getFreshVar(std::string Name, VarAtom::VarKind VK); VarAtom *getOrCreateVar(ConstraintKey V, std::string Name, diff --git a/clang/include/clang/3C/ConstraintsGraph.h b/clang/include/clang/3C/ConstraintsGraph.h index 684fa8427267..deebf43f40f6 100644 --- a/clang/include/clang/3C/ConstraintsGraph.h +++ b/clang/include/clang/3C/ConstraintsGraph.h @@ -36,11 +36,12 @@ class DataNode : public llvm::DGNode, EdgeType> { DataType getData() const { return Data; } - void connectTo(NodeType &Other, bool SoftEdge = false) { - auto *BLR = new EdgeType(Other); + void connectTo(NodeType &Other, bool SoftEdge = false, + Constraint *C = nullptr) { + auto *BLR = new EdgeType(Other, C); BLR->IsSoft = SoftEdge; this->addEdge(*BLR); - auto *BRL = new EdgeType(*this); + auto *BRL = new EdgeType(*this, C); Other.addPredecessor(*BRL); } @@ -93,9 +94,12 @@ struct GraphTraits *> { template struct DataEdge : public llvm::DGEdge, DataEdge> { typedef llvm::DGEdge, DataEdge> SuperType; - explicit DataEdge(DataNode &Node) : SuperType(Node) {} - DataEdge(const DataEdge &E) : SuperType(E) {} + explicit DataEdge(DataNode &Node, Constraint *C = nullptr) + : SuperType(Node), EdgeConstraint(C) {} + DataEdge(const DataEdge &E, Constraint *C = nullptr) + : SuperType(E), EdgeConstraint(C) {} bool IsSoft = false; + Constraint *EdgeConstraint; }; class GraphVizOutputGraph; @@ -135,10 +139,10 @@ class DataGraph invalidateBFSCache(); } - void addEdge(Data L, Data R, bool SoftEdge = false) { + void addEdge(Data L, Data R, bool SoftEdge = false, Constraint *C = nullptr) { NodeType *BL = this->findOrCreateNode(L); NodeType *BR = this->findOrCreateNode(R); - BL->connectTo(*BR, SoftEdge); + BL->connectTo(*BR, SoftEdge, C); invalidateBFSCache(); } @@ -152,13 +156,15 @@ class DataGraph } } - bool getNeighbors(Data D, std::set &DataSet, bool Succ, - bool Append = false, bool IgnoreSoftEdges = false) const { + // This version provides more info by returning graph edges + // rather than data items + bool getIncidentEdges(Data D, std::set &EdgeSet, bool Succ, + bool Append = false, bool IgnoreSoftEdges = false) const { NodeType *N = this->findNode(D); if (N == nullptr) return false; if (!Append) - DataSet.clear(); + EdgeSet.clear(); llvm::SetVector Edges; if (Succ) Edges = N->getEdges(); @@ -166,10 +172,32 @@ class DataGraph Edges = N->getPredecessors(); for (auto *E : Edges) if (!E->IsSoft || !IgnoreSoftEdges) - DataSet.insert(E->getTargetNode().getData()); + EdgeSet.insert(E); + return !EdgeSet.empty(); + } + + bool getNeighbors(Data D, std::set &DataSet, bool Succ, + bool Append = false, bool IgnoreSoftEdges = false) const { + if (!Append) + DataSet.clear(); + + std::set Edges; + getIncidentEdges(D, Edges, Succ, Append, IgnoreSoftEdges); + for (auto *E : Edges) + DataSet.insert(E->getTargetNode().getData()); return !DataSet.empty(); } + bool getSuccessorsEdges(Atom *A, std::set &EdgeSet, + bool Append = false) { + return getIncidentEdges(A, EdgeSet, true, Append); + } + + bool getPredecessorsEdges(Atom *A, std::set &EdgeSet, + bool Append = false) { + return getIncidentEdges(A, EdgeSet, false, Append); + } + bool getSuccessors(Data D, std::set &DataSet, bool Append = false) const { return getNeighbors(D, DataSet, true, Append); @@ -226,7 +254,9 @@ class DataGraph }; // Specialize the graph for the checked and pointer type constraint graphs. This -// graphs stores atoms at each node. +// graphs stores atoms at each node, and constraints on each edge. These edges +// are returned by the specialized `getNeighbors` function to provide constraint +// data to clients. class ConstraintsGraph : public DataGraph { public: // Add an edge to the graph according to the Geq constraint. This is an edge @@ -237,6 +267,7 @@ class ConstraintsGraph : public DataGraph { // be able to retrieve them from the graph. std::set &getAllConstAtoms(); + typedef DataEdge EdgeType; protected: // Add vertex is overridden to save const atoms as they are added to the graph // so that getAllConstAtoms can efficiently retrieve them. diff --git a/clang/include/clang/3C/PersistentSourceLoc.h b/clang/include/clang/3C/PersistentSourceLoc.h index d03fdfb6ee12..85dfa5b4bad6 100644 --- a/clang/include/clang/3C/PersistentSourceLoc.h +++ b/clang/include/clang/3C/PersistentSourceLoc.h @@ -66,20 +66,20 @@ class PersistentSourceLoc { void dump() const { print(llvm::errs()); } static PersistentSourceLoc mkPSL(const clang::Decl *D, - clang::ASTContext &Context); + const clang::ASTContext &Context); static PersistentSourceLoc mkPSL(const clang::Stmt *S, - clang::ASTContext &Context); + const clang::ASTContext &Context); static PersistentSourceLoc mkPSL(const clang::Expr *E, - clang::ASTContext &Context); + const clang::ASTContext &Context); private: // Create a PersistentSourceLoc based on absolute file path // from the given SourceRange and SourceLocation. static PersistentSourceLoc mkPSL(clang::SourceRange SR, clang::SourceLocation SL, - clang::ASTContext &Context); + const clang::ASTContext &Context); // The source file name. std::string FileName; // Starting line number. diff --git a/clang/include/clang/3C/ProgramInfo.h b/clang/include/clang/3C/ProgramInfo.h index ae851421da84..a208458039ab 100644 --- a/clang/include/clang/3C/ProgramInfo.h +++ b/clang/include/clang/3C/ProgramInfo.h @@ -158,9 +158,9 @@ class ProgramInfo : public ProgramVariableAdder { ASTContext *C) const; void constrainWildIfMacro(ConstraintVariable *CV, SourceLocation Location, - PersistentSourceLoc *PSL = nullptr); + const ReasonLoc &Rsn); - void ensureNtCorrect(const QualType &QT, const ASTContext &C, + void ensureNtCorrect(const QualType &QT, const PersistentSourceLoc &PSL, PointerVariableConstraint *PV); void unifyIfTypedef(const QualType &QT, clang::ASTContext &, diff --git a/clang/lib/3C/3C.cpp b/clang/lib/3C/3C.cpp index 9cdbabb8781b..f7a1cf38de5c 100644 --- a/clang/lib/3C/3C.cpp +++ b/clang/lib/3C/3C.cpp @@ -710,50 +710,10 @@ ConstraintsInfo &_3CInterface::getWildPtrsInfo() { return GlobalProgramInfo.getInterimConstraintState(); } -bool _3CInterface::makeSinglePtrNonWild(ConstraintKey TargetPtr) { - std::lock_guard Lock(InterfaceMutex); - CVars RemovePtrs; - RemovePtrs.clear(); - - auto &PtrDisjointSet = GlobalProgramInfo.getInterimConstraintState(); - auto &CS = GlobalProgramInfo.getConstraints(); - - // Get all the current WILD pointers. - CVars OldWildPtrs = PtrDisjointSet.AllWildAtoms; - - // Delete the constraint that make the provided targetPtr WILD. - VarAtom *VA = CS.getOrCreateVar(TargetPtr, "q", VarAtom::V_Other); - Geq NewE(VA, CS.getWild()); - Constraint *OriginalConstraint = *CS.getConstraints().find(&NewE); - CS.removeConstraint(OriginalConstraint); - VA->getAllConstraints().erase(OriginalConstraint); - delete (OriginalConstraint); - - // Reset the constraint system. - CS.resetEnvironment(); - - // Solve the constraints. - //assert (CS == GlobalProgramInfo.getConstraints()); - runSolver(GlobalProgramInfo, FilePaths); - - // Compute new disjoint set. - GlobalProgramInfo.computeInterimConstraintState(FilePaths); - - // Get new WILD pointers. - CVars &NewWildPtrs = PtrDisjointSet.AllWildAtoms; - - // Get the number of pointers that have now converted to non-WILD. - std::set_difference(OldWildPtrs.begin(), OldWildPtrs.end(), - NewWildPtrs.begin(), NewWildPtrs.end(), - std::inserter(RemovePtrs, RemovePtrs.begin())); - - return !RemovePtrs.empty(); -} - void _3CInterface::invalidateAllConstraintsWithReason( Constraint *ConstraintToRemove) { // Get the reason for the current constraint. - std::string ConstraintRsn = ConstraintToRemove->getReason(); + std::string ConstraintRsn = ConstraintToRemove->getReasonText(); Constraints::ConstraintSet ToRemoveConstraints; Constraints &CS = GlobalProgramInfo.getConstraints(); // Remove all constraints that have the reason. @@ -770,39 +730,3 @@ void _3CInterface::invalidateAllConstraintsWithReason( delete (ToDelCons); } } - -bool _3CInterface::invalidateWildReasonGlobally(ConstraintKey PtrKey) { - std::lock_guard Lock(InterfaceMutex); - - CVars RemovePtrs; - RemovePtrs.clear(); - - auto &PtrDisjointSet = GlobalProgramInfo.getInterimConstraintState(); - auto &CS = GlobalProgramInfo.getConstraints(); - - CVars OldWildPtrs = PtrDisjointSet.AllWildAtoms; - - // Delete ALL the constraints that have the same given reason. - VarAtom *VA = CS.getOrCreateVar(PtrKey, "q", VarAtom::V_Other); - Geq NewE(VA, CS.getWild()); - Constraint *OriginalConstraint = *CS.getConstraints().find(&NewE); - invalidateAllConstraintsWithReason(OriginalConstraint); - - // Reset constraint solver. - CS.resetEnvironment(); - - // Solve the constraints. - runSolver(GlobalProgramInfo, FilePaths); - - // Recompute the WILD pointer disjoint sets. - GlobalProgramInfo.computeInterimConstraintState(FilePaths); - - // Computed the number of removed pointers. - CVars &NewWildPtrs = PtrDisjointSet.AllWildAtoms; - - std::set_difference(OldWildPtrs.begin(), OldWildPtrs.end(), - NewWildPtrs.begin(), NewWildPtrs.end(), - std::inserter(RemovePtrs, RemovePtrs.begin())); - - return !RemovePtrs.empty(); -} diff --git a/clang/lib/3C/3CInteractiveData.cpp b/clang/lib/3C/3CInteractiveData.cpp index bc6d417f513a..3a9f039f645f 100644 --- a/clang/lib/3C/3CInteractiveData.cpp +++ b/clang/lib/3C/3CInteractiveData.cpp @@ -66,7 +66,7 @@ void ConstraintsInfo::printStats(llvm::raw_ostream &O) { std::map> RsnBasedWildCKeys; for (auto &PtrR : RootWildAtomsWithReason) { if (AllWildAtoms.find(PtrR.first) != AllWildAtoms.end()) { - RsnBasedWildCKeys[PtrR.second.getWildPtrReason()].insert(PtrR.first); + RsnBasedWildCKeys[PtrR.second.getReason()].insert(PtrR.first); } } bool AddComma = false; @@ -111,8 +111,8 @@ void ConstraintsInfo::printConstraintStats(llvm::raw_ostream &O, ConstraintKey Cause) { O << "{\"ConstraintKey\":" << Cause << ", "; O << "\"Name\":\"" << CS.getVar(Cause)->getStr() << "\", "; - WildPointerInferenceInfo PtrInfo = RootWildAtomsWithReason.at(Cause); - O << "\"Reason\":\"" << PtrInfo.getWildPtrReason() << "\", "; + RootCauseDiagnostic PtrInfo = RootWildAtomsWithReason.at(Cause); + O << "\"Reason\":\"" << PtrInfo.getReason() << "\", "; O << "\"InSrc\":" << (InSrcWildAtoms.find(Cause) != InSrcWildAtoms.end()) << ", "; O << "\"Location\":"; @@ -129,8 +129,24 @@ void ConstraintsInfo::printConstraintStats(llvm::raw_ostream &O, std::set PtrsAffected = PtrSrcWMap[Cause]; O << "\"PtrsAffected\":" << PtrsAffected.size() << ","; - O << "\"PtrsScore\":" << getPtrAffectedScore(PtrsAffected); - O << "}"; + O << "\"PtrsScore\":" << getPtrAffectedScore(PtrsAffected) << ","; + + O << "\"SubReasons\":" << "["; + bool AddComma = false; + for(const ReasonLoc &Rsn : PtrInfo.additionalNotes()) { + if (AddComma) O << ","; + O << "{"; + O << "\"Rsn\":\"" << Rsn.Reason << "\", "; + O << "\"Location\":"; + const PersistentSourceLoc &RPSL = Rsn.Location; + if (RPSL.valid()) + O << llvm::json::Value(RPSL.toString()); + else + O << "null"; + AddComma = true; + O << "}"; + } + O << "]}"; } int ConstraintsInfo::getNumPtrsAffected(ConstraintKey CK) { diff --git a/clang/lib/3C/CheckedRegions.cpp b/clang/lib/3C/CheckedRegions.cpp index 8500725db594..7c6d36cebb93 100644 --- a/clang/lib/3C/CheckedRegions.cpp +++ b/clang/lib/3C/CheckedRegions.cpp @@ -339,7 +339,7 @@ bool CheckedRegionFinder::isInStatementPosition(CallExpr *C) { //TODO there are other statement positions // besides child of compound stmt auto PSL = PersistentSourceLoc::mkPSL(C, *Context); - emitCauseDiagnostic(&PSL); + emitCauseDiagnostic(PSL); return false; } @@ -430,15 +430,15 @@ void CheckedRegionFinder::markChecked(CompoundStmt *S, int Localwild) { Map[Id] = IsChecked ? IS_CHECKED : IS_UNCHECKED; } -void CheckedRegionFinder::emitCauseDiagnostic(PersistentSourceLoc *PSL) { +void CheckedRegionFinder::emitCauseDiagnostic(PersistentSourceLoc PSL) { if (Emitted.find(PSL) == Emitted.end()) { SourceManager &SM = Context->getSourceManager(); llvm::ErrorOr File = - SM.getFileManager().getFile(PSL->getFileName()); + SM.getFileManager().getFile(PSL.getFileName()); if (File.getError()) return; SourceLocation SL = - SM.translateFileLineCol(*File, PSL->getLineNo(), PSL->getColSNo()); + SM.translateFileLineCol(*File, PSL.getLineNo(), PSL.getColSNo()); if (SL.isValid()) reportCustomDiagnostic(Context->getDiagnostics(), DiagnosticsEngine::Warning, diff --git a/clang/lib/3C/ConstraintBuilder.cpp b/clang/lib/3C/ConstraintBuilder.cpp index 03cf8f714ef6..37442ca12546 100644 --- a/clang/lib/3C/ConstraintBuilder.cpp +++ b/clang/lib/3C/ConstraintBuilder.cpp @@ -201,7 +201,8 @@ class FunctionVisitor : public RecursiveASTVisitor { if (FVCons.size() > 1) { PersistentSourceLoc PL = PersistentSourceLoc::mkPSL(E->getCallee(), *Context); - constrainConsVarGeq(FVCons, FVCons, Info.getConstraints(), &PL, + auto Rsn = ReasonLoc("Multiple function variables", PL); + constrainConsVarGeq(FVCons, FVCons, Info.getConstraints(), Rsn, Same_to_Same, false, &Info); } @@ -239,6 +240,8 @@ class FunctionVisitor : public RecursiveASTVisitor { unsigned I = 0; for (const auto &A : E->arguments()) { CSetBkeyPair ArgumentConstraints; + auto ArgPSL = PersistentSourceLoc::mkPSL(A,*Context); + auto Rsn = ReasonLoc("Constrain arguments to parameters", ArgPSL); if (I < TargetFV->numParams()) { // When the function has a void* parameter, Clang will // add an implicit cast to void* here. Generating constraints @@ -267,7 +270,7 @@ class FunctionVisitor : public RecursiveASTVisitor { // Do not handle bounds key here because we will be // doing context-sensitive assignment next. constrainConsVarGeq(ParameterDC, ArgumentConstraints.first, CS, - &PL, CA, false, &Info, false); + Rsn, CA, false, &Info, false); if (_3COpts.AllTypes && TFD != nullptr && I < TFD->getNumParams()) { @@ -291,7 +294,8 @@ class FunctionVisitor : public RecursiveASTVisitor { // In `printf("... %s ...", ...)`, the argument corresponding // to the `%s` should be an _Nt_array_ptr // (https://github.com/correctcomputation/checkedc-clang/issues/549). - constrainVarsTo(ArgumentConstraints.first, CS.getNTArr()); + constrainVarsTo(ArgumentConstraints.first, CS.getNTArr(), + ReasonLoc(NT_ARRAY_REASON,ArgPSL)); } if (_3COpts.Verbose) { std::string FuncName = TargetFV->getName(); @@ -311,14 +315,15 @@ class FunctionVisitor : public RecursiveASTVisitor { // e1[e2] bool VisitArraySubscriptExpr(ArraySubscriptExpr *E) { Constraints &CS = Info.getConstraints(); - constraintInBodyVariable(E->getBase(), CS.getArr()); + auto PSL = PersistentSourceLoc::mkPSL(E,*Context); + constraintInBodyVariable(E->getBase(), CS.getArr(), + ReasonLoc(ARRAY_REASON,PSL)); return true; } // return e; bool VisitReturnStmt(ReturnStmt *S) { // Get function variable constraint of the body - PersistentSourceLoc PL = PersistentSourceLoc::mkPSL(S, *Context); CVarOption CVOpt = Info.getVariable(Function, Context); // Constrain the value returned (if present) against the return value @@ -332,8 +337,10 @@ class FunctionVisitor : public RecursiveASTVisitor { if (FVConstraint *FV = dyn_cast(&CVOpt.getValue())) { // This is to ensure that the return type of the function is same // as the type of return expression. + PersistentSourceLoc PL = PersistentSourceLoc::mkPSL(S, *Context); + auto Rsn = ReasonLoc("Return types must match", PL); constrainConsVarGeq(FV->getInternalReturn(), RconsVar, - Info.getConstraints(), &PL, Same_to_Same, false, + Info.getConstraints(), Rsn, Same_to_Same, false, &Info); } } @@ -375,18 +382,18 @@ class FunctionVisitor : public RecursiveASTVisitor { private: // Constraint all the provided vars to be // equal to the provided type i.e., (V >= type). - void constrainVarsTo(CVarSet &Vars, ConstAtom *CAtom) { + void constrainVarsTo(CVarSet &Vars, ConstAtom *CAtom, const ReasonLoc &Rsn) { Constraints &CS = Info.getConstraints(); for (const auto &I : Vars) if (PVConstraint *PVC = dyn_cast(I)) { - PVC->constrainOuterTo(CS, CAtom); + PVC->constrainOuterTo(CS, CAtom, Rsn); } } // Constraint helpers. - void constraintInBodyVariable(Expr *E, ConstAtom *CAtom) { + void constraintInBodyVariable(Expr *E, ConstAtom *CAtom, const ReasonLoc &Rsn) { CVarSet Var = CB.getExprConstraintVarsSet(E); - constrainVarsTo(Var, CAtom); + constrainVarsTo(Var, CAtom, Rsn); } // Constraint all the argument of the provided @@ -423,9 +430,11 @@ class FunctionVisitor : public RecursiveASTVisitor { std::string Rsn = "Pointer arithmetic performed on a function pointer."; CB.constraintAllCVarsToWild(Var, Rsn, E); } else { + auto PSL = PersistentSourceLoc::mkPSL(E,*Context); if (ModifyingExpr) Info.getABoundsInfo().recordArithmeticOperation(E, &CB); - constraintInBodyVariable(E, Info.getConstraints().getArr()); + constraintInBodyVariable(E, Info.getConstraints().getArr(), + ReasonLoc(ARRAY_REASON,PSL)); } } diff --git a/clang/lib/3C/ConstraintResolver.cpp b/clang/lib/3C/ConstraintResolver.cpp index 09e23192d907..77c264dbfade 100644 --- a/clang/lib/3C/ConstraintResolver.cpp +++ b/clang/lib/3C/ConstraintResolver.cpp @@ -21,21 +21,20 @@ ConstraintResolver::~ConstraintResolver() {} void ConstraintResolver::constraintAllCVarsToWild(const CVarSet &CSet, const std::string &Rsn, Expr *AtExpr) { - PersistentSourceLoc Psl; - PersistentSourceLoc *PslP = nullptr; + PersistentSourceLoc PSL; if (AtExpr != nullptr) { - Psl = PersistentSourceLoc::mkPSL(AtExpr, *Context); - PslP = &Psl; + PSL = PersistentSourceLoc::mkPSL(AtExpr, *Context); } + auto Reason = ReasonLoc(Rsn, PSL); auto &CS = Info.getConstraints(); for (const auto &A : CSet) { - if (PVConstraint *PVC = dyn_cast(A)) - PVC->constrainToWild(CS, Rsn, PslP); + if (auto *PVC = dyn_cast(A)) + PVC->constrainToWild(CS, Reason); else { - FVConstraint *FVC = dyn_cast(A); + auto *FVC = dyn_cast(A); assert(FVC != nullptr); - FVC->constrainToWild(CS, Rsn, PslP); + FVC->constrainToWild(CS, Reason); } } } @@ -65,11 +64,11 @@ CVarSet ConstraintResolver::handleDeref(CVarSet T) { // of indirection (when the constraint is PVConstraint), or return the // constraint unchanged (when the constraint is a function constraint). CVarSet ConstraintResolver::addAtomAll(CVarSet CVS, ConstAtom *PtrTyp, - Constraints &CS) { + ReasonLoc &Rsn, Constraints &CS) { CVarSet Result; for (auto *CV : CVS) { if (PVConstraint *PVC = dyn_cast(CV)) { - Result.insert(PVConstraint::addAtomPVConstraint(PVC, PtrTyp, CS)); + Result.insert(PVConstraint::addAtomPVConstraint(PVC, PtrTyp, Rsn,CS)); } else { Result.insert(CV); } @@ -142,7 +141,7 @@ CVarSet ConstraintResolver::getInvalidCastPVCons(CastExpr *E) { PersistentSourceLoc PL = PersistentSourceLoc::mkPSL(E, *Context); std::string Rsn = "Cast from " + SrcType.getAsString() + " to " + DstType.getAsString(); - P->constrainToWild(Info.getConstraints(), Rsn, &PL); + P->constrainToWild(Info.getConstraints(), ReasonLoc(Rsn, PL)); return {P}; } @@ -185,6 +184,7 @@ CSetBkeyPair ConstraintResolver::getExprConstraintVars(Expr *E) { auto &CS = Info.getConstraints(); QualType TypE = E->getType(); E = E->IgnoreParens(); + auto ExprPSL = PersistentSourceLoc::mkPSL(E,*Context); // Non-pointer (int, char, etc.) types have a special base PVConstraint. if (isNonPtrType(TypE)) { @@ -245,7 +245,8 @@ CSetBkeyPair ConstraintResolver::getExprConstraintVars(Expr *E) { SubTypE->isVoidPointerType()) && !isCastSafe(TypE, SubTypE)) { CVarSet WildCVar = getInvalidCastPVCons(IE); - constrainConsVarGeq(CVs.first, WildCVar, CS, nullptr, Safe_to_Wild, + auto Rsn = ReasonLoc("Unsafe cast",ExprPSL); + constrainConsVarGeq(CVs.first, WildCVar, CS, Rsn, Safe_to_Wild, false, &Info); Ret = std::make_pair(WildCVar, CVs.second); } else { @@ -258,11 +259,12 @@ CSetBkeyPair ConstraintResolver::getExprConstraintVars(Expr *E) { // Is cast internally safe? Return WILD if not. // If the cast is NULL, it will otherwise seem invalid, but we want to // handle it as usual so the type in the cast can be rewritten. + auto Rsn = ReasonLoc("Explicit cast", ExprPSL); if (!isNULLExpression(ECE, *Context) && TypE->isPointerType() && !isCastSafe(TypE, TmpE->getType()) && !isCastofGeneric(ECE)) { CVarSet Vars = getExprConstraintVarsSet(TmpE); Ret = pairWithEmptyBkey(getInvalidCastPVCons(ECE)); - constrainConsVarGeq(Vars, Ret.first, CS, nullptr, Safe_to_Wild, false, + constrainConsVarGeq(Vars, Ret.first, CS, Rsn, Safe_to_Wild, false, &Info); // NB: Expression ECE itself handled in // ConstraintBuilder::FunctionVisitor. @@ -276,7 +278,7 @@ CSetBkeyPair ConstraintResolver::getExprConstraintVars(Expr *E) { // constraining GEQ these vars would be the cast always be WILD. if (!isNULLExpression(ECE, *Context)) { PersistentSourceLoc PL = PersistentSourceLoc::mkPSL(ECE, *Context); - constrainConsVarGeq(P, Vars, Info.getConstraints(), &PL, Same_to_Same, + constrainConsVarGeq(P, Vars, Info.getConstraints(), Rsn, Same_to_Same, false, &Info); } } @@ -378,14 +380,22 @@ CSetBkeyPair ConstraintResolver::getExprConstraintVars(Expr *E) { // does permit taking the address of an _Array_ptr when the array // pointer has no declared bounds. With this constraint added however, // 3C will not generate such code. - for (auto *CV : T.first) - if (auto *PCV = dyn_cast(CV)) + for (auto *CV : T.first) { + if (auto *PCV = dyn_cast(CV)) { // On the other hand, CheckedC does let you take the address of // constant sized arrays. - if (!PCV->isConstantArr()) - PCV->constrainOuterTo(CS, CS.getPtr(), true); + if (!PCV->isConstantArr()) { + auto Rsn = ReasonLoc( + "Operand of address-of has PTR lower bound", ExprPSL); + PCV->constrainOuterTo(CS, CS.getPtr(), Rsn, true); + } + } + } // Add a VarAtom to UOExpr's PVConstraint, for &. - Ret = std::make_pair(addAtomAll(T.first, CS.getPtr(), CS), T.second); + auto Rsn = ReasonLoc( + "Result of address-of has PTR lower bound",ExprPSL); + Ret = std::make_pair(addAtomAll(T.first, CS.getPtr(), + Rsn, CS), T.second); } break; } @@ -472,7 +482,8 @@ CSetBkeyPair ConstraintResolver::getExprConstraintVars(Expr *E) { ExprType = Context->getPointerType(ArgTy); PVConstraint *PVC = new PVConstraint(ExprType, nullptr, N, Info, *Context, nullptr, 0); - PVC->constrainOuterTo(CS, A, true); + PVC->constrainOuterTo(CS, A, + ReasonLoc(ALLOCATOR_REASON, ExprPSL), true); ReturnCVs.insert(PVC); DidInsert = true; if (FuncName.compare("realloc") == 0) { @@ -488,7 +499,7 @@ CSetBkeyPair ConstraintResolver::getExprConstraintVars(Expr *E) { std::string Rsn = "Unsafe call to allocator function."; PersistentSourceLoc PL = PersistentSourceLoc::mkPSL(CE, *Context); ReturnCVs.insert(PVConstraint::getWildPVConstraint( - Info.getConstraints(), Rsn, &PL)); + Info.getConstraints(), ReasonLoc(Rsn, PL))); } /* Normal function call */ @@ -508,7 +519,8 @@ CSetBkeyPair ConstraintResolver::getExprConstraintVars(Expr *E) { // No FVConstraint -- make WILD. std::string Rsn = "Can't get return variable of function call."; PersistentSourceLoc PL = PersistentSourceLoc::mkPSL(CE, *Context); - ReturnCVs.insert(PVConstraint::getWildPVConstraint(CS, Rsn, &PL)); + ReturnCVs.insert( + PVConstraint::getWildPVConstraint(CS, ReasonLoc(Rsn, PL))); } } } @@ -536,7 +548,8 @@ CSetBkeyPair ConstraintResolver::getExprConstraintVars(Expr *E) { if (PCV->hasBoundsKey()) NewCV->setBoundsKey(PCV->getBoundsKey()); } else { - NewCV = CV->getCopy(CS); + auto Rsn = ReasonLoc("Function return R-value", ExprPSL); + NewCV = CV->getCopy(Rsn, CS); } } else { // Allocator functions are treated specially, so they do not have @@ -544,25 +557,26 @@ CSetBkeyPair ConstraintResolver::getExprConstraintVars(Expr *E) { NewCV = CV; } - auto PSL = PersistentSourceLoc::mkPSL(CE, *Context); // Make the bounds key context sensitive. if (NewCV->hasBoundsKey()) { auto CSensBKey = - ABI.getCtxSensCEBoundsKey(PSL, NewCV->getBoundsKey()); + ABI.getCtxSensCEBoundsKey(ExprPSL, NewCV->getBoundsKey()); NewCV->setBoundsKey(CSensBKey); } if (NewCV != CV) { // If the call is in a macro, use Same_to_Same to force checked type // equality and avoid ever needing to insert a cast inside a macro. + auto Rsn = ReasonLoc("Macro call", ExprPSL); ConsAction CA = Rewriter::isRewritable(CE->getExprLoc()) ? Safe_to_Wild : Same_to_Same; - constrainConsVarGeq(NewCV, CV, CS, &PSL, CA, false, &Info); + constrainConsVarGeq(NewCV, CV, CS, Rsn, CA, false, &Info); } TmpCVs.insert(NewCV); // If this is realloc, constrain the first arg to flow to the return + auto Rsn = ReasonLoc("Flow from realloc", ExprPSL); if (!ReallocFlow.empty()) { - constrainConsVarGeq(NewCV, ReallocFlow, Info.getConstraints(), &PSL, + constrainConsVarGeq(NewCV, ReallocFlow, Info.getConstraints(), Rsn, Wild_to_Safe, false, &Info); } } @@ -582,7 +596,9 @@ CSetBkeyPair ConstraintResolver::getExprConstraintVars(Expr *E) { if (ILE->getType()->isArrayType()) { // Array initialization is similar AddrOf, so the same pattern is // used where a new indirection is added to constraint variables. - Ret = std::make_pair(addAtomAll(CVars.first, CS.getArr(), CS), + auto Rsn = ReasonLoc("Array initialization", ExprPSL); + Ret = std::make_pair(addAtomAll(CVars.first, CS.getArr(), + Rsn, CS), CVars.second); } else { // This branch should only be taken on compound literal expressions @@ -603,7 +619,8 @@ CSetBkeyPair ConstraintResolver::getExprConstraintVars(Expr *E) { PVConstraint *P = getRewritablePVConstraint(CLE); PersistentSourceLoc PL = PersistentSourceLoc::mkPSL(CLE, *Context); - constrainConsVarGeq(P, Vars.first, Info.getConstraints(), &PL, + auto Rsn = ReasonLoc("Compound literal", PL); + constrainConsVarGeq(P, Vars.first, Info.getConstraints(), Rsn, Same_to_Same, false, &Info); CVarSet T = {P}; @@ -616,7 +633,8 @@ CSetBkeyPair ConstraintResolver::getExprConstraintVars(Expr *E) { // We create a new constraint variable and constraint it to an Nt_array. PVConstraint *P = new PVConstraint(Str, Info, *Context); - P->constrainOuterTo(CS, CS.getNTArr()); // NB: ARR already there. + P->constrainOuterTo(CS, CS.getNTArr(), + ReasonLoc(STRING_LITERAL_REASON,ExprPSL)); // NB: ARR already there. BoundsKey TmpKey = ABI.getRandomBKey(); P->setBoundsKey(TmpKey); @@ -644,7 +662,7 @@ CSetBkeyPair ConstraintResolver::getExprConstraintVars(Expr *E) { auto *P = new PVConstraint(VarArg, Info, *Context); PersistentSourceLoc PL = PersistentSourceLoc::mkPSL(E, *Context); std::string Rsn = "Accessing VarArg parameter"; - P->constrainToWild(Info.getConstraints(), Rsn, &PL); + P->constrainToWild(Info.getConstraints(), ReasonLoc(Rsn, PL)); Ret = pairWithEmptyBkey({P}); } else { if (_3COpts.Verbose) { @@ -685,7 +703,8 @@ void ConstraintResolver::constrainLocalAssign(Stmt *TSt, Expr *LHS, Expr *RHS, CSetBkeyPair L = getExprConstraintVars(LHS); CSetBkeyPair R = getExprConstraintVars(RHS); bool HandleBoundsKey = L.second.empty() && R.second.empty(); - constrainConsVarGeq(L.first, R.first, Info.getConstraints(), &PL, CAction, + auto Rsn = ReasonLoc("Local Assignment", PL); + constrainConsVarGeq(L.first, R.first, Info.getConstraints(), Rsn, CAction, false, &Info, HandleBoundsKey); // Handle pointer arithmetic. @@ -706,11 +725,11 @@ void ConstraintResolver::constrainLocalAssign(Stmt *TSt, Expr *LHS, Expr *RHS, void ConstraintResolver::constrainLocalAssign(Stmt *TSt, DeclaratorDecl *D, Expr *RHS, ConsAction CAction, bool IgnoreBnds) { - PersistentSourceLoc PL, *PLPtr = nullptr; + PersistentSourceLoc PSL; if (TSt != nullptr) { - PL = PersistentSourceLoc::mkPSL(TSt, *Context); - PLPtr = &PL; + PSL = PersistentSourceLoc::mkPSL(TSt, *Context); } + auto Rsn = ReasonLoc("Local Assignment", PSL); // Get the in-context local constraints. CVarOption V = Info.getVariable(D, Context); auto RHSCons = getExprConstraintVars(RHS); @@ -718,7 +737,7 @@ void ConstraintResolver::constrainLocalAssign(Stmt *TSt, DeclaratorDecl *D, if (V.hasValue()) constrainConsVarGeq(&V.getValue(), RHSCons.first, Info.getConstraints(), - PLPtr, CAction, false, &Info, HandleBoundsKey); + Rsn, CAction, false, &Info, HandleBoundsKey); if (_3COpts.AllTypes && !IgnoreBnds) { if (!HandleBoundsKey || (!(V.hasValue() && isValidCons(&V.getValue())) && !containsValidCons(RHSCons.first))) { @@ -811,7 +830,7 @@ bool ConstraintResolver::isCastofGeneric(CastExpr *C) { PVConstraint *ConstraintResolver::getRewritablePVConstraint(Expr *E) { PVConstraint *P = new PVConstraint(E, Info, *Context); auto PSL = PersistentSourceLoc::mkPSL(E, *Context); - Info.constrainWildIfMacro(P, E->getExprLoc(), &PSL); + Info.constrainWildIfMacro(P, E->getExprLoc(), ReasonLoc(MACRO_REASON, PSL)); return P; } diff --git a/clang/lib/3C/ConstraintVariables.cpp b/clang/lib/3C/ConstraintVariables.cpp index 3b559b96eb5f..4da97be8b4b0 100644 --- a/clang/lib/3C/ConstraintVariables.cpp +++ b/clang/lib/3C/ConstraintVariables.cpp @@ -53,10 +53,10 @@ std::string ConstraintVariable::getOriginalTypeWithName() const { } PointerVariableConstraint *PointerVariableConstraint::getWildPVConstraint( - Constraints &CS, const std::string &Rsn, PersistentSourceLoc *PSL) { + Constraints &CS, const ReasonLoc &Rsn) { auto *WildPVC = new PointerVariableConstraint("wildvar"); VarAtom *VA = CS.createFreshGEQ("wildvar", VarAtom::V_Other, CS.getWild(), - Rsn, PSL); + Rsn); WildPVC->Vars.push_back(VA); WildPVC->SrcVars.push_back(CS.getWild()); @@ -102,21 +102,23 @@ PointerVariableConstraint::derefPVConstraint(PointerVariableConstraint *PVC) { } PointerVariableConstraint *PointerVariableConstraint::addAtomPVConstraint( - PointerVariableConstraint *PVC, ConstAtom *PtrTyp, Constraints &CS) { + PointerVariableConstraint *PVC, ConstAtom *PtrTyp, + ReasonLoc &Rsn, Constraints &CS) { auto *Copy = new PointerVariableConstraint(PVC); std::vector &Vars = Copy->Vars; std::vector &SrcVars = Copy->SrcVars; VarAtom *NewA = CS.getFreshVar("&" + Copy->Name, VarAtom::V_Other); - CS.addConstraint(CS.createGeq(NewA, PtrTyp, false)); + CS.addConstraint(CS.createGeq(NewA, PtrTyp, Rsn, false)); // Add a constraint between the new atom and any existing atom for this // pointer. This is the same constraint that is added between atoms of a // pointer in the PointerVariableConstraint constructor. It forces all inner - // atoms to be wild if an outer atom in wild. + // atoms to be wild if an outer atom is wild. if (!Vars.empty()) if (auto *VA = dyn_cast(*Vars.begin())) - CS.addConstraint(new Geq(VA, NewA)); + CS.addConstraint(new Geq(VA, NewA, + ReasonLoc(INNER_POINTER_REASON, PersistentSourceLoc()))); Vars.insert(Vars.begin(), NewA); SrcVars.insert(SrcVars.begin(), PtrTyp); @@ -215,6 +217,7 @@ PointerVariableConstraint::PointerVariableConstraint( : ConstraintVariable(ConstraintVariable::PointerVariable, QT, N), FV(nullptr), SrcHasItype(false), PartOfFuncPrototype(InFunc != nullptr), Parent(nullptr) { + PersistentSourceLoc PSL = PersistentSourceLoc::mkPSL(D, C); QualType QTy = QT; const Type *Ty = QTy.getTypePtr(); auto &CS = I.getConstraints(); @@ -357,7 +360,7 @@ PointerVariableConstraint::PointerVariableConstraint( std::string TyName = tyToStr(Ty); if (isVarArgType(TyName)) { // Variable number of arguments. Make it WILD. - std::string Rsn = "Variable number of arguments."; + auto Rsn = ReasonLoc("Variable number of arguments.", PSL); VarAtom *WildVA = CS.createFreshGEQ(Npre + N, VK, CS.getWild(), Rsn); Vars.push_back(WildVA); SrcVars.push_back(CS.getWild()); @@ -475,13 +478,14 @@ PointerVariableConstraint::PointerVariableConstraint( // Incomplete arrays are not given ARR as an upper bound because the // transformation int[] -> _Ptr is permitted but int[1] -> _Ptr // is not. + auto Rsn = ReasonLoc("0-sized Array", PSL); if (ArrSizes[TypeIdx].first == O_SizedArray) { - CS.addConstraint(CS.createGeq(CS.getArr(), VA, false)); + CS.addConstraint(CS.createGeq(CS.getArr(), VA, Rsn, false)); // A constant array declared with size 0 cannot be _Nt_checked. Checked // C requires that _Nt_checked arrays are not empty since the declared // size of the array includes the null terminator. if (ArrSizes[TypeIdx].second == 0) - CS.addConstraint(CS.createGeq(VA, CS.getArr(), false)); + CS.addConstraint(CS.createGeq(VA, CS.getArr(), Rsn, false)); } } @@ -527,7 +531,7 @@ PointerVariableConstraint::PointerVariableConstraint( // TODO: Github issue #61: improve handling of types for variable arguments. for (const auto &V : Vars) if (VarAtom *VA = dyn_cast(V)) - CS.addConstraint(CS.createGeq(VA, CS.getWild(), Rsn)); + CS.addConstraint(CS.createGeq(VA, CS.getWild(), ReasonLoc(Rsn,PSL))); } // Add qualifiers. @@ -541,7 +545,7 @@ PointerVariableConstraint::PointerVariableConstraint( VarAtom *VI = dyn_cast(Vars[VarIdx]); VarAtom *VJ = dyn_cast(Vars[VarIdx + 1]); if (VI && VJ) - CS.addConstraint(new Geq(VJ, VI)); + CS.addConstraint(new Geq(VJ, VI, ReasonLoc(INNER_POINTER_REASON, PSL))); } } } @@ -981,19 +985,20 @@ PointerVariableConstraint::mkString(Constraints &CS, } bool PVConstraint::addArgumentConstraint(ConstraintVariable *DstCons, + ReasonLoc &Rsn, ProgramInfo &Info) { if (this->Parent == nullptr) { bool RetVal = false; if (isPartOfFunctionPrototype()) { RetVal = ArgumentConstraints.insert(DstCons).second; if (RetVal && this->HasEqArgumentConstraints) { - constrainConsVarGeq(DstCons, this, Info.getConstraints(), nullptr, + constrainConsVarGeq(DstCons, this, Info.getConstraints(), Rsn, Same_to_Same, true, &Info); } } return RetVal; } - return this->Parent->addArgumentConstraint(DstCons, Info); + return this->Parent->addArgumentConstraint(DstCons, Rsn, Info); } const CVarSet &PVConstraint::getArgumentConstraints() const { @@ -1041,6 +1046,7 @@ FunctionVariableConstraint::FunctionVariableConstraint( HasEqArgumentConstraints = false; IsFunctionPtr = true; TypeParams = 0; + PersistentSourceLoc PSL; // Metadata about function. FunctionDecl *FD = nullptr; @@ -1057,7 +1063,7 @@ FunctionVariableConstraint::FunctionVariableConstraint( Hasbody = true; IsStatic = !(FD->isGlobal()); ASTContext *TmpCtx = const_cast(&Ctx); - auto PSL = PersistentSourceLoc::mkPSL(D, *TmpCtx); + PSL = PersistentSourceLoc::mkPSL(D, *TmpCtx); FileName = PSL.getFileName(); IsFunctionPtr = false; TypeParams = FD->getNumTypeVars(); @@ -1155,24 +1161,19 @@ FunctionVariableConstraint::FunctionVariableConstraint( DidConvert = true; } else { if (!Ext->isOriginallyChecked()) { - Ext->constrainToWild(CS, VOID_TYPE_REASON); + Ext->constrainToWild(CS, ReasonLoc(VOID_TYPE_REASON, PSL)); } } } if (DidConvert) TypeParams = 1; } -void FunctionVariableConstraint::constrainToWild(Constraints &CS, - const std::string &Rsn) const { - constrainToWild(CS, Rsn, nullptr); -} - void FunctionVariableConstraint::constrainToWild( - Constraints &CS, const std::string &Rsn, PersistentSourceLoc *PL) const { - ReturnVar.ExternalConstraint->constrainToWild(CS, Rsn, PL); + Constraints &CS, const ReasonLoc &Rsn) const { + ReturnVar.ExternalConstraint->constrainToWild(CS, Rsn); for (const auto &V : ParamVars) - V.ExternalConstraint->constrainToWild(CS, Rsn, PL); + V.ExternalConstraint->constrainToWild(CS, Rsn); } bool FunctionVariableConstraint::anyChanges(const EnvironmentMap &E) const { return ReturnVar.ExternalConstraint->anyChanges(E) || @@ -1212,40 +1213,42 @@ bool FunctionVariableConstraint::hasNtArr(const EnvironmentMap &E, return ReturnVar.ExternalConstraint->hasNtArr(E, AIdx); } -FVConstraint *FunctionVariableConstraint::getCopy(Constraints &CS) { +FVConstraint *FunctionVariableConstraint::getCopy(ReasonLoc &Rsn, + Constraints &CS) { FunctionVariableConstraint *Copy = new FunctionVariableConstraint(this); - Copy->ReturnVar = FVComponentVariable(&Copy->ReturnVar, CS); + Copy->ReturnVar = FVComponentVariable(&Copy->ReturnVar, Rsn, CS); // Make copy of ParameterCVs too. std::vector FreshParams; for (auto &ParmPv : Copy->ParamVars) - FreshParams.push_back(FVComponentVariable(&ParmPv, CS)); + FreshParams.push_back(FVComponentVariable(&ParmPv, Rsn, CS)); Copy->ParamVars = FreshParams; return Copy; } -void PVConstraint::equateArgumentConstraints(ProgramInfo &Info) { +void PVConstraint::equateArgumentConstraints(ProgramInfo &Info, ReasonLoc &Rsn) { if (HasEqArgumentConstraints) { return; } HasEqArgumentConstraints = true; constrainConsVarGeq(this, this->ArgumentConstraints, Info.getConstraints(), - nullptr, Same_to_Same, true, &Info); + Rsn, Same_to_Same, true, &Info); if (this->FV != nullptr) { - this->FV->equateArgumentConstraints(Info); + this->FV->equateArgumentConstraints(Info, Rsn); } } void FunctionVariableConstraint::equateFVConstraintVars( - ConstraintVariable *CV, ProgramInfo &Info) const { + ConstraintVariable *CV, ProgramInfo &Info, ReasonLoc &Rsn) const { if (FVConstraint *FVCons = dyn_cast(CV)) { for (auto &PCon : FVCons->ParamVars) - PCon.InternalConstraint->equateArgumentConstraints(Info); - FVCons->ReturnVar.InternalConstraint->equateArgumentConstraints(Info); + PCon.InternalConstraint->equateArgumentConstraints(Info, Rsn); + FVCons->ReturnVar.InternalConstraint->equateArgumentConstraints(Info, Rsn); } } -void FunctionVariableConstraint::equateArgumentConstraints(ProgramInfo &Info) { +void FunctionVariableConstraint::equateArgumentConstraints(ProgramInfo &Info, + ReasonLoc &Rsn) { if (HasEqArgumentConstraints) { return; } @@ -1253,7 +1256,7 @@ void FunctionVariableConstraint::equateArgumentConstraints(ProgramInfo &Info) { HasEqArgumentConstraints = true; // Equate arguments and parameters vars. - this->equateFVConstraintVars(this, Info); + this->equateFVConstraintVars(this, Info, Rsn); // Is this not a function pointer? if (!IsFunctionPtr) { @@ -1269,18 +1272,12 @@ void FunctionVariableConstraint::equateArgumentConstraints(ProgramInfo &Info) { assert(DefnCons != nullptr); // Equate arguments and parameters vars. - this->equateFVConstraintVars(DefnCons, Info); + this->equateFVConstraintVars(DefnCons, Info, Rsn); } } void PointerVariableConstraint::constrainToWild(Constraints &CS, - const std::string &Rsn) const { - constrainToWild(CS, Rsn, nullptr); -} - -void PointerVariableConstraint::constrainToWild(Constraints &CS, - const std::string &Rsn, - PersistentSourceLoc *PL) const { + const ReasonLoc &Rsn) const { // Find the first VarAtom. All atoms before this are ConstAtoms, so // constraining them isn't useful; VarAtom *FirstVA = nullptr; @@ -1294,24 +1291,25 @@ void PointerVariableConstraint::constrainToWild(Constraints &CS, // implicitly constrained to WILD because of GEQ constraints that exist // between levels of a pointer. if (FirstVA) - CS.addConstraint(CS.createGeq(FirstVA, CS.getWild(), Rsn, PL, true)); + CS.addConstraint(CS.createGeq(FirstVA, CS.getWild(), Rsn, true)); if (FV) - FV->constrainToWild(CS, Rsn, PL); + FV->constrainToWild(CS, Rsn); } void PointerVariableConstraint::constrainIdxTo(Constraints &CS, ConstAtom *C, - unsigned int Idx, bool DoLB, - bool Soft) { + unsigned int Idx, + const ReasonLoc &Rsn, + bool DoLB, bool Soft) { assert(C == CS.getPtr() || C == CS.getArr() || C == CS.getNTArr()); if (Vars.size() > Idx) { Atom *A = Vars[Idx]; if (VarAtom *VA = dyn_cast(A)) { if (DoLB) - CS.addConstraint(CS.createGeq(VA, C, false, Soft)); + CS.addConstraint(CS.createGeq(VA, C, Rsn, false, Soft)); else - CS.addConstraint(CS.createGeq(C, VA, false, Soft)); + CS.addConstraint(CS.createGeq(C, VA, Rsn, false, Soft)); } else if (ConstAtom *CA = dyn_cast(A)) { if (DoLB) { if (*CA < *C) { @@ -1329,8 +1327,9 @@ void PointerVariableConstraint::constrainIdxTo(Constraints &CS, ConstAtom *C, } void PointerVariableConstraint::constrainOuterTo(Constraints &CS, ConstAtom *C, + const ReasonLoc &Rsn, bool DoLB, bool Soft) { - constrainIdxTo(CS, C, 0, DoLB, Soft); + constrainIdxTo(CS, C, 0, Rsn, DoLB, Soft); } bool PointerVariableConstraint::anyArgumentIsWild(const EnvironmentMap &E) { @@ -1376,7 +1375,8 @@ bool PointerVariableConstraint::anyChanges(const EnvironmentMap &E) const { return PtrChanged; } -PVConstraint *PointerVariableConstraint::getCopy(Constraints &CS) { +PVConstraint *PointerVariableConstraint::getCopy(ReasonLoc &Rsn, + Constraints &CS) { auto *Copy = new PointerVariableConstraint(this); // After the copy construct, the copy Vars vector holds exactly the same @@ -1390,8 +1390,9 @@ PVConstraint *PointerVariableConstraint::getCopy(Constraints &CS) { } else if (auto *VA = dyn_cast(*VAIt)) { VarAtom *FreshVA = CS.getFreshVar(VA->getName(), VA->getVarKind()); FreshVars.push_back(FreshVA); - if (!isa(*CAIt)) - CS.addConstraint(CS.createGeq(*CAIt, FreshVA, false)); + if (!isa(*CAIt)){ + CS.addConstraint(CS.createGeq(*CAIt, FreshVA, Rsn, false)); + } } ++VAIt; ++CAIt; @@ -1399,7 +1400,7 @@ PVConstraint *PointerVariableConstraint::getCopy(Constraints &CS) { Copy->Vars = FreshVars; if (Copy->FV != nullptr) - Copy->FV = Copy->FV->getCopy(CS); + Copy->FV = Copy->FV->getCopy(Rsn, CS); return Copy; } @@ -1705,9 +1706,8 @@ FunctionVariableConstraint::mkString(Constraints &CS, // CA |- R <: L // Action depends on the kind of constraint (checked, ptyp), // which is inferred from the atom type -static void createAtomGeq(Constraints &CS, Atom *L, Atom *R, std::string &Rsn, - PersistentSourceLoc *PSL, ConsAction CAct, - bool DoEqType) { +static void createAtomGeq(Constraints &CS, Atom *L, Atom *R, + const ReasonLoc &Rsn, ConsAction CAct, bool DoEqType) { ConstAtom *CAL, *CAR; VarAtom *VAL, *VAR; ConstAtom *Wild = CS.getWild(); @@ -1746,35 +1746,35 @@ static void createAtomGeq(Constraints &CS, Atom *L, Atom *R, std::string &Rsn, switch (CAct) { case Same_to_Same: // Equality for checked. - CS.addConstraint(CS.createGeq(L, R, Rsn, PSL, true)); - CS.addConstraint(CS.createGeq(R, L, Rsn, PSL, true)); + CS.addConstraint(CS.createGeq(L, R, Rsn, true)); + CS.addConstraint(CS.createGeq(R, L, Rsn, true)); // Not for ptyp. - CS.addConstraint(CS.createGeq(L, R, Rsn, PSL, false)); + CS.addConstraint(CS.createGeq(L, R, Rsn, false)); // Unless indicated. if (DoEqType) - CS.addConstraint(CS.createGeq(R, L, Rsn, PSL, false)); + CS.addConstraint(CS.createGeq(R, L, Rsn, false)); break; case Safe_to_Wild: - CS.addConstraint(CS.createGeq(L, R, Rsn, PSL, true)); - CS.addConstraint(CS.createGeq(L, R, Rsn, PSL, false)); + CS.addConstraint(CS.createGeq(L, R, Rsn, true)); + CS.addConstraint(CS.createGeq(L, R, Rsn, false)); if (DoEqType) { - CS.addConstraint(CS.createGeq(R, L, Rsn, PSL, true)); - CS.addConstraint(CS.createGeq(R, L, Rsn, PSL, false)); + CS.addConstraint(CS.createGeq(R, L, Rsn, true)); + CS.addConstraint(CS.createGeq(R, L, Rsn, false)); } break; case Wild_to_Safe: if (!DisableRDs) { // Note: reversal. - CS.addConstraint(CS.createGeq(R, L, Rsn, PSL, true)); + CS.addConstraint(CS.createGeq(R, L, Rsn, true)); } else { // Add edges both ways. - CS.addConstraint(CS.createGeq(L, R, Rsn, PSL, true)); - CS.addConstraint(CS.createGeq(R, L, Rsn, PSL, true)); + CS.addConstraint(CS.createGeq(L, R, Rsn, true)); + CS.addConstraint(CS.createGeq(R, L, Rsn, true)); } - CS.addConstraint(CS.createGeq(L, R, Rsn, PSL, false)); + CS.addConstraint(CS.createGeq(L, R, Rsn, false)); if (DoEqType) { - CS.addConstraint(CS.createGeq(L, R, Rsn, PSL, true)); - CS.addConstraint(CS.createGeq(R, L, Rsn, PSL, false)); + CS.addConstraint(CS.createGeq(L, R, Rsn, true)); + CS.addConstraint(CS.createGeq(R, L, Rsn, false)); } break; } @@ -1783,23 +1783,23 @@ static void createAtomGeq(Constraints &CS, Atom *L, Atom *R, std::string &Rsn, if (CAL == Wild || CAR == Wild) { switch (CAct) { case Same_to_Same: - CS.addConstraint(CS.createGeq(L, R, Rsn, PSL, true)); - CS.addConstraint(CS.createGeq(R, L, Rsn, PSL, true)); + CS.addConstraint(CS.createGeq(L, R, Rsn, true)); + CS.addConstraint(CS.createGeq(R, L, Rsn, true)); break; case Safe_to_Wild: - CS.addConstraint(CS.createGeq(L, R, Rsn, PSL, true)); + CS.addConstraint(CS.createGeq(L, R, Rsn, true)); if (DoEqType) - CS.addConstraint(CS.createGeq(R, L, Rsn, PSL, true)); + CS.addConstraint(CS.createGeq(R, L, Rsn, true)); break; case Wild_to_Safe: if (!DisableRDs) { // Note: reversal. - CS.addConstraint(CS.createGeq(R, L, Rsn, PSL, true)); + CS.addConstraint(CS.createGeq(R, L, Rsn, true)); } else { - CS.addConstraint(CS.createGeq(L, R, Rsn, PSL, true)); + CS.addConstraint(CS.createGeq(L, R, Rsn, true)); } if (DoEqType) - CS.addConstraint(CS.createGeq(L, R, Rsn, PSL, true)); + CS.addConstraint(CS.createGeq(L, R, Rsn, true)); break; } } else { @@ -1808,9 +1808,9 @@ static void createAtomGeq(Constraints &CS, Atom *L, Atom *R, std::string &Rsn, case Same_to_Same: case Safe_to_Wild: case Wild_to_Safe: - CS.addConstraint(CS.createGeq(L, R, Rsn, PSL, false)); + CS.addConstraint(CS.createGeq(L, R, Rsn, false)); if (DoEqType) - CS.addConstraint(CS.createGeq(R, L, Rsn, PSL, false)); + CS.addConstraint(CS.createGeq(R, L, Rsn, false)); break; } } @@ -1820,7 +1820,7 @@ static void createAtomGeq(Constraints &CS, Atom *L, Atom *R, std::string &Rsn, // Generate constraints according to CA |- RHS <: LHS. // If doEqType is true, then also do CA |- LHS <: RHS. void constrainConsVarGeq(ConstraintVariable *LHS, ConstraintVariable *RHS, - Constraints &CS, PersistentSourceLoc *PL, + Constraints &CS, const ReasonLoc &Rsn, ConsAction CA, bool DoEqType, ProgramInfo *Info, bool HandleBoundsKey) { @@ -1828,12 +1828,13 @@ void constrainConsVarGeq(ConstraintVariable *LHS, ConstraintVariable *RHS, // This can happen when a non-function pointer gets assigned to // a function pointer. if (LHS == nullptr || RHS == nullptr) { - std::string Rsn = "Assignment a non-pointer to a pointer"; + auto Reason = ReasonLoc("Assignment a non-pointer to a pointer", + Rsn.Location); if (LHS != nullptr) { - LHS->constrainToWild(CS, Rsn, PL); + LHS->constrainToWild(CS, Reason); } if (RHS != nullptr) { - RHS->constrainToWild(CS, Rsn, PL); + RHS->constrainToWild(CS, Reason); } return; } @@ -1845,16 +1846,17 @@ void constrainConsVarGeq(ConstraintVariable *LHS, ConstraintVariable *RHS, // This is an assignment between function pointer and // function pointer or a function. // Force past/future callers of function to use equality constraints. - FCLHS->equateArgumentConstraints(*Info); - FCRHS->equateArgumentConstraints(*Info); + auto Reason = ReasonLoc("Assignment of function pointer", Rsn.Location); + FCLHS->equateArgumentConstraints(*Info, Reason); + FCRHS->equateArgumentConstraints(*Info, Reason); // Constrain the return values covariantly. // FIXME: Make neg(CA) here? Function pointers equated. constrainConsVarGeq(FCLHS->getExternalReturn(), - FCRHS->getExternalReturn(), CS, PL, Same_to_Same, + FCRHS->getExternalReturn(), CS, Reason, Same_to_Same, DoEqType, Info, HandleBoundsKey); constrainConsVarGeq(FCLHS->getInternalReturn(), - FCRHS->getInternalReturn(), CS, PL, Same_to_Same, + FCRHS->getInternalReturn(), CS, Reason, Same_to_Same, DoEqType, Info, HandleBoundsKey); // Constrain the parameters contravariantly. @@ -1863,20 +1865,21 @@ void constrainConsVarGeq(ConstraintVariable *LHS, ConstraintVariable *RHS, ConstraintVariable *LHSV = FCLHS->getExternalParam(I); ConstraintVariable *RHSV = FCRHS->getExternalParam(I); // FIXME: Make neg(CA) here? Now: Function pointers equated. - constrainConsVarGeq(RHSV, LHSV, CS, PL, Same_to_Same, DoEqType, + constrainConsVarGeq(RHSV, LHSV, CS, Reason, Same_to_Same, DoEqType, Info, HandleBoundsKey); ConstraintVariable *LParam = FCLHS->getInternalParam(I); ConstraintVariable *RParam = FCRHS->getInternalParam(I); - constrainConsVarGeq(RParam, LParam, CS, PL, Same_to_Same, DoEqType, + constrainConsVarGeq(RParam, LParam, CS, Reason, Same_to_Same, DoEqType, Info, HandleBoundsKey); } } else { // Constrain both to be top. - std::string Rsn = - "Assigning from:" + FCRHS->getName() + " to " + FCLHS->getName(); - RHS->constrainToWild(CS, Rsn, PL); - LHS->constrainToWild(CS, Rsn, PL); + auto WildReason = ReasonLoc( + "Assigning from " + FCRHS->getName() + " to " + FCLHS->getName(), + Rsn.Location); + RHS->constrainToWild(CS, WildReason); + LHS->constrainToWild(CS, WildReason); } } else { llvm_unreachable("impossible"); @@ -1890,11 +1893,13 @@ void constrainConsVarGeq(ConstraintVariable *LHS, ConstraintVariable *RHS, PCRHS->getBoundsKey()); } - std::string Rsn = ""; + auto Reason = ReasonLoc( + "Assigning from " + PCRHS->getName() + " to " + PCLHS->getName(), + Rsn.Location); // This is to handle function subtyping. Try to add LHS and RHS // to each others argument constraints. - PCLHS->addArgumentConstraint(PCRHS, *Info); - PCRHS->addArgumentConstraint(PCLHS, *Info); + PCLHS->addArgumentConstraint(PCRHS, Reason, *Info); + PCRHS->addArgumentConstraint(PCLHS, Reason, *Info); // Element-wise constrain PCLHS and PCRHS to be equal. CAtoms CLHS = PCLHS->getCvars(); CAtoms CRHS = PCRHS->getCvars(); @@ -1913,24 +1918,26 @@ void constrainConsVarGeq(ConstraintVariable *LHS, ConstraintVariable *RHS, // Get outermost pointer first, using current ConsAction. if (N == 0) - createAtomGeq(CS, IAtom, JAtom, Rsn, PL, CA, DoEqType); + createAtomGeq(CS, IAtom, JAtom, Reason, CA, DoEqType); else { // Now constrain the inner ones as equal. - createAtomGeq(CS, IAtom, JAtom, Rsn, PL, CA, true); + createAtomGeq(CS, IAtom, JAtom, Reason, CA, true); } } // Unequal sizes means casting from (say) T** to T*; not safe. // unless assigning to a generic type. } else { // Constrain both to be top. - std::string Rsn = "Assigning from:" + std::to_string(CRHS.size()) + + auto WildReason = ReasonLoc( + "Assigning from " + std::to_string(CRHS.size()) + " depth pointer to " + - std::to_string(CLHS.size()) + " depth pointer."; - PCLHS->constrainToWild(CS, Rsn, PL); - PCRHS->constrainToWild(CS, Rsn, PL); + std::to_string(CLHS.size()) + " depth pointer.", + Rsn.Location); + PCLHS->constrainToWild(CS, WildReason); + PCRHS->constrainToWild(CS, WildReason); } // Equate the corresponding FunctionConstraint. - constrainConsVarGeq(PCLHS->getFV(), PCRHS->getFV(), CS, PL, CA, + constrainConsVarGeq(PCLHS->getFV(), PCRHS->getFV(), CS, Reason, CA, DoEqType, Info, HandleBoundsKey); } } else @@ -1939,42 +1946,48 @@ void constrainConsVarGeq(ConstraintVariable *LHS, ConstraintVariable *RHS, llvm_unreachable("unknown kind"); } else { // Assigning from a function variable to a pointer variable? + auto Reason = ReasonLoc( + "Assigning from a function variable to a pointer variable", + Rsn.Location); PVConstraint *PCLHS = dyn_cast(LHS); FVConstraint *FCRHS = dyn_cast(RHS); if (PCLHS && FCRHS) { if (FVConstraint *FCLHS = PCLHS->getFV()) { - constrainConsVarGeq(FCLHS, FCRHS, CS, PL, CA, DoEqType, Info, + constrainConsVarGeq(FCLHS, FCRHS, CS, Reason, CA, DoEqType, Info, HandleBoundsKey); } else { - std::string Rsn = "Function:" + FCRHS->getName() + - " assigned to non-function pointer."; - LHS->constrainToWild(CS, Rsn, PL); - RHS->constrainToWild(CS, Rsn, PL); + auto WildReason = ReasonLoc("Function:" + FCRHS->getName() + + " assigned to non-function pointer.", + Rsn.Location); + LHS->constrainToWild(CS, WildReason); + RHS->constrainToWild(CS, WildReason); } } else { // Constrain everything in both to wild. - std::string Rsn = "Assignment to functions from variables"; - LHS->constrainToWild(CS, Rsn, PL); - RHS->constrainToWild(CS, Rsn, PL); + auto WildReason = ReasonLoc( + "Assignment to functions from variables", + Rsn.Location); + LHS->constrainToWild(CS, WildReason); + RHS->constrainToWild(CS, WildReason); } } } void constrainConsVarGeq(ConstraintVariable *LHS, const CVarSet &RHS, - Constraints &CS, PersistentSourceLoc *PL, + Constraints &CS, const ReasonLoc &Rsn, ConsAction CA, bool DoEqType, ProgramInfo *Info, bool HandleBoundsKey) { for (const auto &J : RHS) - constrainConsVarGeq(LHS, J, CS, PL, CA, DoEqType, Info, HandleBoundsKey); + constrainConsVarGeq(LHS, J, CS, Rsn, CA, DoEqType, Info, HandleBoundsKey); } // Given an RHS and a LHS, constrain them to be equal. void constrainConsVarGeq(const CVarSet &LHS, const CVarSet &RHS, - Constraints &CS, PersistentSourceLoc *PL, + Constraints &CS, const ReasonLoc &Rsn, ConsAction CA, bool DoEqType, ProgramInfo *Info, bool HandleBoundsKey) { for (const auto &I : LHS) - constrainConsVarGeq(I, RHS, CS, PL, CA, DoEqType, Info, HandleBoundsKey); + constrainConsVarGeq(I, RHS, CS, Rsn, CA, DoEqType, Info, HandleBoundsKey); } // True if [C] is a PVConstraint that contains at least one Atom (i.e., @@ -2056,22 +2069,18 @@ Atom *PointerVariableConstraint::getAtom(unsigned AtomIdx, Constraints &CS) { } void PointerVariableConstraint::equateWithItype( - ProgramInfo &I, const std::string &ReasonUnchangeable, - PersistentSourceLoc *PSL) { + ProgramInfo &I, const ReasonLoc &ReasonUnchangeable) { Constraints &CS = I.getConstraints(); assert(SrcVars.size() == Vars.size()); for (unsigned VarIdx = 0; VarIdx < Vars.size(); VarIdx++) { ConstAtom *CA = SrcVars[VarIdx]; if (isa(CA)) - CS.addConstraint(CS.createGeq( - Vars[VarIdx], CA, - ReasonUnchangeable.empty() ? DEFAULT_REASON : ReasonUnchangeable, PSL, - true)); + CS.addConstraint(CS.createGeq(Vars[VarIdx], CA,ReasonUnchangeable,true)); else Vars[VarIdx] = SrcVars[VarIdx]; } if (FV) { - FV->equateWithItype(I, ReasonUnchangeable, PSL); + FV->equateWithItype(I, ReasonUnchangeable); } } @@ -2113,11 +2122,10 @@ bool FunctionVariableConstraint::isOriginallyChecked() const { } void FunctionVariableConstraint::equateWithItype( - ProgramInfo &I, const std::string &ReasonUnchangeable, - PersistentSourceLoc *PSL) { - ReturnVar.equateWithItype(I, ReasonUnchangeable, PSL); + ProgramInfo &I, const ReasonLoc &ReasonUnchangeable) { + ReturnVar.equateWithItype(I, ReasonUnchangeable); for (auto Param : ParamVars) - Param.equateWithItype(I, ReasonUnchangeable, PSL); + Param.equateWithItype(I, ReasonUnchangeable); } void FVComponentVariable::mergeDeclaration(FVComponentVariable *From, @@ -2277,18 +2285,19 @@ FVComponentVariable::FVComponentVariable(const QualType &QT, } void FVComponentVariable::equateWithItype(ProgramInfo &I, - const std::string &ReasonUnchangeable, - PersistentSourceLoc *PSL) const { + const ReasonLoc &ReasonUnchangeable) const { Constraints &CS = I.getConstraints(); - const std::string ReasonUnchangeable2 = - (ReasonUnchangeable.empty() && ExternalConstraint->isGeneric()) - ? "Internal constraint for generic function declaration, " - "for which 3C currently does not support re-solving." + const ReasonLoc &ReasonUnchangeable2 = + (ReasonUnchangeable.isDefault() && ExternalConstraint->isGeneric()) + ? ReasonLoc("Internal constraint for generic function declaration, " + "for which 3C currently does not support re-solving.", + // TODO: What PSL should we actually use here? + ReasonUnchangeable.Location) : ReasonUnchangeable; bool HasItype = ExternalConstraint->srcHasItype(); // If the type cannot change at all (ReasonUnchangeable2 is set), then we // constrain both the external and internal types to not change. - bool MustConstrainInternalType = !ReasonUnchangeable2.empty(); + bool MustConstrainInternalType = !ReasonUnchangeable2.isDefault(); // Otherwise, if a pointer is an array pointer with declared bounds or is a // constant size array, then we want to ensure the external type continues to // solve to ARR or NTARR; see the comment on @@ -2299,25 +2308,26 @@ void FVComponentVariable::equateWithItype(ProgramInfo &I, bool MustBeArray = ExternalConstraint->srcHasBounds() || ExternalConstraint->hasSomeSizedArr(); if (HasItype && (MustConstrainInternalType || MustBeArray)) { - ExternalConstraint->equateWithItype(I, ReasonUnchangeable2, PSL); + ExternalConstraint->equateWithItype(I, ReasonUnchangeable2); if (ExternalConstraint != InternalConstraint) linkInternalExternal(I, false); if (MustConstrainInternalType) - InternalConstraint->constrainToWild(CS, ReasonUnchangeable2, PSL); + InternalConstraint->constrainToWild(CS, ReasonUnchangeable2); } } void FVComponentVariable::linkInternalExternal(ProgramInfo &I, bool EquateChecked) const { Constraints &CS = I.getConstraints(); + auto LinkReason = ReasonLoc("Function Internal/External Link", PersistentSourceLoc()); for (unsigned J = 0; J < InternalConstraint->getCvars().size(); J++) { Atom *InternalA = InternalConstraint->getCvars()[J]; Atom *ExternalA = ExternalConstraint->getCvars()[J]; if (isa(InternalA) || isa(ExternalA)) { // Equate pointer types for internal and external parameter constraint // variables. - CS.addConstraint(CS.createGeq(InternalA, ExternalA, false)); - CS.addConstraint(CS.createGeq(ExternalA, InternalA, false)); + CS.addConstraint(CS.createGeq(InternalA, ExternalA, LinkReason, false)); + CS.addConstraint(CS.createGeq(ExternalA, InternalA, LinkReason, false)); if (!isa(ExternalA)) { // Constrain Internal >= External. If external solves to wild, then so @@ -2325,7 +2335,8 @@ void FVComponentVariable::linkInternalExternal(ProgramInfo &I, // use causes the internal variable to be wild because the external // variable solves to WILD only when there is an unsafe use that // cannot be resolved by inserting casts. - CS.addConstraint(CS.createGeq(InternalA, ExternalA, true)); + CS.addConstraint(CS.createGeq(InternalA, ExternalA, + LinkReason, true)); // Atoms of return constraint variables are unified after the first // level. This is because CheckedC does not allow assignment from e.g. @@ -2333,14 +2344,15 @@ void FVComponentVariable::linkInternalExternal(ProgramInfo &I, // variable with type `int **`. if (DisableFunctionEdges || DisableRDs || EquateChecked || (ExternalConstraint->getName() == RETVAR && J > 0)) - CS.addConstraint(CS.createGeq(ExternalA, InternalA, true)); + CS.addConstraint(CS.createGeq(ExternalA, InternalA, + LinkReason, true)); } } } if (FVConstraint *ExtFV = ExternalConstraint->getFV()) { FVConstraint *IntFV = InternalConstraint->getFV(); assert(IntFV != nullptr); - constrainConsVarGeq(ExtFV, IntFV, CS, nullptr, Same_to_Same, true, &I); + constrainConsVarGeq(ExtFV, IntFV, CS, LinkReason, Same_to_Same, true, &I); } } @@ -2355,7 +2367,8 @@ bool FVComponentVariable::solutionEqualTo(Constraints &CS, } FVComponentVariable::FVComponentVariable(FVComponentVariable *Ot, + ReasonLoc &Rsn, Constraints &CS) { - InternalConstraint = Ot->InternalConstraint->getCopy(CS); - ExternalConstraint = Ot->ExternalConstraint->getCopy(CS); + InternalConstraint = Ot->InternalConstraint->getCopy(Rsn,CS); + ExternalConstraint = Ot->ExternalConstraint->getCopy(Rsn,CS); } diff --git a/clang/lib/3C/Constraints.cpp b/clang/lib/3C/Constraints.cpp index bf396b180be3..5e6268455777 100644 --- a/clang/lib/3C/Constraints.cpp +++ b/clang/lib/3C/Constraints.cpp @@ -33,15 +33,6 @@ static cl::opt cl::desc("Perform only least solution for Pty Constrains."), cl::init(false), cl::cat(SolverCategory)); -Constraint::Constraint(ConstraintKind K, const std::string &Rsn, - PersistentSourceLoc *PL) - : Constraint(K, Rsn) { - if (PL != nullptr && PL->valid()) - this->PL = *PL; - else - this->PL = PersistentSourceLoc(); -} - // Remove the constraint from the global constraint set. bool Constraints::removeConstraint(Constraint *C) { bool RetVal = false; @@ -77,13 +68,17 @@ void Constraints::editConstraintHook(Constraint *C) { if (RHSA) { if (!dyn_cast(E->getLHS())) { E->setChecked(getWild()); - E->setReason(POINTER_IS_ARRAY_REASON); + ReasonLoc Rsn = E->getReason(); + Rsn.Reason = POINTER_IS_ARRAY_REASON; + E->setReason(Rsn); } } else { assert(LHSA && "Adding constraint between constants?!"); if (!dyn_cast(E->getRHS())) { E->setChecked(getWild()); - E->setReason(POINTER_IS_ARRAY_REASON); + ReasonLoc Rsn = E->getReason(); + Rsn.Reason = POINTER_IS_ARRAY_REASON; + E->setReason(Rsn); } } } @@ -140,8 +135,9 @@ bool Constraints::addConstraint(Constraint *C) { bool Constraints::addReasonBasedConstraint(Constraint *C) { // Only insert if this is an Eq constraint and has a valid reason. if (Geq *E = dyn_cast(C)) { - if (E->getReason() != DEFAULT_REASON && !E->getReason().empty()) - return this->ConstraintsByReason[E->getReason()].insert(E).second; + if (E->getReasonText() != DEFAULT_REASON && !E->getReasonText().empty() && + E->getReason().Location.valid()) + return this->ConstraintsByReason[E->getReasonText()].insert(E).second; } return false; } @@ -149,9 +145,9 @@ bool Constraints::addReasonBasedConstraint(Constraint *C) { bool Constraints::removeReasonBasedConstraint(Constraint *C) { if (Geq *E = dyn_cast(C)) { // Remove if the constraint is present. - if (this->ConstraintsByReason.find(E->getReason()) != + if (this->ConstraintsByReason.find(E->getReasonText()) != this->ConstraintsByReason.end()) - return this->ConstraintsByReason[E->getReason()].erase(E) > 0; + return this->ConstraintsByReason[E->getReasonText()].erase(E) > 0; } return false; } @@ -178,7 +174,8 @@ bool Constraints::removeReasonBasedConstraint(Constraint *C) { static bool doSolve(ConstraintsGraph &CG, ConstraintsEnv &Env, Constraints *CS, bool DoLeastSolution, - std::set *InitVs, std::set &Conflicts) { + std::set *InitVs, + std::set &Conflicts) { std::vector WorkList; @@ -218,12 +215,12 @@ doSolve(ConstraintsGraph &CG, } // Check Upper/lower bounds hold; collect failures in conflicts set. - std::set Neighbors; + std::set IncidentEdges; bool Ok = true; for (ConstAtom *Cbound : CG.getAllConstAtoms()) { - if (CG.getNeighbors(Cbound, Neighbors, !DoLeastSolution)) { - for (Atom *A : Neighbors) { - VarAtom *VA = dyn_cast(A); + if (CG.getIncidentEdges(Cbound, IncidentEdges, !DoLeastSolution)) { + for (auto *E : IncidentEdges) { + VarAtom *VA = dyn_cast(E->getTargetNode().getData()); if (VA == nullptr) continue; ConstAtom *Csol = Env.getAssignment(VA); @@ -234,7 +231,7 @@ doSolve(ConstraintsGraph &CG, // wild after pointer type solving is finished. Checked types will // be resolved with this new constraint, transitively propagating the // new WILD-ness. - Conflicts.insert(VA); + Conflicts.insert(E); // Failure case. if (_3COpts.Verbose) { errs() << "Unsolvable constraints: "; @@ -323,7 +320,7 @@ static std::set findBounded(ConstraintsGraph &CG, } bool Constraints::graphBasedSolve() { - std::set Conflicts; + std::set Conflicts; ConstraintsGraph SolChkCG; ConstraintsGraph SolPtrTypCG; ConstraintsEnv &Env = Environment; @@ -438,13 +435,35 @@ bool Constraints::graphBasedSolve() { if (!Res) { std::set Rest; Env.doCheckedSolve(true); - for (VarAtom *VA : Conflicts) { - assert(VA != nullptr); - std::string Rsn = "Bad pointer type solution"; - Geq *ConflictConstraint = createGeq(VA, getWild(), Rsn); + for (auto *Conflict : Conflicts) { + Atom *ConflictAtom = Conflict->getTargetNode().getData(); + assert(ConflictAtom != nullptr); + ReasonLoc Rsn1 = Conflict->EdgeConstraint->getReason(); + // Determine a second from the constraints immediately incident to the + // conflicting atom. A future improvement should traverse the + // constraint graph to find the contradictory constraints to constant + // atoms. See correctcomputation/checkedc-clang#680. + auto Succs = Conflict->getTargetNode().getEdges(); + ReasonLoc Rsn2; + for (auto *Succ : Succs) { + if (auto *SuccGeq = dyn_cast(Succ->EdgeConstraint)) { + if (Env.getAssignment(ConflictAtom) == + Env.getAssignment(SuccGeq->getLHS()) || + Env.getAssignment(ConflictAtom) == + Env.getAssignment(SuccGeq->getRHS())) { + Rsn2 = Succ->EdgeConstraint->getReason(); + break; + } + } + } + auto Rsn = ReasonLoc("Inferred conflicting types", + PersistentSourceLoc()); + Geq *ConflictConstraint = createGeq(ConflictAtom, getWild(), Rsn); + ConflictConstraint->addReason(Rsn1); + ConflictConstraint->addReason(Rsn2); addConstraint(ConflictConstraint); SolChkCG.addConstraint(ConflictConstraint, *this); - Rest.insert(VA); + Rest.insert(cast(ConflictAtom)); } Conflicts.clear(); /* FIXME: Should we propagate the old res? */ @@ -537,10 +556,9 @@ VarAtom *Constraints::getVar(ConstraintKey V) const { // should generally be used instead of using constant atoms directly if the the // VarAtom will be used in the variables vector of a PVConstraint. VarAtom *Constraints::createFreshGEQ(std::string Name, VarAtom::VarKind VK, - ConstAtom *Con, std::string Rsn, - PersistentSourceLoc *PSL) { + ConstAtom *Con, ReasonLoc Rsn) { VarAtom *VA = getFreshVar(Name, VK); - addConstraint(createGeq(VA, Con, Rsn, PSL)); + addConstraint(createGeq(VA, Con, Rsn)); return VA; } @@ -565,26 +583,16 @@ const ConstraintsGraph &Constraints::getPtrTypCG() const { return *PtrTypCG; } -Geq *Constraints::createGeq(Atom *Lhs, Atom *Rhs, bool IsCheckedConstraint, - bool Soft) { - return new Geq(Lhs, Rhs, IsCheckedConstraint, Soft); -} - -Geq *Constraints::createGeq(Atom *Lhs, Atom *Rhs, const std::string &Rsn, - bool IsCheckedConstraint) { - return new Geq(Lhs, Rhs, Rsn, IsCheckedConstraint); -} - -Geq *Constraints::createGeq(Atom *Lhs, Atom *Rhs, const std::string &Rsn, - PersistentSourceLoc *PL, bool IsCheckedConstraint) { - if (PL != nullptr && PL->valid()) { +Geq *Constraints::createGeq(Atom *Lhs, Atom *Rhs, ReasonLoc Rsn, + bool IsCheckedConstraint, bool Soft) { + if (Rsn.Location.valid()) { // Make this invalid, if the source location is not absolute path // this is to avoid crashes in clangd. - if (!llvm::sys::path::is_absolute(PL->getFileName())) - PL = nullptr; + if (!llvm::sys::path::is_absolute(Rsn.Location.getFileName())) + Rsn.Location = PersistentSourceLoc(); } assert("Shouldn't be constraining WILD >= VAR" && Lhs != getWild()); - return new Geq(Lhs, Rhs, Rsn, PL, IsCheckedConstraint); + return new Geq(Lhs, Rhs, Rsn, IsCheckedConstraint, Soft); } void Constraints::resetEnvironment() { diff --git a/clang/lib/3C/ConstraintsGraph.cpp b/clang/lib/3C/ConstraintsGraph.cpp index 6908c47dc17c..ed73c1d49909 100644 --- a/clang/lib/3C/ConstraintsGraph.cpp +++ b/clang/lib/3C/ConstraintsGraph.cpp @@ -32,7 +32,7 @@ void ConstraintsGraph::addConstraint(Geq *C, const Constraints &CS) { if (auto *VA2 = clang::dyn_cast(A2)) assert(CS.getVar(VA2->getLoc()) == VA2); - addEdge(A2, A1, C->isSoft()); + addEdge(A2, A1, C->isSoft(), C); } std::string llvm::DOTGraphTraits::getNodeLabel( diff --git a/clang/lib/3C/PersistentSourceLoc.cpp b/clang/lib/3C/PersistentSourceLoc.cpp index 2fe317bda265..a37e52df92cd 100644 --- a/clang/lib/3C/PersistentSourceLoc.cpp +++ b/clang/lib/3C/PersistentSourceLoc.cpp @@ -19,20 +19,24 @@ using namespace llvm; // This currently the expansion location for the declarations source location. // If we want to add more complete source for macros in the future, I expect we // will need to the spelling location instead. -PersistentSourceLoc PersistentSourceLoc::mkPSL(const Decl *D, ASTContext &C) { +PersistentSourceLoc PersistentSourceLoc::mkPSL(const Decl *D, + const ASTContext &C) { + if (D == nullptr) return PersistentSourceLoc(); SourceLocation SL = C.getSourceManager().getExpansionLoc(D->getLocation()); return mkPSL(D->getSourceRange(), SL, C); } // Create a PersistentSourceLoc for a Stmt. PersistentSourceLoc PersistentSourceLoc::mkPSL(const Stmt *S, - ASTContext &Context) { + const ASTContext &Context) { + if (S == nullptr) return PersistentSourceLoc(); return mkPSL(S->getSourceRange(), S->getBeginLoc(), Context); } // Create a PersistentSourceLoc for an Expression. PersistentSourceLoc PersistentSourceLoc::mkPSL(const clang::Expr *E, - clang::ASTContext &Context) { + const clang::ASTContext &Context) { + if (E == nullptr) return PersistentSourceLoc(); return mkPSL(E->getSourceRange(), E->getBeginLoc(), Context); } @@ -40,8 +44,8 @@ PersistentSourceLoc PersistentSourceLoc::mkPSL(const clang::Expr *E, // line and column numbers for a SourceLocation. PersistentSourceLoc PersistentSourceLoc::mkPSL(clang::SourceRange SR, SourceLocation SL, - ASTContext &Context) { - SourceManager &SM = Context.getSourceManager(); + const ASTContext &Context) { + const SourceManager &SM = Context.getSourceManager(); PresumedLoc PL = SM.getPresumedLoc(SL); // If there is no PresumedLoc, create a nullary PersistentSourceLoc. diff --git a/clang/lib/3C/ProgramInfo.cpp b/clang/lib/3C/ProgramInfo.cpp index 6cf40e3d9384..3bad8a95a28c 100644 --- a/clang/lib/3C/ProgramInfo.cpp +++ b/clang/lib/3C/ProgramInfo.cpp @@ -381,6 +381,9 @@ bool ProgramInfo::link() { if (_3COpts.Verbose) llvm::errs() << "Linking!\n"; + auto Rsn = ReasonLoc("Linking global variables", + PersistentSourceLoc()); + // Equate the constraints for all global variables. // This is needed for variables that are defined as extern. for (const auto &V : GlobalVariableSymbols) { @@ -393,7 +396,7 @@ bool ProgramInfo::link() { if (_3COpts.Verbose) llvm::errs() << "Global variables:" << V.first << "\n"; while (J != C.end()) { - constrainConsVarGeq(*I, *J, CS, nullptr, Same_to_Same, true, this); + constrainConsVarGeq(*I, *J, CS, Rsn, Same_to_Same, true, this); ++I; ++J; } @@ -405,12 +408,13 @@ bool ProgramInfo::link() { // constrain everything about it if (!V.second) { std::string VarName = V.first; - std::string Rsn = - "External global variable " + VarName + " has no definition"; + auto WildReason = ReasonLoc( + "External global variable " + VarName + " has no definition", + Rsn.Location); const std::set &C = GlobalVariableSymbols[VarName]; for (const auto &Var : C) { // TODO: Is there an easy way to get a PSL to attach to the constraint? - Var->constrainToWild(CS, Rsn); + Var->constrainToWild(CS, WildReason); } } } @@ -436,8 +440,9 @@ bool ProgramInfo::link() { void ProgramInfo::linkFunction(FunctionVariableConstraint *FV) { // If there was a checked type on a variable in the input program, it // should stay that way. Otherwise, we shouldn't be adding a checked type - // to an undefined function. - std::string Rsn = (FV->hasBody() ? "" : "Unchecked pointer in parameter or " + // to an undefined function. DEFAULT_REASON is a sentinel for + // ConstraintVariable::equateWithItype; see the comment there. + std::string Rsn = (FV->hasBody() ? DEFAULT_REASON : "Unchecked pointer in parameter or " "return of undefined function " + FV->getName()); @@ -445,7 +450,8 @@ void ProgramInfo::linkFunction(FunctionVariableConstraint *FV) { // unchecked type. // TODO: Ditto re getting a PSL (in the case in which Rsn is non-empty and // it is actually used). - FV->equateWithItype(*this, Rsn, nullptr); + auto Reason = ReasonLoc(Rsn, PersistentSourceLoc()); + FV->equateWithItype(*this, Reason); // Used to apply constraints to parameters and returns for function without a // body. In the default configuration, the function is fully constrained so @@ -453,11 +459,11 @@ void ProgramInfo::linkFunction(FunctionVariableConstraint *FV) { // --infer-types-for-undefs, only internal variables are constrained, allowing // external variables to solve to checked types meaning the parameter will be // rewritten to an itype. - auto LinkComponent = [this, Rsn](const FVComponentVariable *FVC) { - FVC->getInternal()->constrainToWild(CS, Rsn); + auto LinkComponent = [this, Reason](const FVComponentVariable *FVC) { + FVC->getInternal()->constrainToWild(CS, Reason); if (!_3COpts.InferTypesForUndefs && !FVC->getExternal()->srcHasItype() && !FVC->getExternal()->isGeneric()) - FVC->getExternal()->constrainToWild(CS, Rsn); + FVC->getExternal()->constrainToWild(CS, Reason); }; if (!FV->hasBody()) { @@ -578,7 +584,7 @@ void ProgramInfo::addVariable(clang::DeclaratorDecl *D, // anyways. This happens if the name of the variable is a macro defined // differently is different parts of the program. std::string Rsn = "Duplicate source location. Possibly part of a macro."; - Variables[PLoc]->constrainToWild(CS, Rsn, &PLoc); + Variables[PLoc]->constrainToWild(CS, ReasonLoc(Rsn, PLoc)); } return; } @@ -601,7 +607,7 @@ void ProgramInfo::addVariable(clang::DeclaratorDecl *D, // function to the function map anyways. The function map indexes by // function name, so there's no collision. insertNewFVConstraint(FD, F, AstContext); - constrainWildIfMacro(F, FD->getLocation()); + constrainWildIfMacro(F, FD->getLocation(), ReasonLoc(MACRO_REASON, PLoc)); } else { // A function with the same name exists in the same source location. // This happens when a function is defined in a header file which is @@ -621,29 +627,34 @@ void ProgramInfo::addVariable(clang::DeclaratorDecl *D, auto RetTy = FD->getReturnType(); unifyIfTypedef(RetTy, *AstContext, F->getExternalReturn(), Wild_to_Safe); unifyIfTypedef(RetTy, *AstContext, F->getInternalReturn(), Safe_to_Wild); - ensureNtCorrect(RetTy, *AstContext, F->getExternalReturn()); - ensureNtCorrect(RetTy, *AstContext, F->getInternalReturn()); + auto PSL = PersistentSourceLoc::mkPSL(FD,*AstContext); + ensureNtCorrect(RetTy, PSL, F->getExternalReturn()); + ensureNtCorrect(RetTy, PSL, F->getInternalReturn()); // Add mappings from the parameters PLoc to the constraint variables for // the parameters. for (unsigned I = 0; I < FD->getNumParams(); I++) { ParmVarDecl *PVD = FD->getParamDecl(I); + auto ParamPSL = PersistentSourceLoc::mkPSL(PVD,*AstContext); QualType ParamTy = PVD->getType(); PVConstraint *PVInternal = F->getInternalParam(I); PVConstraint *PVExternal = F->getExternalParam(I); unifyIfTypedef(ParamTy, *AstContext, PVExternal, Wild_to_Safe); unifyIfTypedef(ParamTy, *AstContext, PVInternal, Safe_to_Wild); - ensureNtCorrect(ParamTy, *AstContext, PVInternal); - ensureNtCorrect(ParamTy, *AstContext, PVExternal); + ensureNtCorrect(ParamTy, ParamPSL, PVInternal); + ensureNtCorrect(ParamTy, ParamPSL, PVExternal); PVInternal->setValidDecl(); PersistentSourceLoc PSL = PersistentSourceLoc::mkPSL(PVD, *AstContext); // Constraint variable is stored on the parent function, so we need to // constrain to WILD even if we don't end up storing this in the map. - constrainWildIfMacro(PVExternal, PVD->getLocation()); + constrainWildIfMacro(PVExternal, PVD->getLocation(), + ReasonLoc(MACRO_REASON, PSL)); // If this is "main", constrain its argv parameter to a nested arr if (_3COpts.AllTypes && FuncName == "main" && FD->isGlobal() && I == 1) { - PVInternal->constrainOuterTo(CS, CS.getArr()); - PVInternal->constrainIdxTo(CS, CS.getNTArr(), 1); + PVInternal->constrainOuterTo(CS, CS.getArr(), + ReasonLoc(SPECIAL_REASON("main"), PSL)); + PVInternal->constrainIdxTo(CS, CS.getNTArr(), 1, + ReasonLoc(SPECIAL_REASON("main"), PSL)); } // It is possible to have a param decl in a macro when the function is // not. @@ -661,7 +672,8 @@ void ProgramInfo::addVariable(clang::DeclaratorDecl *D, NewCV = P; std::string VarName(VD->getName()); unifyIfTypedef(QT, *AstContext, P); - ensureNtCorrect(VD->getType(), *AstContext, P); + auto PSL = PersistentSourceLoc::mkPSL(VD, *AstContext); + ensureNtCorrect(VD->getType(), PSL, P); if (VD->hasGlobalStorage()) { // If we see a definition for this global variable, indicate so in // ExternGVars. @@ -690,17 +702,20 @@ void ProgramInfo::addVariable(clang::DeclaratorDecl *D, assert("We shouldn't be adding a null CV to Variables map." && NewCV); if (!canWrite(PLoc.getFileName())) { - NewCV->equateWithItype(*this, UNWRITABLE_REASON, &PLoc); - NewCV->constrainToWild(CS, UNWRITABLE_REASON, &PLoc); + auto Rsn = ReasonLoc(UNWRITABLE_REASON, PLoc); + NewCV->equateWithItype(*this, Rsn); + NewCV->constrainToWild(CS, Rsn); } - constrainWildIfMacro(NewCV, D->getLocation()); + constrainWildIfMacro(NewCV, D->getLocation(), ReasonLoc(MACRO_REASON, PLoc)); Variables[PLoc] = NewCV; } -void ProgramInfo::ensureNtCorrect(const QualType &QT, const ASTContext &C, +void ProgramInfo::ensureNtCorrect(const QualType &QT, + const PersistentSourceLoc &PSL, PointerVariableConstraint *PV) { if (_3COpts.AllTypes && !canBeNtArray(QT)) { - PV->constrainOuterTo(CS, CS.getArr(), true, true); + PV->constrainOuterTo(CS, CS.getArr(), + ReasonLoc(ARRAY_REASON, PSL), true, true); } } @@ -710,10 +725,11 @@ void ProgramInfo::unifyIfTypedef(const QualType &QT, ASTContext &Context, auto *TDecl = TDT->getDecl(); auto PSL = PersistentSourceLoc::mkPSL(TDecl, Context); auto O = lookupTypedef(PSL); + auto Rsn = ReasonLoc("typedef", PSL); if (O.hasValue()) { auto *Bounds = &O.getValue(); P->setTypedef(Bounds, TDecl->getNameAsString()); - constrainConsVarGeq(P, Bounds, CS, &PSL, CA, false, this); + constrainConsVarGeq(P, Bounds, CS, Rsn, CA, false, this); } } } @@ -762,7 +778,7 @@ void ProgramInfo::storePersistentConstraints(Expr *E, const CSetBkeyPair &Vars, auto PSL = PersistentSourceLoc::mkPSL(E, *C); if (PSL.valid() && !canWrite(PSL.getFileName())) for (ConstraintVariable *CVar : Vars.first) - CVar->constrainToWild(CS, UNWRITABLE_REASON, &PSL); + CVar->constrainToWild(CS, ReasonLoc(UNWRITABLE_REASON, PSL)); IDAndTranslationUnit Key = getExprKey(E, C); ExprConstraintVars[Key] = Vars; @@ -794,10 +810,9 @@ void ProgramInfo::removePersistentConstraints(Expr *E, ASTContext *C) { // in macros. void ProgramInfo::constrainWildIfMacro(ConstraintVariable *CV, SourceLocation Location, - PersistentSourceLoc *PSL) { - std::string Rsn = "Pointer in Macro declaration."; + const ReasonLoc &Rsn) { if (!Rewriter::isRewritable(Location)) - CV->constrainToWild(CS, Rsn, PSL); + CV->constrainToWild(CS, Rsn); } //std::string ProgramInfo::getUniqueDeclKey(Decl *D, ASTContext *C) { @@ -1011,7 +1026,7 @@ bool ProgramInfo::computeInterimConstraintState( CState.AtomSourceMap.insert(std::make_pair(E.first, E.second)); auto &WildPtrsReason = CState.RootWildAtomsWithReason; - for (auto *CurrC : CS.getConstraints()) { + for (Constraint *CurrC : CS.getConstraints()) { if (Geq *EC = dyn_cast(CurrC)) { VarAtom *VLhs = dyn_cast(EC->getLHS()); if (EC->constraintIsChecked() && dyn_cast(EC->getRHS())) { @@ -1019,7 +1034,14 @@ bool ProgramInfo::computeInterimConstraintState( PersistentSourceLoc APSL = CState.AtomSourceMap[VLhs->getLoc()]; if (!PSL.valid() && APSL.valid()) PSL = APSL; - WildPointerInferenceInfo Info(EC->getReason(), PSL); + auto Rsn = ReasonLoc(EC->getReasonText(), PSL); + RootCauseDiagnostic Info(Rsn); + for (const auto &Reason : CurrC->additionalReasons()) { + PersistentSourceLoc P = Reason.Location; + if (!P.valid() && APSL.valid()) + P = APSL; + Info.addReason(ReasonLoc(Reason.Reason, P)); + } WildPtrsReason.insert(std::make_pair(VLhs->getLoc(), Info)); } } @@ -1144,12 +1166,14 @@ void ProgramInfo::addTypedef(PersistentSourceLoc PSL, bool CanRewriteDef, V = new PointerVariableConstraint(TD, *this, C); if (!CanRewriteDef) - V->constrainToWild(this->getConstraints(), "Unable to rewrite a typedef with multiple names", &PSL); + V->constrainToWild(this->getConstraints(), + ReasonLoc("Unable to rewrite a typedef with multiple names", PSL)); if (!canWrite(PSL.getFileName())) - V->constrainToWild(this->getConstraints(), UNWRITABLE_REASON, &PSL); + V->constrainToWild(this->getConstraints(), + ReasonLoc(UNWRITABLE_REASON, PSL)); - constrainWildIfMacro(V, TD->getLocation(), &PSL); + constrainWildIfMacro(V, TD->getLocation(), ReasonLoc(MACRO_REASON, PSL)); this->TypedefVars[PSL] = {*V}; } diff --git a/clang/lib/3C/RewriteUtils.cpp b/clang/lib/3C/RewriteUtils.cpp index 31bbc31e0422..d9f69597a356 100644 --- a/clang/lib/3C/RewriteUtils.cpp +++ b/clang/lib/3C/RewriteUtils.cpp @@ -578,8 +578,7 @@ void RewriteConsumer::emitRootCauseDiagnostics(ASTContext &Context) { SourceManager &SM = Context.getSourceManager(); for (auto &WReason : I.RootWildAtomsWithReason) { // Avoid emitting the same diagnostic message twice. - WildPointerInferenceInfo PtrInfo = WReason.second; - PersistentSourceLoc PSL = PtrInfo.getLocation(); + const PersistentSourceLoc& PSL = WReason.second.getLocation(); if (PSL.valid() && EmittedDiagnostics.find(PSL) == EmittedDiagnostics.end()) { @@ -598,7 +597,25 @@ void RewriteConsumer::emitRootCauseDiagnostics(ASTContext &Context) { // SL is invalid when the File is not in the current translation unit. if (SL.isValid()) { EmittedDiagnostics.insert(PSL); - DE.Report(SL, ID) << PtrCount << WReason.second.getWildPtrReason(); + DE.Report(SL, ID) << PtrCount << WReason.second.getReason(); + } + // if notes have sources in other files, these files may not + // be in the same TU and will not be displayed. At the initial + // time of this comment, that was expected to be very rare. + // see https://github.com/correctcomputation/checkedc-clang/pull/708#discussion_r716903129 + // for a discussion + for (auto &Note : WReason.second.additionalNotes()) { + PersistentSourceLoc NPSL = Note.Location; + llvm::ErrorOr NFile = + SM.getFileManager().getFile(NPSL.getFileName()); + if (!NFile.getError()) { + SourceLocation NSL = SM.translateFileLineCol( + *NFile, NPSL.getLineNo(), NPSL.getColSNo()); + if (NSL.isValid()) { + unsigned NID = DE.getCustomDiagID(DiagnosticsEngine::Note, "%0"); + DE.Report(NSL, NID) << Note.Reason; + } + } } } } diff --git a/clang/lib/3C/TypeVariableAnalysis.cpp b/clang/lib/3C/TypeVariableAnalysis.cpp index 3145b149e20c..365dca6eecb4 100644 --- a/clang/lib/3C/TypeVariableAnalysis.cpp +++ b/clang/lib/3C/TypeVariableAnalysis.cpp @@ -160,14 +160,16 @@ bool TypeVarVisitor::VisitCallExpr(CallExpr *CE) { // elsewhere, especially `ConstraintResolver::getExprConstraintVars` // using variable `ReallocFlow`. Because `realloc` can take a wild // pointer and return a safe one. + auto PSL = PersistentSourceLoc::mkPSL(CE,*Context); + auto Rsn = ReasonLoc("Type variable", PSL); if (FD->getNameAsString() == "realloc") { constrainConsVarGeq(P, TVEntry.second.getConstraintVariables(), - Info.getConstraints(), nullptr, Wild_to_Safe, false, + Info.getConstraints(), Rsn, Wild_to_Safe, false, &Info); } else { constrainConsVarGeq(P, TVEntry.second.getConstraintVariables(), - Info.getConstraints(), nullptr, Safe_to_Wild, false, + Info.getConstraints(), Rsn, Safe_to_Wild, false, &Info); } diff --git a/clang/test/3C/canwrite_constraints.h b/clang/test/3C/canwrite_constraints.h index 56f18a50fa1a..0d5758a41f61 100644 --- a/clang/test/3C/canwrite_constraints.h +++ b/clang/test/3C/canwrite_constraints.h @@ -48,10 +48,10 @@ typedef int *intptr; // expected-warning@+1 {{Source code in non-writable file}} void unwritable_cast(void((*g)(int *q)) : itype(_Ptr)>)) { - // expected-warning@+1 {{Source code in non-writable file}} int *p = 0; // Now 3C thinks it needs to insert _Assume_bounds_cast<_Ptr> around `p` // because it forgets that it is allowed to use the original type of `g`. + // expected-warning@+2 {{Source code in non-writable file}} // expected-warning@+1 {{3C internal error: tried to insert a cast into an unwritable file}} (*g)(p); } diff --git a/clang/test/3C/json_formatting.c b/clang/test/3C/json_formatting.c index 2a060ea567cf..9b2a08a53607 100644 --- a/clang/test/3C/json_formatting.c +++ b/clang/test/3C/json_formatting.c @@ -12,3 +12,12 @@ int *a; int *b(int *c); static int *d() { return 0; } void e(int *f, int len) { f[0]; } + +// from root_cause.c, this has a conflicting constraint +// with additional reasons that add more json structure +int get_strlen(char *s : itype(_Nt_array_ptr)); +void test_conflict() { + char *c; + get_strlen(c); + char **cptr = &c; +} diff --git a/clang/test/3C/root_cause.c b/clang/test/3C/root_cause.c index 318c1f94bbdc..22bcd4cc0c5f 100644 --- a/clang/test/3C/root_cause.c +++ b/clang/test/3C/root_cause.c @@ -16,29 +16,31 @@ void *x; // expected-warning {{1 unchecked pointer: Default void* type}} void test0() { // unwritable-expected-warning@+1 {{0 unchecked pointers: Source code in non-writable file}} int *a; - // unwritable-expected-warning@+1 {{0 unchecked pointers: Source code in non-writable file}} char *b; // unwritable-expected-warning@+1 {{0 unchecked pointers: Source code in non-writable file}} a = b; // expected-warning {{2 unchecked pointers: Cast from char * to int *}} - // unwritable-expected-warning@+1 {{0 unchecked pointers: Source code in non-writable file}} int *c; + // unwritable-expected-warning@+1 {{0 unchecked pointers: Source code in non-writable file}} (char *)c; // expected-warning {{1 unchecked pointer: Cast from int * to char *}} - // unwritable-expected-warning@+1 {{0 unchecked pointers: Source code in non-writable file}} int *e; // unwritable-expected-warning@+1 {{0 unchecked pointers: Source code in non-writable file}} char *f; + // unwritable-expected-warning@+2 {{0 unchecked pointers: Source code in non-writable file}} // unwritable-expected-warning@+1 {{0 unchecked pointers: Source code in non-writable file}} f = (char *)e; // expected-warning {{2 unchecked pointers: Cast from int * to char *}} } void test1() { int a; + int *b; // unwritable-expected-warning@+1 {{0 unchecked pointers: Source code in non-writable file}} - int *b; + b = my_malloc(sizeof(int)); // #conflict + // expected-warning@#conflict {{1 unchecked pointer: Inferred conflicting types}} + // expected-note@#conflict {{Return type from an allocator}} + // expected-note@#conflict {{Assigning from &my_malloc to my_malloc_tyarg_0}} // unwritable-expected-warning@+1 {{0 unchecked pointers: Source code in non-writable file}} - b = my_malloc(sizeof(int)); // expected-warning {{1 unchecked pointer: Bad pointer type solution}} b[0] = 1; union u { @@ -88,3 +90,21 @@ PA pa_test0, pa_test1; // unwritable-expected-warning@+2 {{0 unchecked pointers: Source code in non-writable file}} // expected-warning@+1 {{1 unchecked pointer: Internal constraint for generic function declaration, for which 3C currently does not support re-solving.}} _Itype_for_any(T) void remember(void *p : itype(_Ptr)) {} + +// Demonstrate multiple locations in notes +// unwritable-expected-warning@+1 {{0 unchecked pointers: Source code in non-writable file}} +int get_strlen(char *s : itype(_Nt_array_ptr)); // expected-warning {{1 unchecked pointer: Unchecked pointer in parameter or return of undefined function get_strlen}} +void test_conflict() { + char *c; // #decl + // unwritable-expected-warning@+1 {{0 unchecked pointers: Source code in non-writable file}} + get_strlen(c); // #as_nt + // unwritable-expected-warning@+2 {{0 unchecked pointers: Source code in non-writable file}} + // unwritable-expected-warning@+1 {{0 unchecked pointers: Source code in non-writable file}} + char **cptr = &c; // #as_ptr +} +// expected-warning@#decl {{2 unchecked pointers: Inferred conflicting types}} +// expected-note@#as_ptr {{Operand of address-of has PTR lower bound}} +// expected-note@#as_nt {{Assigning from c to s}} + + + From 8170d2fba47bf24bf64e183508e6789d3bc81e0b Mon Sep 17 00:00:00 2001 From: "Matt McCutchen (Correct Computation)" Date: Wed, 6 Oct 2021 11:43:46 -0400 Subject: [PATCH 32/38] mkString fixes (mostly moved from the multi-decl overhaul) (#714) - Remove extra spaces after wild pointer `*` symbols in some cases. - Generate valid output in some more complex cases involving checked pointers, fixed-size arrays, and/or function pointers. - Remove now-obsolete code from mkString. --- clang/include/clang/3C/ConstraintVariables.h | 18 ++- clang/lib/3C/ConstraintVariables.cpp | 115 +++++++------------ clang/test/3C/compound_literal.c | 2 +- clang/test/3C/definedType.c | 2 + clang/test/3C/fptr_array.c | 20 +++- clang/test/3C/itype_typedef.c | 2 +- clang/test/3C/itypes_for_extern.c | 10 +- clang/test/3C/ptr_array.c | 4 +- 8 files changed, 79 insertions(+), 94 deletions(-) diff --git a/clang/include/clang/3C/ConstraintVariables.h b/clang/include/clang/3C/ConstraintVariables.h index 470363066b11..65b0974decb7 100644 --- a/clang/include/clang/3C/ConstraintVariables.h +++ b/clang/include/clang/3C/ConstraintVariables.h @@ -42,8 +42,15 @@ typedef std::set CVars; // Holds Atoms, one for each of the pointer (*) declared in the program. typedef std::vector CAtoms; +// Options for ConstraintVariable::mkString, using the code pattern described in +// clang/include/clang/3C/OptionalParams.h. Use the MKSTRING_OPTS macro to +// generate a MkStringOpts instance at a call site. struct MkStringOpts { + // True when the generated string should include + // the name of the variable, false for just the type. bool EmitName = true; + // True when the generated string is expected + // to be used inside an itype. bool ForItype = false; bool EmitPointee = false; bool UnmaskTypedef = false; @@ -113,11 +120,12 @@ class ConstraintVariable { qtyToStr(QT, N == RETVAR ? "" : N)) {} public: - // Create a "for-rewriting" representation of this ConstraintVariable. - // The 'emitName' parameter is true when the generated string should include - // the name of the variable, false for just the type. - // The 'forIType' parameter is true when the generated string is expected - // to be used inside an itype. + // Generate source code for the type and (in certain cases) the name of the + // variable or function represented by this ConstraintVariable that reflects + // any changes made by 3C and is suitable to insert during rewriting. + // + // This method is used in several contexts with special requirements, which + // are addressed by the options in MkStringOpts; see the comments there. virtual std::string mkString(Constraints &CS, const MkStringOpts &Opts = {}) const = 0; diff --git a/clang/lib/3C/ConstraintVariables.cpp b/clang/lib/3C/ConstraintVariables.cpp index 4da97be8b4b0..011a438ead5b 100644 --- a/clang/lib/3C/ConstraintVariables.cpp +++ b/clang/lib/3C/ConstraintVariables.cpp @@ -750,12 +750,19 @@ PointerVariableConstraint::mkString(Constraints &CS, UNPACK_OPTS(EmitName, ForItype, EmitPointee, UnmaskTypedef, UseName, ForItypeBase); + // This function has become pretty ad-hoc and has a number of known bugs: see + // https://github.com/correctcomputation/checkedc-clang/issues/703. We hope to + // overhaul it in the future. + // The name field encodes if this variable is the return type for a function. // TODO: store this information in a separate field. bool IsReturn = getName() == RETVAR; - if (UseName.empty()) + if (UseName.empty()) { UseName = getName(); + if (UseName.empty()) + EmitName = false; + } if (IsTypedef && !UnmaskTypedef) { std::string QualTypedef = gatherQualStrings() + TypedefString; @@ -782,13 +789,9 @@ PointerVariableConstraint::mkString(Constraints &CS, bool EmittedBase = false; // Have we emitted the name of the variable yet? bool EmittedName = false; - // Was the last variable an Array? - bool PrevArr = false; - // Is the entire type so far an array? - bool AllArrays = true; if (!EmitName || IsReturn) EmittedName = true; - uint32_t TypeIdx = 0; + int TypeIdx = 0; // If we've set a GenericIndex for void, it means we're converting it into // a generic function so give it the default generic type name. @@ -802,7 +805,6 @@ PointerVariableConstraint::mkString(Constraints &CS, } auto It = Vars.begin(); - auto I = 0; // Skip over first pointer level if only emitting pointee string. // This is needed when inserting type arguments. if (EmitPointee) @@ -810,8 +812,8 @@ PointerVariableConstraint::mkString(Constraints &CS, // Iterate through the vars(), but if we have an internal typedef, then stop // once you reach the typedef's level. for (; It != Vars.end() && IMPLIES(TypedefLevelInfo.HasTypedef, - I < TypedefLevelInfo.TypedefLevel); - ++It, I++) { + TypeIdx < TypedefLevelInfo.TypedefLevel); + ++It, TypeIdx++) { const auto &V = *It; ConstAtom *C = nullptr; if (ForItypeBase) { @@ -833,21 +835,23 @@ PointerVariableConstraint::mkString(Constraints &CS, if (!ForItype && InferredGenericIndex == -1 && isVoidPtr()) K = Atom::A_Wild; - if (PrevArr && ArrSizes.at(TypeIdx).first != O_SizedArray && !EmittedName) { - EmittedName = true; + // In a case like `_Ptr p[2]`, the ` p[2]` needs to end up _after_ the + // `>`, so we need to push the ` p[2]` onto EndStrs _before_ the code below + // pushes the `>`. In general, before we visit a checked pointer level (not + // a checked array level), we need to transfer any pending array levels and + // emit the name (if applicable). + if (K != Atom::A_Wild && ArrSizes.at(TypeIdx).first != O_SizedArray) { addArrayAnnotations(ConstArrs, EndStrs); - EndStrs.push_front(" " + UseName); + if (!EmittedName) { + EmittedName = true; + EndStrs.push_front(" " + UseName); + } } - PrevArr = ArrSizes.at(TypeIdx).first == O_SizedArray; switch (K) { case Atom::A_Ptr: getQualString(TypeIdx, Ss); - // We need to check and see if this level of variable - // is constrained by a bounds safe interface. If it is, - // then we shouldn't re-write it. - AllArrays = false; EmittedBase = false; Ss << "_Ptr<"; EndStrs.push_front(">"); @@ -861,38 +865,28 @@ PointerVariableConstraint::mkString(Constraints &CS, // we should substitute [K]. if (emitArraySize(ConstArrs, TypeIdx, K)) break; - AllArrays = false; - // We need to check and see if this level of variable - // is constrained by a bounds safe interface. If it is, - // then we shouldn't re-write it. EmittedBase = false; Ss << "_Array_ptr<"; EndStrs.push_front(">"); break; case Atom::A_NTArr: + // If this is an NTArray. + getQualString(TypeIdx, Ss); if (emitArraySize(ConstArrs, TypeIdx, K)) break; - AllArrays = false; - // This additional check is to prevent fall-through from the array. - if (K == Atom::A_NTArr) { - // If this is an NTArray. - getQualString(TypeIdx, Ss); - // We need to check and see if this level of variable - // is constrained by a bounds safe interface. If it is, - // then we shouldn't re-write it. - EmittedBase = false; - Ss << "_Nt_array_ptr<"; - EndStrs.push_front(">"); - break; - } - LLVM_FALLTHROUGH; + EmittedBase = false; + Ss << "_Nt_array_ptr<"; + EndStrs.push_front(">"); + break; // If there is no array in the original program, then we fall through to // the case where we write a pointer value. case Atom::A_Wild: if (emitArraySize(ConstArrs, TypeIdx, K)) break; - AllArrays = false; + // FIXME: This code emits wild pointer levels with the outermost on the + // left. The outermost should be on the right + // (https://github.com/correctcomputation/checkedc-clang/issues/161). if (FV != nullptr) { FptrInner << "*"; getQualString(TypeIdx, FptrInner); @@ -912,35 +906,19 @@ PointerVariableConstraint::mkString(Constraints &CS, llvm_unreachable("impossible"); break; } - TypeIdx++; - } - - // If the previous variable was an array or - // if we are leaving an array run, we need to emit the - // annotation for a stack-array - if (PrevArr && !ConstArrs.empty()) - addArrayAnnotations(ConstArrs, EndStrs); - - // If the whole type is an array so far, and we haven't emitted - // a name yet, then emit the name so that it appears before - // the the stack array type. - if (PrevArr && !EmittedName && AllArrays) { - EmittedName = true; - EndStrs.push_front(" " + UseName); } if (!EmittedBase) { // If we have a FV pointer, then our "base" type is a function pointer type. if (FV) { - if (Ss.str().empty()) { - if (!EmittedName) { - FptrInner << UseName; - EmittedName = true; - } - for (std::string Str : EndStrs) - FptrInner << Str; - EndStrs.clear(); + if (!EmittedName) { + FptrInner << UseName; + EmittedName = true; } + std::deque FptrEndStrs; + addArrayAnnotations(ConstArrs, FptrEndStrs); + for (std::string Str : FptrEndStrs) + FptrInner << Str; bool EmitFVName = !FptrInner.str().empty(); if (EmitFVName) Ss << FV->mkString(CS, MKSTRING_OPTS(UseName = FptrInner.str(), @@ -958,27 +936,20 @@ PointerVariableConstraint::mkString(Constraints &CS, } } - // Add closing elements to type - for (std::string Str : EndStrs) { - Ss << Str; - } - // No space after itype. - if (!EmittedName && !UseName.empty()) { + if (!EmittedName) { if (!StringRef(Ss.str()).endswith("*")) Ss << " "; Ss << UseName; } - // Final array dropping. - if (!ConstArrs.empty()) { - std::deque ArrStrs; - addArrayAnnotations(ConstArrs, ArrStrs); - for (std::string Str : ArrStrs) - Ss << Str; + // Add closing elements to type + addArrayAnnotations(ConstArrs, EndStrs); + for (std::string Str : EndStrs) { + Ss << Str; } - if (IsReturn && !ForItype) + if (IsReturn && !ForItype && !StringRef(Ss.str()).endswith("*")) Ss << " "; return Ss.str(); diff --git a/clang/test/3C/compound_literal.c b/clang/test/3C/compound_literal.c index b749aeb3ad7d..d656369b3abe 100644 --- a/clang/test/3C/compound_literal.c +++ b/clang/test/3C/compound_literal.c @@ -67,7 +67,7 @@ void lists() { int *d[2] = (int *[2]){&x, (int *)1}; //CHECK_NOALL: int *d[2] = (int *[2]){&x, (int *)1}; - //CHECK_ALL: int * d _Checked[2] = (int * _Checked[2]){&x, (int *)1}; + //CHECK_ALL: int *d _Checked[2] = (int * _Checked[2]){&x, (int *)1}; int *d0 = d[0]; //CHECK: int *d0 = d[0]; diff --git a/clang/test/3C/definedType.c b/clang/test/3C/definedType.c index 0593f0849e47..344657841754 100644 --- a/clang/test/3C/definedType.c +++ b/clang/test/3C/definedType.c @@ -57,6 +57,8 @@ baz **f[1]; // CHECK_ALL: _Ptr<_Ptr> f _Checked[1] = {((void *)0)}; baz (*g)[1]; // CHECK_ALL: _Ptr g = ((void *)0); +baz *(*g2)[1]; +// CHECK_ALL: _Ptr<_Ptr _Checked[1]> g2 = ((void *)0); baz h[1][1]; // CHECK_ALL: baz h _Checked[1] _Checked[1]; diff --git a/clang/test/3C/fptr_array.c b/clang/test/3C/fptr_array.c index 2e6c22907303..8d12acc69153 100644 --- a/clang/test/3C/fptr_array.c +++ b/clang/test/3C/fptr_array.c @@ -9,7 +9,7 @@ void (*fs[2])(int *); void (*f)(int *); -//CHECK_NOALL: void (* fs[2])(_Ptr) = {((void *)0)}; +//CHECK_NOALL: void (*fs[2])(_Ptr) = {((void *)0)}; //CHECK_NOALL: void (*f)(_Ptr) = ((void *)0); //CHECK_ALL: _Ptr)> fs _Checked[2] = {((void *)0)}; //CHECK_ALL: _Ptr)> f = ((void *)0); @@ -17,7 +17,7 @@ void (*f)(int *); void (*gs[2])(int *); void g_impl(int *x) { x = 1; } void (*g)(int *) = g_impl; -//CHECK_NOALL: void (* gs[2])(int * : itype(_Ptr)) = {((void *)0)}; +//CHECK_NOALL: void (*gs[2])(int * : itype(_Ptr)) = {((void *)0)}; //CHECK_NOALL: void g_impl(int *x : itype(_Ptr)) { x = 1; } //CHECK_NOALL: void (*g)(int * : itype(_Ptr)) = g_impl; @@ -30,21 +30,21 @@ void (*h)(void *); int *(*is[2])(void); int *(*i)(void); -//CHECK_NOALL: _Ptr (* is[2])(void) = {((void *)0)}; +//CHECK_NOALL: _Ptr (*is[2])(void) = {((void *)0)}; //CHECK_NOALL: _Ptr (*i)(void) = ((void *)0); //CHECK_ALL: _Ptr<_Ptr (void)> is _Checked[2] = {((void *)0)}; //CHECK_ALL: _Ptr<_Ptr (void)> i = ((void *)0); int *(**js[2])(void); int *(**j)(void); -//CHECK_NOALL: _Ptr (** js[2])(void) = {((void *)0)}; +//CHECK_NOALL: _Ptr (**js[2])(void) = {((void *)0)}; //CHECK_NOALL: _Ptr (**j)(void) = ((void *)0); //CHECK_ALL: _Ptr<_Ptr<_Ptr (void)>> js _Checked[2] = {((void *)0)}; //CHECK_ALL: _Ptr<_Ptr<_Ptr (void)>> j = ((void *)0); int *(*ks[2][2])(void); int *(*k)(void); -//CHECK_NOALL: _Ptr (* ks[2][2])(void) = {((void *)0)}; +//CHECK_NOALL: _Ptr (*ks[2][2])(void) = {((void *)0)}; //CHECK_NOALL: _Ptr (*k)(void) = ((void *)0); //CHECK_ALL: _Ptr<_Ptr (void)> ks _Checked[2] _Checked[2] = {((void *)0)}; //CHECK_ALL: _Ptr<_Ptr (void)> k = ((void *)0); @@ -53,6 +53,9 @@ void (* const l)(int *); //CHECK_NOALL: void (*const l)(_Ptr) = ((void *)0); //CHECK_ALL: const _Ptr)> l = ((void *)0); +#define UNWRITABLE_MS_DECL void (*ms[2])(int *) +UNWRITABLE_MS_DECL; + void test(void){ fs[1] = f; gs[1] = g; @@ -63,4 +66,11 @@ void test(void){ void (* const ls[1])(int *) = {l}; //CHECK_NOALL: void (*const ls[1])(_Ptr) = {l}; //CHECK_ALL: const _Ptr)> ls _Checked[1] = {l}; + + void (*(*pms)[2])(int *) = &ms; + //CHECK: _Ptr pms = &ms; + + void (*(*apms[1])[2])(int *) = {&ms}; + //CHECK_NOALL: void (*(*apms[1])[2])(int *) = {&ms}; + //CHECK_ALL: _Ptr apms _Checked[1] = {&ms}; } diff --git a/clang/test/3C/itype_typedef.c b/clang/test/3C/itype_typedef.c index f86d7d6b914e..6de35ae7c6bc 100644 --- a/clang/test/3C/itype_typedef.c +++ b/clang/test/3C/itype_typedef.c @@ -34,7 +34,7 @@ void test2(td2 a) { typedef int *td3; td3 test3() { //CHECK: typedef _Ptr td3; -//CHECK: int * test3(void) : itype(td3) { +//CHECK: int *test3(void) : itype(td3) { return (int*) 1; } diff --git a/clang/test/3C/itypes_for_extern.c b/clang/test/3C/itypes_for_extern.c index 46edee5f0649..6566cc1e25bb 100644 --- a/clang/test/3C/itypes_for_extern.c +++ b/clang/test/3C/itypes_for_extern.c @@ -30,14 +30,8 @@ int *baz(int *a, int len, int *b) { return a; } -// FIXME: The space between after the first star is needed for the idempotence -// test to pass. If there isn't a space there, 3c will add one when -// re-run on its original output. This should be fixed ideally in two -// ways. First, the space shouldn't be added when not present in the -// original source, and second, the second conversion should not rewrite -// the declaration. -void buz(int * (*f)(int *, int *)) {} -//CHECK: void buz(int * ((*f)(int *, int *)) : itype(_Ptr<_Ptr (_Ptr, _Ptr)>)) _Checked {} +void buz(int *(*f)(int *, int *)) {} +//CHECK: void buz(int *((*f)(int *, int *)) : itype(_Ptr<_Ptr (_Ptr, _Ptr)>)) _Checked {} typedef int * int_star; void typedef_test(int_star p) {} diff --git a/clang/test/3C/ptr_array.c b/clang/test/3C/ptr_array.c index ab4fa535bb41..72ea53909cb7 100644 --- a/clang/test/3C/ptr_array.c +++ b/clang/test/3C/ptr_array.c @@ -28,7 +28,7 @@ void test1(int *a) { int *b[1] = {a}; //CHECK_NOALL: int *b[1] = {a}; - //CHECK_ALL: int * b _Checked[1] = {a}; + //CHECK_ALL: int *b _Checked[1] = {a}; } /* Example from from the issue */ @@ -40,7 +40,7 @@ int *foo() { int z = 3; int *ptrs[4] = {&x, &y, &z, (int *)5}; //CHECK_NOALL: int *ptrs[4] = {&x, &y, &z, (int *)5}; - //CHECK_ALL: int * ptrs _Checked[4] = {&x, &y, &z, (int *)5}; + //CHECK_ALL: int *ptrs _Checked[4] = {&x, &y, &z, (int *)5}; int *ret; //CHECK: int *ret; for (int i = 0; i < 4; i++) { From 48ebd6cb9ff487d4004214491499b14cf623f173 Mon Sep 17 00:00:00 2001 From: Kyle Headley Date: Mon, 18 Oct 2021 14:15:39 -0400 Subject: [PATCH 33/38] Use bounds(unknown) rather than an locally-undefined variable copied from function parameter (#717) * implement trivial fix for bounds * change to bounds(unknown) and regenerate tests * add test from issue --- clang/lib/3C/CastPlacement.cpp | 29 +++++++------- clang/test/3C/b_tests/b1_allsafe.c | 4 +- .../test/3C/b_tests/b23_explicitunsafecast.c | 4 +- clang/test/3C/b_tests/b23_retswitchexplicit.c | 4 +- .../test/3C/b_tests/b24_implicitunsafecast.c | 4 +- clang/test/3C/b_tests/b24_retswitchimplicit.c | 4 +- clang/test/3C/b_tests/b25_castprotosafe.c | 4 +- clang/test/3C/b_tests/b26_castprotounsafe.c | 4 +- .../3C/b_tests/b26_castprotounsafeimplicit.c | 4 +- .../b26_castprotounsafeimplicitretswitch.c | 4 +- clang/test/3C/b_tests/b2_calleeunsafe.c | 4 +- clang/test/3C/b_tests/b3_onecallerunsafe.c | 4 +- clang/test/3C/b_tests/b4_bothunsafe.c | 4 +- clang/test/3C/b_tests/b5_calleeunsafeproto.c | 4 +- clang/test/3C/b_tests/b6_callerunsafeproto.c | 4 +- clang/test/3C/b_tests/b7_allsafeproto.c | 4 +- clang/test/3C/cast.c | 38 +++++++++++++++---- .../test/3C/generated_tests/arrinstructboth.c | 4 +- .../generated_tests/arrinstructbothmulti1.c | 4 +- .../3C/generated_tests/arrinstructcallee.c | 4 +- .../generated_tests/arrinstructcalleemulti1.c | 4 +- .../3C/generated_tests/arrinstructcaller.c | 4 +- .../generated_tests/arrinstructcallermulti1.c | 4 +- .../3C/generated_tests/arrinstructprotoboth.c | 4 +- .../generated_tests/arrinstructprotocallee.c | 4 +- .../generated_tests/arrinstructprotocaller.c | 4 +- clang/test/3C/itype_nt_arr_cast.c | 3 +- clang/test/3C/linkedlist.c | 19 +++++++++- clang/test/3C/malloc_array.c | 2 +- clang/test/3C/pointerarithm.c | 4 +- clang/test/3C/subtyp.c | 2 +- 31 files changed, 114 insertions(+), 79 deletions(-) diff --git a/clang/lib/3C/CastPlacement.cpp b/clang/lib/3C/CastPlacement.cpp index b3886eaed7e5..9d0ff2798209 100644 --- a/clang/lib/3C/CastPlacement.cpp +++ b/clang/lib/3C/CastPlacement.cpp @@ -172,33 +172,30 @@ CastPlacementVisitor::getCastString(ConstraintVariable *Dst, case CAST_TO_WILD: return std::make_pair("((" + Dst->getRewritableOriginalTy() + ")", ")"); case CAST_TO_CHECKED: { + // Needed as default to TypeVar branch below, reset otherwise. + std::string Type = "_Ptr<"; std::string Suffix = ")"; if (const auto *DstPVC = dyn_cast(Dst)) { assert("Checked cast not to a pointer" && !DstPVC->getCvars().empty()); ConstAtom *CA = Info.getConstraints().getAssignment(DstPVC->getCvars().at(0)); - // Writing an _Assume_bounds_cast to an array type requires inserting - // the bounds for destination array. These can come from the source - // code or the infered bounds. If neither source is available, use empty - // bounds. - if (isa(CA) || isa(CA)) { - std::string Bounds = ""; - if (DstPVC->srcHasBounds()) - Bounds = DstPVC->getBoundsStr(); - else if (DstPVC->hasBoundsKey()) - Bounds = ABRewriter.getBoundsString(DstPVC, nullptr, true); - if (Bounds.empty()) - Bounds = "byte_count(0)"; - - Suffix = ", " + Bounds + ")"; + // TODO: Writing an _Assume_bounds_cast to an array type requires + // inserting the bounds for destination array. But the names used in src + // and dest may be different, so we need more sophisticated code to + // convert to local variable names. Use unknown bounds for now. + if (isa(CA)) { + Type = "_Array_ptr<"; + Suffix = ", bounds(unknown))"; + } else if (isa(CA)) { + Type = "_Nt_array_ptr<"; + Suffix = ", bounds(unknown))"; } } // The destination's type may be generic, which would have an out-of-scope // type var, so use the already analysed local type var instead - std::string Type; if (TypeVar != nullptr) { - Type = "_Ptr<" + + Type += TypeVar->mkString(Info.getConstraints(), MKSTRING_OPTS(EmitName = false, EmitPointee = true)) + ">"; diff --git a/clang/test/3C/b_tests/b1_allsafe.c b/clang/test/3C/b_tests/b1_allsafe.c index 19f1af154241..9b2786683aed 100644 --- a/clang/test/3C/b_tests/b1_allsafe.c +++ b/clang/test/3C/b_tests/b1_allsafe.c @@ -31,7 +31,7 @@ int *foo() { //CHECK: _Ptr y = &sy; int *z = sus(x, y); //CHECK_NOALL: _Ptr z = sus(x, y); - //CHECK_ALL: _Ptr z = sus(_Assume_bounds_cast<_Array_ptr>(x, byte_count(0)), y); + //CHECK_ALL: _Ptr z = sus(_Assume_bounds_cast<_Array_ptr>(x, bounds(unknown)), y); *z = *z + 1; return z; } @@ -47,6 +47,6 @@ int *bar() { //CHECK: _Ptr y = &sy; int *z = (sus(x, y)); //CHECK_NOALL: _Ptr z = (sus(x, y)); - //CHECK_ALL: _Ptr z = (sus(_Assume_bounds_cast<_Array_ptr>(x, byte_count(0)), y)); + //CHECK_ALL: _Ptr z = (sus(_Assume_bounds_cast<_Array_ptr>(x, bounds(unknown)), y)); return z; } diff --git a/clang/test/3C/b_tests/b23_explicitunsafecast.c b/clang/test/3C/b_tests/b23_explicitunsafecast.c index 8c5627e6160a..e406686ec11a 100644 --- a/clang/test/3C/b_tests/b23_explicitunsafecast.c +++ b/clang/test/3C/b_tests/b23_explicitunsafecast.c @@ -30,7 +30,7 @@ int *foo() { //CHECK: _Ptr y = &sy; int *z = (int *)sus(x, y); //CHECK_NOALL: _Ptr z = (_Ptr)sus(x, y); - //CHECK_ALL: _Ptr z = (_Ptr)sus(_Assume_bounds_cast<_Array_ptr>(x, byte_count(0)), y); + //CHECK_ALL: _Ptr z = (_Ptr)sus(_Assume_bounds_cast<_Array_ptr>(x, bounds(unknown)), y); *z = *z + 1; return z; } @@ -45,6 +45,6 @@ char *bar() { //CHECK: _Ptr y = &sy; char *z = (char *)(sus(x, y)); //CHECK_NOALL: char *z = (char *)(((int *)sus(x, y))); - //CHECK_ALL: char *z = (char *)(((int *)sus(_Assume_bounds_cast<_Array_ptr>(x, byte_count(0)), y))); + //CHECK_ALL: char *z = (char *)(((int *)sus(_Assume_bounds_cast<_Array_ptr>(x, bounds(unknown)), y))); return z; } diff --git a/clang/test/3C/b_tests/b23_retswitchexplicit.c b/clang/test/3C/b_tests/b23_retswitchexplicit.c index dc1c305020ee..41573e1d60bc 100644 --- a/clang/test/3C/b_tests/b23_retswitchexplicit.c +++ b/clang/test/3C/b_tests/b23_retswitchexplicit.c @@ -30,7 +30,7 @@ char *foo() { //CHECK: _Ptr y = &sy; char *z = (int *)sus(x, y); //CHECK_NOALL: char *z = (int *)sus(x, y); - //CHECK_ALL: char *z = (int *)sus(_Assume_bounds_cast<_Array_ptr>(x, byte_count(0)), y); + //CHECK_ALL: char *z = (int *)sus(_Assume_bounds_cast<_Array_ptr>(x, bounds(unknown)), y); *z = *z + 1; return z; } @@ -45,6 +45,6 @@ int *bar() { //CHECK: _Ptr y = &sy; int *z = (char *)(sus(x, y)); //CHECK_NOALL: int *z = (char *)(sus(x, y)); - //CHECK_ALL: int *z = (char *)(sus(_Assume_bounds_cast<_Array_ptr>(x, byte_count(0)), y)); + //CHECK_ALL: int *z = (char *)(sus(_Assume_bounds_cast<_Array_ptr>(x, bounds(unknown)), y)); return z; } diff --git a/clang/test/3C/b_tests/b24_implicitunsafecast.c b/clang/test/3C/b_tests/b24_implicitunsafecast.c index 95f1c228ef39..5306503b62ca 100644 --- a/clang/test/3C/b_tests/b24_implicitunsafecast.c +++ b/clang/test/3C/b_tests/b24_implicitunsafecast.c @@ -30,7 +30,7 @@ int *foo() { //CHECK: _Ptr y = &sy; int *z = (int *)sus(x, y); //CHECK_NOALL: _Ptr z = (_Ptr)sus(x, y); - //CHECK_ALL: _Ptr z = (_Ptr)sus(_Assume_bounds_cast<_Array_ptr>(x, byte_count(0)), y); + //CHECK_ALL: _Ptr z = (_Ptr)sus(_Assume_bounds_cast<_Array_ptr>(x, bounds(unknown)), y); *z = *z + 1; return z; } @@ -45,6 +45,6 @@ char *bar() { //CHECK: _Ptr y = &sy; char *z = sus(x, y); //CHECK_NOALL: char *z = ((int *)sus(x, y)); - //CHECK_ALL: char *z = ((int *)sus(_Assume_bounds_cast<_Array_ptr>(x, byte_count(0)), y)); + //CHECK_ALL: char *z = ((int *)sus(_Assume_bounds_cast<_Array_ptr>(x, bounds(unknown)), y)); return z; } diff --git a/clang/test/3C/b_tests/b24_retswitchimplicit.c b/clang/test/3C/b_tests/b24_retswitchimplicit.c index 97422ff7038a..efd2b99a964e 100644 --- a/clang/test/3C/b_tests/b24_retswitchimplicit.c +++ b/clang/test/3C/b_tests/b24_retswitchimplicit.c @@ -30,7 +30,7 @@ char *foo() { //CHECK: _Ptr y = &sy; char *z = (int *)sus(x, y); //CHECK_NOALL: char *z = (int *)sus(x, y); - //CHECK_ALL: char *z = (int *)sus(_Assume_bounds_cast<_Array_ptr>(x, byte_count(0)), y); + //CHECK_ALL: char *z = (int *)sus(_Assume_bounds_cast<_Array_ptr>(x, bounds(unknown)), y); *z = *z + 1; return z; } @@ -45,6 +45,6 @@ int *bar() { //CHECK: _Ptr y = &sy; int *z = sus(x, y); //CHECK_NOALL: int *z = ((char *)sus(x, y)); - //CHECK_ALL: int *z = ((char *)sus(_Assume_bounds_cast<_Array_ptr>(x, byte_count(0)), y)); + //CHECK_ALL: int *z = ((char *)sus(_Assume_bounds_cast<_Array_ptr>(x, bounds(unknown)), y)); return z; } diff --git a/clang/test/3C/b_tests/b25_castprotosafe.c b/clang/test/3C/b_tests/b25_castprotosafe.c index e3df2fc8d5f1..30836e7a982a 100644 --- a/clang/test/3C/b_tests/b25_castprotosafe.c +++ b/clang/test/3C/b_tests/b25_castprotosafe.c @@ -23,7 +23,7 @@ int *foo() { //CHECK: _Ptr y = &sy; int *z = (int *)sus(x, y); //CHECK_NOALL: _Ptr z = (_Ptr)sus(x, y); - //CHECK_ALL: _Ptr z = (_Ptr)sus(_Assume_bounds_cast<_Array_ptr>(x, byte_count(0)), y); + //CHECK_ALL: _Ptr z = (_Ptr)sus(_Assume_bounds_cast<_Array_ptr>(x, bounds(unknown)), y); *z = *z + 1; return z; } @@ -38,7 +38,7 @@ int *bar() { //CHECK: _Ptr y = &sy; int *z = (int *)(sus(x, y)); //CHECK_NOALL: _Ptr z = (_Ptr)(sus(x, y)); - //CHECK_ALL: _Ptr z = (_Ptr)(sus(_Assume_bounds_cast<_Array_ptr>(x, byte_count(0)), y)); + //CHECK_ALL: _Ptr z = (_Ptr)(sus(_Assume_bounds_cast<_Array_ptr>(x, bounds(unknown)), y)); return z; } diff --git a/clang/test/3C/b_tests/b26_castprotounsafe.c b/clang/test/3C/b_tests/b26_castprotounsafe.c index 17d2c22fc3da..121075d90065 100644 --- a/clang/test/3C/b_tests/b26_castprotounsafe.c +++ b/clang/test/3C/b_tests/b26_castprotounsafe.c @@ -23,7 +23,7 @@ int *foo() { //CHECK: _Ptr y = &sy; int *z = (int *)sus(x, y); //CHECK_NOALL: _Ptr z = (_Ptr)sus(x, y); - //CHECK_ALL: _Ptr z = (_Ptr)sus(_Assume_bounds_cast<_Array_ptr>(x, byte_count(0)), y); + //CHECK_ALL: _Ptr z = (_Ptr)sus(_Assume_bounds_cast<_Array_ptr>(x, bounds(unknown)), y); *z = *z + 1; return z; } @@ -38,7 +38,7 @@ char *bar() { //CHECK: _Ptr y = &sy; char *z = (char *)(sus(x, y)); //CHECK_NOALL: char *z = (char *)(((int *)sus(x, y))); - //CHECK_ALL: char *z = (char *)(((int *)sus(_Assume_bounds_cast<_Array_ptr>(x, byte_count(0)), y))); + //CHECK_ALL: char *z = (char *)(((int *)sus(_Assume_bounds_cast<_Array_ptr>(x, bounds(unknown)), y))); return z; } diff --git a/clang/test/3C/b_tests/b26_castprotounsafeimplicit.c b/clang/test/3C/b_tests/b26_castprotounsafeimplicit.c index b5138fff1d1f..c31ef1a89c40 100644 --- a/clang/test/3C/b_tests/b26_castprotounsafeimplicit.c +++ b/clang/test/3C/b_tests/b26_castprotounsafeimplicit.c @@ -23,7 +23,7 @@ int *foo() { //CHECK: _Ptr y = &sy; int *z = (int *)sus(x, y); //CHECK_NOALL: _Ptr z = (_Ptr)sus(x, y); - //CHECK_ALL: _Ptr z = (_Ptr)sus(_Assume_bounds_cast<_Array_ptr>(x, byte_count(0)), y); + //CHECK_ALL: _Ptr z = (_Ptr)sus(_Assume_bounds_cast<_Array_ptr>(x, bounds(unknown)), y); *z = *z + 1; return z; } @@ -38,7 +38,7 @@ char *bar() { //CHECK: _Ptr y = &sy; char *z = (sus(x, y)); //CHECK_NOALL: char *z = (((int *)sus(x, y))); - //CHECK_ALL: char *z = (((int *)sus(_Assume_bounds_cast<_Array_ptr>(x, byte_count(0)), y))); + //CHECK_ALL: char *z = (((int *)sus(_Assume_bounds_cast<_Array_ptr>(x, bounds(unknown)), y))); return z; } diff --git a/clang/test/3C/b_tests/b26_castprotounsafeimplicitretswitch.c b/clang/test/3C/b_tests/b26_castprotounsafeimplicitretswitch.c index fe050ca58a77..562c1f79526a 100644 --- a/clang/test/3C/b_tests/b26_castprotounsafeimplicitretswitch.c +++ b/clang/test/3C/b_tests/b26_castprotounsafeimplicitretswitch.c @@ -23,7 +23,7 @@ char *foo() { //CHECK: _Ptr y = &sy; char *z = (int *)sus(x, y); //CHECK_NOALL: char *z = (int *)sus(x, y); - //CHECK_ALL: char *z = (int *)sus(_Assume_bounds_cast<_Array_ptr>(x, byte_count(0)), y); + //CHECK_ALL: char *z = (int *)sus(_Assume_bounds_cast<_Array_ptr>(x, bounds(unknown)), y); *z = *z + 1; return z; } @@ -38,7 +38,7 @@ int *bar() { //CHECK: _Ptr y = &sy; int *z = (sus(x, y)); //CHECK_NOALL: int *z = (((char *)sus(x, y))); - //CHECK_ALL: int *z = (((char *)sus(_Assume_bounds_cast<_Array_ptr>(x, byte_count(0)), y))); + //CHECK_ALL: int *z = (((char *)sus(_Assume_bounds_cast<_Array_ptr>(x, bounds(unknown)), y))); return z; } diff --git a/clang/test/3C/b_tests/b2_calleeunsafe.c b/clang/test/3C/b_tests/b2_calleeunsafe.c index 4ca1297289b7..c1df90e06978 100644 --- a/clang/test/3C/b_tests/b2_calleeunsafe.c +++ b/clang/test/3C/b_tests/b2_calleeunsafe.c @@ -32,7 +32,7 @@ int *foo() { //CHECK: _Ptr y = &sy; int *z = sus(x, y); //CHECK_NOALL: _Ptr z = sus(x, y); - //CHECK_ALL: _Ptr z = sus(_Assume_bounds_cast<_Array_ptr>(x, byte_count(0)), y); + //CHECK_ALL: _Ptr z = sus(_Assume_bounds_cast<_Array_ptr>(x, bounds(unknown)), y); *z = *z + 1; return z; } @@ -48,6 +48,6 @@ int *bar() { //CHECK: _Ptr y = &sy; int *z = (sus(x, y)); //CHECK_NOALL: _Ptr z = (sus(x, y)); - //CHECK_ALL: _Ptr z = (sus(_Assume_bounds_cast<_Array_ptr>(x, byte_count(0)), y)); + //CHECK_ALL: _Ptr z = (sus(_Assume_bounds_cast<_Array_ptr>(x, bounds(unknown)), y)); return z; } diff --git a/clang/test/3C/b_tests/b3_onecallerunsafe.c b/clang/test/3C/b_tests/b3_onecallerunsafe.c index a4f88688c401..34697e0439ab 100644 --- a/clang/test/3C/b_tests/b3_onecallerunsafe.c +++ b/clang/test/3C/b_tests/b3_onecallerunsafe.c @@ -33,7 +33,7 @@ int *foo() { //CHECK: _Ptr y = &sy; int *z = sus(x, y); //CHECK_NOALL: _Ptr z = sus(x, y); - //CHECK_ALL: _Ptr z = sus(_Assume_bounds_cast<_Array_ptr>(x, byte_count(0)), y); + //CHECK_ALL: _Ptr z = sus(_Assume_bounds_cast<_Array_ptr>(x, bounds(unknown)), y); *z = *z + 1; return z; } @@ -50,7 +50,7 @@ int *bar() { //CHECK: _Ptr y = &sy; int *z = sus(x, y); //CHECK_NOALL: int *z = ((int *)sus(x, y)); - //CHECK_ALL: _Array_ptr z = sus(_Assume_bounds_cast<_Array_ptr>(x, byte_count(0)), y); + //CHECK_ALL: _Array_ptr z = sus(_Assume_bounds_cast<_Array_ptr>(x, bounds(unknown)), y); z += 2; *z = -17; return z; diff --git a/clang/test/3C/b_tests/b4_bothunsafe.c b/clang/test/3C/b_tests/b4_bothunsafe.c index 3f6bf0ad71c1..c90de3045d92 100644 --- a/clang/test/3C/b_tests/b4_bothunsafe.c +++ b/clang/test/3C/b_tests/b4_bothunsafe.c @@ -32,7 +32,7 @@ int *foo() { //CHECK: _Ptr y = &sy; int *z = sus(x, y); //CHECK_NOALL: _Ptr z = sus(x, y); - //CHECK_ALL: _Ptr z = sus(_Assume_bounds_cast<_Array_ptr>(x, byte_count(0)), y); + //CHECK_ALL: _Ptr z = sus(_Assume_bounds_cast<_Array_ptr>(x, bounds(unknown)), y); *z = *z + 1; return z; } @@ -48,7 +48,7 @@ int *bar() { //CHECK: _Ptr y = &sy; int *z = sus(x, y); //CHECK_NOALL: int *z = sus(x, y); - //CHECK_ALL: _Array_ptr z = sus(_Assume_bounds_cast<_Array_ptr>(x, byte_count(0)), y); + //CHECK_ALL: _Array_ptr z = sus(_Assume_bounds_cast<_Array_ptr>(x, bounds(unknown)), y); z += 2; *z = -17; return z; diff --git a/clang/test/3C/b_tests/b5_calleeunsafeproto.c b/clang/test/3C/b_tests/b5_calleeunsafeproto.c index 0f27240a86ca..d7949a890323 100644 --- a/clang/test/3C/b_tests/b5_calleeunsafeproto.c +++ b/clang/test/3C/b_tests/b5_calleeunsafeproto.c @@ -24,7 +24,7 @@ int *foo() { //CHECK: _Ptr y = &sy; int *z = sus(x, y); //CHECK_NOALL: _Ptr z = sus(x, y); - //CHECK_ALL: _Ptr z = sus(_Assume_bounds_cast<_Array_ptr>(x, byte_count(0)), y); + //CHECK_ALL: _Ptr z = sus(_Assume_bounds_cast<_Array_ptr>(x, bounds(unknown)), y); *z = *z + 1; return z; } @@ -40,7 +40,7 @@ int *bar() { //CHECK: _Ptr y = &sy; int *z = (sus(x, y)); //CHECK_NOALL: _Ptr z = (sus(x, y)); - //CHECK_ALL: _Ptr z = (sus(_Assume_bounds_cast<_Array_ptr>(x, byte_count(0)), y)); + //CHECK_ALL: _Ptr z = (sus(_Assume_bounds_cast<_Array_ptr>(x, bounds(unknown)), y)); return z; } diff --git a/clang/test/3C/b_tests/b6_callerunsafeproto.c b/clang/test/3C/b_tests/b6_callerunsafeproto.c index abf1ad882a0d..8e0ada35d2c5 100644 --- a/clang/test/3C/b_tests/b6_callerunsafeproto.c +++ b/clang/test/3C/b_tests/b6_callerunsafeproto.c @@ -24,7 +24,7 @@ int *foo() { //CHECK: _Ptr y = &sy; int *z = sus(x, y); //CHECK_NOALL: _Ptr z = sus(x, y); - //CHECK_ALL: _Ptr z = sus(_Assume_bounds_cast<_Array_ptr>(x, byte_count(0)), y); + //CHECK_ALL: _Ptr z = sus(_Assume_bounds_cast<_Array_ptr>(x, bounds(unknown)), y); *z = *z + 1; return z; } @@ -40,7 +40,7 @@ int *bar() { //CHECK: _Ptr y = &sy; int *z = (sus(x, y)); //CHECK_NOALL: int *z = (((int *)sus(x, y))); - //CHECK_ALL: _Array_ptr z = (sus(_Assume_bounds_cast<_Array_ptr>(x, byte_count(0)), y)); + //CHECK_ALL: _Array_ptr z = (sus(_Assume_bounds_cast<_Array_ptr>(x, bounds(unknown)), y)); z += 2; return z; } diff --git a/clang/test/3C/b_tests/b7_allsafeproto.c b/clang/test/3C/b_tests/b7_allsafeproto.c index 4db8908bb82a..ac31308eb3af 100644 --- a/clang/test/3C/b_tests/b7_allsafeproto.c +++ b/clang/test/3C/b_tests/b7_allsafeproto.c @@ -24,7 +24,7 @@ int *foo() { //CHECK: _Ptr y = &sy; int *z = sus(x, y); //CHECK_NOALL: _Ptr z = sus(x, y); - //CHECK_ALL: _Ptr z = sus(_Assume_bounds_cast<_Array_ptr>(x, byte_count(0)), y); + //CHECK_ALL: _Ptr z = sus(_Assume_bounds_cast<_Array_ptr>(x, bounds(unknown)), y); *z = *z + 1; return z; } @@ -40,7 +40,7 @@ int *bar() { //CHECK: _Ptr y = &sy; int *z = (sus(x, y)); //CHECK_NOALL: _Ptr z = (sus(x, y)); - //CHECK_ALL: _Ptr z = (sus(_Assume_bounds_cast<_Array_ptr>(x, byte_count(0)), y)); + //CHECK_ALL: _Ptr z = (sus(_Assume_bounds_cast<_Array_ptr>(x, bounds(unknown)), y)); return z; } diff --git a/clang/test/3C/cast.c b/clang/test/3C/cast.c index deba20691f52..3b1c1de94572 100644 --- a/clang/test/3C/cast.c +++ b/clang/test/3C/cast.c @@ -27,28 +27,52 @@ void bar(void) { #include _Itype_for_any(T) void *ident(void *i : itype(_Ptr)) +//CHECK: void *ident(void *i : itype(_Ptr)) : itype(_Ptr) { return i;}; +//CHECK: : itype(_Ptr) { return i;}; void test_generic_casts(void) { int *ptr_cast = (int *)malloc(3 * sizeof(int)); - // CHECK_ALL: _Ptr ptr_cast = (_Ptr)malloc(3 * sizeof(int)); + //CHECK_NOALL: int *ptr_cast = (int *)malloc(3 * sizeof(int)); + //CHECK_ALL: _Ptr ptr_cast = (_Ptr)malloc(3 * sizeof(int)); int *ptr_nocast = malloc(3 * sizeof(int)); - // CHECK_ALL: _Ptr ptr_nocast = malloc(3 * sizeof(int)); + //CHECK_NOALL: int *ptr_nocast = malloc(3 * sizeof(int)); + //CHECK_ALL: _Ptr ptr_nocast = malloc(3 * sizeof(int)); char *ptr_badcast = (int *)malloc(3 * sizeof(int)); - // CHECK_ALL: char *ptr_badcast = (int *)malloc(3 * sizeof(int)); + //CHECK: char *ptr_badcast = (int *)malloc(3 * sizeof(int)); int *ptr_custom = (int *)ident(ptr_cast); - // CHECK_ALL: _Ptr ptr_custom = (_Ptr)ident(ptr_cast); + //CHECK_NOALL: int *ptr_custom = (int *)ident(ptr_cast); + //CHECK_ALL: _Ptr ptr_custom = (_Ptr)ident(ptr_cast); int *ptr_cast_c = (int *)calloc(3, sizeof(int)); - // CHECK_ALL: _Ptr ptr_cast_c = (_Ptr)calloc(3, sizeof(int)); + //CHECK_NOALL: int *ptr_cast_c = (int *)calloc(3, sizeof(int)); + //CHECK_ALL: _Ptr ptr_cast_c = (_Ptr)calloc(3, sizeof(int)); int *ptr_cast_r = (int *)realloc(ptr_cast_c, 3 * sizeof(int)); - // CHECK_ALL: _Ptr ptr_cast_r = (_Ptr)realloc(ptr_cast_c, 3 * sizeof(int)); + //CHECK: _Ptr ptr_cast_r = (_Ptr)realloc(ptr_cast_c, 3 * sizeof(int)); } // Check that casts to generics use the local type variables int *mk_ptr(void); +//CHECK: int *mk_ptr(void); _For_any(T) void free_ptr(_Ptr p_ptr) {} +//CHECK: _For_any(T) void free_ptr(_Ptr p_ptr) _Checked {} void work (void) { int *node = mk_ptr(); // wild because extern unavailable free_ptr(node); - // CHECK: free_ptr(_Assume_bounds_cast<_Ptr>(node)); + //CHECK: free_ptr(_Assume_bounds_cast<_Ptr>(node)); +} + +// cast from wild to checked needs to cast to the new destination type +_Itype_for_any(T) void has_itype(void *a : itype(_Array_ptr) byte_count(l), unsigned int l); +void infers_generic(void *a, unsigned int l) { + //CHECK_ALL: _Itype_for_any(T) void infers_generic(_Array_ptr a : byte_count(l), unsigned int l) { + has_itype(a, l); +} +void test_cast_to_inferred() { + int *p = 1; + int l; + + has_itype(p, l); + //CHECK: has_itype(p, l); + infers_generic(p, l); + //CHECK_ALL: infers_generic(_Assume_bounds_cast<_Array_ptr>(p, bounds(unknown)), l); } diff --git a/clang/test/3C/generated_tests/arrinstructboth.c b/clang/test/3C/generated_tests/arrinstructboth.c index 36d80ae409c1..8a5b6ed221de 100644 --- a/clang/test/3C/generated_tests/arrinstructboth.c +++ b/clang/test/3C/generated_tests/arrinstructboth.c @@ -131,7 +131,7 @@ struct warr *foo() { //CHECK_ALL: struct warr *y = malloc(sizeof(struct warr)); struct warr *z = sus(x, y); //CHECK_NOALL: _Ptr z = sus(x, y); - //CHECK_ALL: _Ptr z = sus(x, _Assume_bounds_cast<_Array_ptr>(y, byte_count(0))); + //CHECK_ALL: _Ptr z = sus(x, _Assume_bounds_cast<_Array_ptr>(y, bounds(unknown))); return z; } @@ -145,7 +145,7 @@ struct warr *bar() { //CHECK_ALL: struct warr *y = malloc(sizeof(struct warr)); struct warr *z = sus(x, y); //CHECK_NOALL: struct warr *z = sus(x, y); - //CHECK_ALL: _Array_ptr z = sus(x, _Assume_bounds_cast<_Array_ptr>(y, byte_count(0))); + //CHECK_ALL: _Array_ptr z = sus(x, _Assume_bounds_cast<_Array_ptr>(y, bounds(unknown))); z += 2; return z; } diff --git a/clang/test/3C/generated_tests/arrinstructbothmulti1.c b/clang/test/3C/generated_tests/arrinstructbothmulti1.c index 3c05909763be..2684a417e625 100644 --- a/clang/test/3C/generated_tests/arrinstructbothmulti1.c +++ b/clang/test/3C/generated_tests/arrinstructbothmulti1.c @@ -120,7 +120,7 @@ struct warr *foo() { //CHECK_ALL: struct warr *y = malloc(sizeof(struct warr)); struct warr *z = sus(x, y); //CHECK_NOALL: _Ptr z = sus(x, y); - //CHECK_ALL: _Ptr z = sus(x, _Assume_bounds_cast<_Array_ptr>(y, byte_count(0))); + //CHECK_ALL: _Ptr z = sus(x, _Assume_bounds_cast<_Array_ptr>(y, bounds(unknown))); return z; } @@ -134,7 +134,7 @@ struct warr *bar() { //CHECK_ALL: struct warr *y = malloc(sizeof(struct warr)); struct warr *z = sus(x, y); //CHECK_NOALL: struct warr *z = sus(x, y); - //CHECK_ALL: _Array_ptr z = sus(x, _Assume_bounds_cast<_Array_ptr>(y, byte_count(0))); + //CHECK_ALL: _Array_ptr z = sus(x, _Assume_bounds_cast<_Array_ptr>(y, bounds(unknown))); z += 2; return z; } diff --git a/clang/test/3C/generated_tests/arrinstructcallee.c b/clang/test/3C/generated_tests/arrinstructcallee.c index a2dfd8816e62..f91dcdda75bf 100644 --- a/clang/test/3C/generated_tests/arrinstructcallee.c +++ b/clang/test/3C/generated_tests/arrinstructcallee.c @@ -131,7 +131,7 @@ struct warr *foo() { //CHECK_ALL: struct warr *y = malloc(sizeof(struct warr)); struct warr *z = sus(x, y); //CHECK_NOALL: _Ptr z = sus(x, y); - //CHECK_ALL: _Ptr z = sus(x, _Assume_bounds_cast<_Array_ptr>(y, byte_count(0))); + //CHECK_ALL: _Ptr z = sus(x, _Assume_bounds_cast<_Array_ptr>(y, bounds(unknown))); return z; } @@ -144,6 +144,6 @@ struct warr *bar() { //CHECK_ALL: struct warr *y = malloc(sizeof(struct warr)); struct warr *z = sus(x, y); //CHECK_NOALL: _Ptr z = sus(x, y); - //CHECK_ALL: _Ptr z = sus(x, _Assume_bounds_cast<_Array_ptr>(y, byte_count(0))); + //CHECK_ALL: _Ptr z = sus(x, _Assume_bounds_cast<_Array_ptr>(y, bounds(unknown))); return z; } diff --git a/clang/test/3C/generated_tests/arrinstructcalleemulti1.c b/clang/test/3C/generated_tests/arrinstructcalleemulti1.c index 6e9e45634928..8f2393e06e98 100644 --- a/clang/test/3C/generated_tests/arrinstructcalleemulti1.c +++ b/clang/test/3C/generated_tests/arrinstructcalleemulti1.c @@ -120,7 +120,7 @@ struct warr *foo() { //CHECK_ALL: struct warr *y = malloc(sizeof(struct warr)); struct warr *z = sus(x, y); //CHECK_NOALL: _Ptr z = sus(x, y); - //CHECK_ALL: _Ptr z = sus(x, _Assume_bounds_cast<_Array_ptr>(y, byte_count(0))); + //CHECK_ALL: _Ptr z = sus(x, _Assume_bounds_cast<_Array_ptr>(y, bounds(unknown))); return z; } @@ -133,6 +133,6 @@ struct warr *bar() { //CHECK_ALL: struct warr *y = malloc(sizeof(struct warr)); struct warr *z = sus(x, y); //CHECK_NOALL: _Ptr z = sus(x, y); - //CHECK_ALL: _Ptr z = sus(x, _Assume_bounds_cast<_Array_ptr>(y, byte_count(0))); + //CHECK_ALL: _Ptr z = sus(x, _Assume_bounds_cast<_Array_ptr>(y, bounds(unknown))); return z; } diff --git a/clang/test/3C/generated_tests/arrinstructcaller.c b/clang/test/3C/generated_tests/arrinstructcaller.c index 995989752a08..f5b0e36430c1 100644 --- a/clang/test/3C/generated_tests/arrinstructcaller.c +++ b/clang/test/3C/generated_tests/arrinstructcaller.c @@ -130,7 +130,7 @@ struct warr *foo() { //CHECK_ALL: struct warr *y = malloc(sizeof(struct warr)); struct warr *z = sus(x, y); //CHECK_NOALL: _Ptr z = sus(x, y); - //CHECK_ALL: _Ptr z = sus(x, _Assume_bounds_cast<_Array_ptr>(y, byte_count(0))); + //CHECK_ALL: _Ptr z = sus(x, _Assume_bounds_cast<_Array_ptr>(y, bounds(unknown))); return z; } @@ -144,7 +144,7 @@ struct warr *bar() { //CHECK_ALL: struct warr *y = malloc(sizeof(struct warr)); struct warr *z = sus(x, y); //CHECK_NOALL: struct warr *z = ((struct warr *)sus(x, y)); - //CHECK_ALL: _Array_ptr z = sus(x, _Assume_bounds_cast<_Array_ptr>(y, byte_count(0))); + //CHECK_ALL: _Array_ptr z = sus(x, _Assume_bounds_cast<_Array_ptr>(y, bounds(unknown))); z += 2; return z; } diff --git a/clang/test/3C/generated_tests/arrinstructcallermulti1.c b/clang/test/3C/generated_tests/arrinstructcallermulti1.c index 4b03204ff246..0e047d5320f9 100644 --- a/clang/test/3C/generated_tests/arrinstructcallermulti1.c +++ b/clang/test/3C/generated_tests/arrinstructcallermulti1.c @@ -120,7 +120,7 @@ struct warr *foo() { //CHECK_ALL: struct warr *y = malloc(sizeof(struct warr)); struct warr *z = sus(x, y); //CHECK_NOALL: _Ptr z = sus(x, y); - //CHECK_ALL: _Ptr z = sus(x, _Assume_bounds_cast<_Array_ptr>(y, byte_count(0))); + //CHECK_ALL: _Ptr z = sus(x, _Assume_bounds_cast<_Array_ptr>(y, bounds(unknown))); return z; } @@ -134,7 +134,7 @@ struct warr *bar() { //CHECK_ALL: struct warr *y = malloc(sizeof(struct warr)); struct warr *z = sus(x, y); //CHECK_NOALL: struct warr *z = ((struct warr *)sus(x, y)); - //CHECK_ALL: _Array_ptr z = sus(x, _Assume_bounds_cast<_Array_ptr>(y, byte_count(0))); + //CHECK_ALL: _Array_ptr z = sus(x, _Assume_bounds_cast<_Array_ptr>(y, bounds(unknown))); z += 2; return z; } diff --git a/clang/test/3C/generated_tests/arrinstructprotoboth.c b/clang/test/3C/generated_tests/arrinstructprotoboth.c index a8101eb3516a..3431912cf4ac 100644 --- a/clang/test/3C/generated_tests/arrinstructprotoboth.c +++ b/clang/test/3C/generated_tests/arrinstructprotoboth.c @@ -117,7 +117,7 @@ struct warr *foo() { //CHECK_ALL: struct warr *y = malloc(sizeof(struct warr)); struct warr *z = sus(x, y); //CHECK_NOALL: _Ptr z = sus(x, y); - //CHECK_ALL: _Ptr z = sus(x, _Assume_bounds_cast<_Array_ptr>(y, byte_count(0))); + //CHECK_ALL: _Ptr z = sus(x, _Assume_bounds_cast<_Array_ptr>(y, bounds(unknown))); return z; } @@ -131,7 +131,7 @@ struct warr *bar() { //CHECK_ALL: struct warr *y = malloc(sizeof(struct warr)); struct warr *z = sus(x, y); //CHECK_NOALL: struct warr *z = sus(x, y); - //CHECK_ALL: _Array_ptr z = sus(x, _Assume_bounds_cast<_Array_ptr>(y, byte_count(0))); + //CHECK_ALL: _Array_ptr z = sus(x, _Assume_bounds_cast<_Array_ptr>(y, bounds(unknown))); z += 2; return z; } diff --git a/clang/test/3C/generated_tests/arrinstructprotocallee.c b/clang/test/3C/generated_tests/arrinstructprotocallee.c index c40450f1840d..44ba0c399d4a 100644 --- a/clang/test/3C/generated_tests/arrinstructprotocallee.c +++ b/clang/test/3C/generated_tests/arrinstructprotocallee.c @@ -117,7 +117,7 @@ struct warr *foo() { //CHECK_ALL: struct warr *y = malloc(sizeof(struct warr)); struct warr *z = sus(x, y); //CHECK_NOALL: _Ptr z = sus(x, y); - //CHECK_ALL: _Ptr z = sus(x, _Assume_bounds_cast<_Array_ptr>(y, byte_count(0))); + //CHECK_ALL: _Ptr z = sus(x, _Assume_bounds_cast<_Array_ptr>(y, bounds(unknown))); return z; } @@ -130,7 +130,7 @@ struct warr *bar() { //CHECK_ALL: struct warr *y = malloc(sizeof(struct warr)); struct warr *z = sus(x, y); //CHECK_NOALL: _Ptr z = sus(x, y); - //CHECK_ALL: _Ptr z = sus(x, _Assume_bounds_cast<_Array_ptr>(y, byte_count(0))); + //CHECK_ALL: _Ptr z = sus(x, _Assume_bounds_cast<_Array_ptr>(y, bounds(unknown))); return z; } diff --git a/clang/test/3C/generated_tests/arrinstructprotocaller.c b/clang/test/3C/generated_tests/arrinstructprotocaller.c index 02f0c661c8fd..7d1e90653dee 100644 --- a/clang/test/3C/generated_tests/arrinstructprotocaller.c +++ b/clang/test/3C/generated_tests/arrinstructprotocaller.c @@ -117,7 +117,7 @@ struct warr *foo() { //CHECK_ALL: struct warr *y = malloc(sizeof(struct warr)); struct warr *z = sus(x, y); //CHECK_NOALL: _Ptr z = sus(x, y); - //CHECK_ALL: _Ptr z = sus(x, _Assume_bounds_cast<_Array_ptr>(y, byte_count(0))); + //CHECK_ALL: _Ptr z = sus(x, _Assume_bounds_cast<_Array_ptr>(y, bounds(unknown))); return z; } @@ -131,7 +131,7 @@ struct warr *bar() { //CHECK_ALL: struct warr *y = malloc(sizeof(struct warr)); struct warr *z = sus(x, y); //CHECK_NOALL: struct warr *z = ((struct warr *)sus(x, y)); - //CHECK_ALL: _Array_ptr z = sus(x, _Assume_bounds_cast<_Array_ptr>(y, byte_count(0))); + //CHECK_ALL: _Array_ptr z = sus(x, _Assume_bounds_cast<_Array_ptr>(y, bounds(unknown))); z += 2; return z; } diff --git a/clang/test/3C/itype_nt_arr_cast.c b/clang/test/3C/itype_nt_arr_cast.c index 45cdc290aacc..07fa0dd45b01 100644 --- a/clang/test/3C/itype_nt_arr_cast.c +++ b/clang/test/3C/itype_nt_arr_cast.c @@ -1,7 +1,6 @@ // RUN: rm -rf %t* // RUN: 3c -base-dir=%S -alltypes -addcr %s -- | FileCheck -match-full-lines -check-prefixes="CHECK_ALL","CHECK" %s // RUN: 3c -base-dir=%S -addcr %s -- | FileCheck -match-full-lines -check-prefixes="CHECK_NOALL","CHECK" %s -// RUN: 3c -base-dir=%S -alltypes -addcr %s -- | %clang -c -fcheckedc-extension -x c -o /dev/null - // RUN: 3c -base-dir=%S -alltypes -output-dir=%t.checked %s -- // RUN: 3c -base-dir=%t.checked -alltypes %t.checked/itype_nt_arr_cast.c -- | diff %t.checked/itype_nt_arr_cast.c - @@ -59,6 +58,6 @@ char *caller4(char *c) { // works as is, and I don't want to waste time when this is just a workaround // for a CheckedC clang bug. fn4(c); - //CHECK: fn4(_Assume_bounds_cast<_Nt_array_ptr>(c, byte_count(0))); + //CHECK: fn4(_Assume_bounds_cast<_Nt_array_ptr>(c, bounds(unknown))); return c; } diff --git a/clang/test/3C/linkedlist.c b/clang/test/3C/linkedlist.c index 20c9addd6866..e0329f0c4b54 100644 --- a/clang/test/3C/linkedlist.c +++ b/clang/test/3C/linkedlist.c @@ -37,6 +37,7 @@ struct node { struct list { Node *head; + //CHECK: _Ptr head; }; Node *createnode(int data); @@ -46,7 +47,7 @@ Node *createnode(int data) { //CHECK: _Ptr createnode(int data) { Node *newNode = malloc(sizeof(Node)); - //CHECK: _Ptr newNode = malloc(sizeof(Node)); + //CHECK: _Ptr newNode = malloc(sizeof(Node)); if (!newNode) { @@ -80,6 +81,7 @@ void display(List *list) { //CHECK: void display(_Ptr list) { Node *current = list->head; + //CHECK: _Ptr current = list->head; if (list->head == NULL) @@ -95,8 +97,10 @@ void add(int data, List *list) { //CHECK: void add(int data, _Ptr list) { Node *current = NULL; + //CHECK: _Ptr current = NULL; if (list->head == NULL) { + //CHECK: if (list->head == NULL) _Checked { list->head = createnode(data); @@ -107,6 +111,7 @@ void add(int data, List *list) { current = list->head; while (current->next != NULL) { + //CHECK: while (current->next != NULL) _Checked { current = current->next; } @@ -119,10 +124,13 @@ void delete (int data, List *list) { //CHECK: void delete (int data, _Ptr list) { Node *current = list->head; + //CHECK: _Ptr current = list->head; Node *previous = current; + //CHECK: _Ptr previous = current; while (current != NULL) { + //CHECK: while (current != NULL) _Checked { if (current->data == data) { @@ -147,12 +155,16 @@ void reverse(List *list) { //CHECK: void reverse(_Ptr list) { Node *reversed = NULL; + //CHECK: _Ptr reversed = NULL; Node *current = list->head; + //CHECK: _Ptr current = list->head; Node *temp = NULL; + //CHECK: _Ptr temp = NULL; while (current != NULL) { + //CHECK: while (current != NULL) _Checked { temp = current; @@ -167,13 +179,16 @@ void reverse(List *list) { } void destroy(List *list) { - //CHECK: void destroy(_Ptr list) { + //CHECK: void destroy(_Ptr list) { Node *current = list->head; + //CHECK: _Ptr current = list->head; Node *next = current; + //CHECK: _Ptr next = current; while (current != NULL) { + //CHECK: while (current != NULL) _Checked { next = current->next; free(current); current = next; diff --git a/clang/test/3C/malloc_array.c b/clang/test/3C/malloc_array.c index 7dc493c7d20e..d9f872e82f24 100644 --- a/clang/test/3C/malloc_array.c +++ b/clang/test/3C/malloc_array.c @@ -20,7 +20,7 @@ void bar(void) { //CHECK: y = (int *)5; int *z = foo(y); //CHECK_NOALL: _Ptr z = foo(y); - //CHECK_ALL: _Ptr z = foo(_Assume_bounds_cast<_Array_ptr>(y, count(2 + 1))); + //CHECK_ALL: _Ptr z = foo(_Assume_bounds_cast<_Array_ptr>(y, bounds(unknown))); } void force(int *x) {} diff --git a/clang/test/3C/pointerarithm.c b/clang/test/3C/pointerarithm.c index bd729eea5ac3..1b0a961e4561 100644 --- a/clang/test/3C/pointerarithm.c +++ b/clang/test/3C/pointerarithm.c @@ -26,7 +26,7 @@ int *foo() { } //CHECK: _Ptr foo(void) { //CHECK: _Ptr y = &sy; -//CHECK: _Ptr z = sus(_Assume_bounds_cast<_Array_ptr>(x, byte_count(0)), y); +//CHECK: _Ptr z = sus(_Assume_bounds_cast<_Array_ptr>(x, bounds(unknown)), y); int *bar() { int sx = 3, sy = 4, *x = &sx, *y = &sy; @@ -36,4 +36,4 @@ int *bar() { } //CHECK: _Ptr bar(void) { //CHECK: _Ptr y = &sy; -//CHECK: _Ptr z = sus(_Assume_bounds_cast<_Array_ptr>(x, byte_count(0)), y) + 2; +//CHECK: _Ptr z = sus(_Assume_bounds_cast<_Array_ptr>(x, bounds(unknown)), y) + 2; diff --git a/clang/test/3C/subtyp.c b/clang/test/3C/subtyp.c index 433c4eef6b38..58f579e4f4f3 100644 --- a/clang/test/3C/subtyp.c +++ b/clang/test/3C/subtyp.c @@ -25,7 +25,7 @@ void baz() { //CHECK: int *x = (int *)5; foo(x); //CHECK_NOALL: foo(x); - //CHECK_ALL: foo(_Assume_bounds_cast<_Nt_array_ptr>(x, byte_count(0))); + //CHECK_ALL: foo(_Assume_bounds_cast<_Nt_array_ptr>(x, bounds(unknown))); } void force(int *x) {} //CHECK: void force(_Ptr x) _Checked {} From 359b6a418ec19726cbcb500c7d7696c6bd24cb71 Mon Sep 17 00:00:00 2001 From: John Kastner Date: Tue, 19 Oct 2021 17:05:36 -0400 Subject: [PATCH 34/38] Reduce minimum number of affected pointers for `-warn-root-cause` to 1 (#721) Root causes will now be emitted if they affect at least one pointer. This is a change from the previous minimum of two affected pointers. Fixes correctcomputation/checkedc-clang#685. --- clang/lib/3C/RewriteUtils.cpp | 7 +++---- clang/test/3C/base_subdir/unwritable_rootcauses.c | 2 ++ clang/test/3C/root_cause.c | 4 ++-- clang/test/3C/root_cause.h | 9 +++++++++ 4 files changed, 16 insertions(+), 6 deletions(-) create mode 100644 clang/test/3C/root_cause.h diff --git a/clang/lib/3C/RewriteUtils.cpp b/clang/lib/3C/RewriteUtils.cpp index d9f69597a356..90ac3561f5eb 100644 --- a/clang/lib/3C/RewriteUtils.cpp +++ b/clang/lib/3C/RewriteUtils.cpp @@ -589,11 +589,10 @@ void RewriteConsumer::emitRootCauseDiagnostics(ASTContext &Context) { if (!File.getError()) { SourceLocation SL = SM.translateFileLineCol(*File, PSL.getLineNo(), PSL.getColSNo()); - // Limit emitted root causes to those that effect more than one pointer - // or are in the main file of the TU. Alternatively, don't filter causes - // if -warn-all-root-cause is passed. + // Limit emitted root causes to those that effect at least one pointer. + // Alternatively, don't filter causes if -warn-all-root-cause is passed. int PtrCount = I.getNumPtrsAffected(WReason.first); - if (_3COpts.WarnAllRootCause || SM.isInMainFile(SL) || PtrCount > 1) { + if (_3COpts.WarnAllRootCause || PtrCount > 0) { // SL is invalid when the File is not in the current translation unit. if (SL.isValid()) { EmittedDiagnostics.insert(PSL); diff --git a/clang/test/3C/base_subdir/unwritable_rootcauses.c b/clang/test/3C/base_subdir/unwritable_rootcauses.c index 2c4f76c187fe..1d0bb33d23be 100644 --- a/clang/test/3C/base_subdir/unwritable_rootcauses.c +++ b/clang/test/3C/base_subdir/unwritable_rootcauses.c @@ -1,4 +1,6 @@ // RUN: cd %S // RUN: 3c -use-malloc=my_malloc -alltypes -addcr -output-dir=%t.checked/base_subdir -warn-all-root-cause %s -- -Xclang -verify=unwritable-expected -Wno-everything +// RUN: 3c -use-malloc=my_malloc -alltypes -addcr -output-dir=%t.checked/base_subdir -warn-root-cause %s -- -Xclang -verify=not-all-unwritable-expected -Wno-everything +// not-all-unwritable-expected-no-diagnostics #include "../root_cause.c" diff --git a/clang/test/3C/root_cause.c b/clang/test/3C/root_cause.c index 22bcd4cc0c5f..17811a8c7820 100644 --- a/clang/test/3C/root_cause.c +++ b/clang/test/3C/root_cause.c @@ -1,4 +1,5 @@ // RUN: 3c -use-malloc=my_malloc -base-dir=%S -alltypes -warn-all-root-cause %s -- -Xclang -verify -Wno-everything +// RUN: 3c -use-malloc=my_malloc -base-dir=%S -alltypes -warn-root-cause %s -- -Xclang -verify -Wno-everything // This test is unusual in that it checks for the errors in the code @@ -106,5 +107,4 @@ void test_conflict() { // expected-note@#as_ptr {{Operand of address-of has PTR lower bound}} // expected-note@#as_nt {{Assigning from c to s}} - - +#include "root_cause.h" diff --git a/clang/test/3C/root_cause.h b/clang/test/3C/root_cause.h new file mode 100644 index 000000000000..9000046bdfad --- /dev/null +++ b/clang/test/3C/root_cause.h @@ -0,0 +1,9 @@ +// Test that root cause errors are reported correctly in include header files. +// Included by root_cause.c + +void outside_of_main() { + // expected-warning@+3 {{1 unchecked pointer: Cast from int to int *}} + // unwritable-expected-warning@+2 {{0 unchecked pointers: Source code in non-writable file}} + // unwritable-expected-warning@+1 {{0 unchecked pointers: Source code in non-writable file}} + int *c = 1; +} From 94cd56c6568184a347790fdc33265a0801dc5495 Mon Sep 17 00:00:00 2001 From: John Kastner Date: Mon, 25 Oct 2021 12:39:14 -0400 Subject: [PATCH 35/38] More command line options clean up (#732) This is a follow up PR from #692 that moves all command line options into the _3COptions structure, moves all of 3C's command line options into the same category so that all options are shown by -help (fixes #393), and reformats some of the code around creating command line options. It also removes the the option -enable-itypeprop which was not referenced anywhere in the code. --- clang/include/clang/3C/3CGlobalOptions.h | 38 ++-- clang/lib/3C/3C.cpp | 10 +- clang/lib/3C/AVarBoundsInfo.cpp | 10 +- clang/lib/3C/ConstraintVariables.cpp | 20 +- clang/lib/3C/Constraints.cpp | 27 +-- clang/lib/3C/IntermediateToolHook.cpp | 9 +- clang/lib/3C/ProgramInfo.cpp | 1 - clang/tools/3c/3CStandalone.cpp | 239 +++++++++++++---------- 8 files changed, 169 insertions(+), 185 deletions(-) diff --git a/clang/include/clang/3C/3CGlobalOptions.h b/clang/include/clang/3C/3CGlobalOptions.h index e092c126683b..2615c13a9e75 100644 --- a/clang/include/clang/3C/3CGlobalOptions.h +++ b/clang/include/clang/3C/3CGlobalOptions.h @@ -21,54 +21,40 @@ // NOLINTNEXTLINE(readability-identifier-naming) struct _3COptions { bool DumpIntermediate; - bool Verbose; - std::string OutputPostfix; std::string OutputDir; - std::string ConstraintOutputJson; - bool DumpStats; - std::string StatsOutputJson; - std::string WildPtrInfoJson; - std::string PerWildPtrInfoJson; - std::vector AllocatorFunctions; - bool HandleVARARGS; - - bool EnablePropThruIType; - std::string BaseDir; bool AllowSourcesOutsideBaseDir; - bool AllTypes; - bool AddCheckedRegions; - bool EnableCCTypeChecker; - bool WarnRootCause; - bool WarnAllRootCause; - -#ifdef FIVE_C - bool RemoveItypes; - bool ForceItypes; -#endif - bool DumpUnwritableChanges; bool AllowUnwritableChanges; - bool AllowRewriteFailures; - bool ItypesForExtern; - bool InferTypesForUndefs; + bool DebugSolver; + bool OnlyGreatestSol; + bool OnlyLeastSol; + bool DisableRDs; + bool DisableFunctionEdges; + bool DisableInfDecls; + bool DisableArrH; + bool DebugArrSolver; +#ifdef FIVE_C + bool RemoveItypes; + bool ForceItypes; +#endif }; // NOLINTNEXTLINE(readability-identifier-naming) diff --git a/clang/lib/3C/3C.cpp b/clang/lib/3C/3C.cpp index f7a1cf38de5c..7eebc2edd660 100644 --- a/clang/lib/3C/3C.cpp +++ b/clang/lib/3C/3C.cpp @@ -32,12 +32,6 @@ using namespace llvm; #define BEFORE_SOLVING_SUFFIX "_before_solving_" #define AFTER_SUBTYPING_SUFFIX "_after_subtyping_" -cl::OptionCategory ArrBoundsInferCat("Array bounds inference options"); -static cl::opt - DebugArrSolver("debug-arr-solver", - cl::desc("Dump array bounds inference graph"), - cl::init(false), cl::cat(ArrBoundsInferCat)); - std::set FilePaths; struct _3COptions _3COpts; @@ -586,7 +580,7 @@ bool _3CInterface::solveConstraints() { GlobalProgramInfo.getABoundsInfo().addConstantArrayBounds( GlobalProgramInfo); - if (DebugArrSolver) + if (_3COpts.DebugArrSolver) GlobalProgramInfo.getABoundsInfo().dumpAVarGraph( "arr_bounds_initial.dot"); @@ -670,7 +664,7 @@ bool _3CInterface::writeAllConvertedFilesToDisk() { } bool _3CInterface::dumpStats() { - if (_3COpts.AllTypes && DebugArrSolver) { + if (_3COpts.AllTypes && _3COpts.DebugArrSolver) { GlobalProgramInfo.getABoundsInfo().dumpAVarGraph("arr_bounds_final.dot"); } diff --git a/clang/lib/3C/AVarBoundsInfo.cpp b/clang/lib/3C/AVarBoundsInfo.cpp index 3bb8bbd90745..82b3ea846ebd 100644 --- a/clang/lib/3C/AVarBoundsInfo.cpp +++ b/clang/lib/3C/AVarBoundsInfo.cpp @@ -13,18 +13,12 @@ #include "clang/3C/AVarBoundsInfo.h" #include "clang/3C/ConstraintResolver.h" #include "clang/3C/ProgramInfo.h" +#include "clang/3C/3CGlobalOptions.h" #include std::vector AVarBoundsInfo::PrioList{Declared, Allocator, FlowInferred, Heuristics}; -extern cl::OptionCategory ArrBoundsInferCat; -static cl::opt DisableInfDecls("disable-arr-missd", - cl::desc("Disable ignoring of missed " - "bounds from declarations."), - cl::init(false), - cl::cat(ArrBoundsInferCat)); - void AVarBoundsStats::print(llvm::raw_ostream &O, const std::set *InSrcArrs, bool JsonFormat) const { @@ -412,7 +406,7 @@ bool AvarBoundsInference::predictBounds(BoundsKey K, } else { bool IsDeclaredB = areDeclaredBounds(NBK, NKBChoice); - if (!IsDeclaredB || DisableInfDecls) { + if (!IsDeclaredB || _3COpts.DisableInfDecls) { // Oh, there are bounds for neighbour NBK but no bounds // can be inferred for K from it. InferredNBnds.clear(); diff --git a/clang/lib/3C/ConstraintVariables.cpp b/clang/lib/3C/ConstraintVariables.cpp index 011a438ead5b..da065bfe0c0b 100644 --- a/clang/lib/3C/ConstraintVariables.cpp +++ b/clang/lib/3C/ConstraintVariables.cpp @@ -14,24 +14,12 @@ #include "clang/3C/ProgramInfo.h" #include "clang/Lex/Lexer.h" #include "llvm/ADT/StringSwitch.h" -#include "llvm/Support/CommandLine.h" #include using namespace clang; // Macro for boolean implication. #define IMPLIES(a, b) ((a) ? (b) : true) -static llvm::cl::OptionCategory OptimizationCategory("Optimization category"); -static llvm::cl::opt - DisableRDs("disable-rds", - llvm::cl::desc("Disable reverse edges for Checked Constraints."), - llvm::cl::init(false), llvm::cl::cat(OptimizationCategory)); - -static llvm::cl::opt DisableFunctionEdges( - "disable-fnedgs", - llvm::cl::desc("Disable reverse edges for external functions."), - llvm::cl::init(false), llvm::cl::cat(OptimizationCategory)); - std::string ConstraintVariable::getRewritableOriginalTy() const { std::string OrigTyString = getOriginalTy(); std::string SpaceStr = " "; @@ -1734,7 +1722,7 @@ static void createAtomGeq(Constraints &CS, Atom *L, Atom *R, } break; case Wild_to_Safe: - if (!DisableRDs) { + if (!_3COpts.DisableRDs) { // Note: reversal. CS.addConstraint(CS.createGeq(R, L, Rsn, true)); } else { @@ -1763,7 +1751,7 @@ static void createAtomGeq(Constraints &CS, Atom *L, Atom *R, CS.addConstraint(CS.createGeq(R, L, Rsn, true)); break; case Wild_to_Safe: - if (!DisableRDs) { + if (!_3COpts.DisableRDs) { // Note: reversal. CS.addConstraint(CS.createGeq(R, L, Rsn, true)); } else { @@ -2313,8 +2301,8 @@ void FVComponentVariable::linkInternalExternal(ProgramInfo &I, // level. This is because CheckedC does not allow assignment from e.g. // a function return of type `int ** : itype(_Ptr<_Ptr>)` to a // variable with type `int **`. - if (DisableFunctionEdges || DisableRDs || EquateChecked || - (ExternalConstraint->getName() == RETVAR && J > 0)) + if (_3COpts.DisableFunctionEdges || _3COpts.DisableRDs || + EquateChecked || (ExternalConstraint->getName() == RETVAR && J > 0)) CS.addConstraint(CS.createGeq(ExternalA, InternalA, LinkReason, true)); } diff --git a/clang/lib/3C/Constraints.cpp b/clang/lib/3C/Constraints.cpp index 5e6268455777..04ab233a76f1 100644 --- a/clang/lib/3C/Constraints.cpp +++ b/clang/lib/3C/Constraints.cpp @@ -13,26 +13,11 @@ #include "clang/3C/3CGlobalOptions.h" #include "clang/3C/ConstraintVariables.h" #include "clang/3C/ConstraintsGraph.h" -#include "llvm/Support/CommandLine.h" #include #include using namespace llvm; -static cl::OptionCategory SolverCategory("solver options"); -static cl::opt DebugSolver("debug-solver", - cl::desc("Dump intermediate solver state"), - cl::init(false), cl::cat(SolverCategory)); -static cl::opt OnlyGreatestSol( - "only-g-sol", - cl::desc("Perform only greatest solution for Pty Constrains."), - cl::init(false), cl::cat(SolverCategory)); - -static cl::opt - OnlyLeastSol("only-l-sol", - cl::desc("Perform only least solution for Pty Constrains."), - cl::init(false), cl::cat(SolverCategory)); - // Remove the constraint from the global constraint set. bool Constraints::removeConstraint(Constraint *C) { bool RetVal = false; @@ -339,7 +324,7 @@ bool Constraints::graphBasedSolve() { } } - if (DebugSolver) + if (_3COpts.DebugSolver) GraphVizOutputGraph::dumpConstraintGraphs("initial_constraints_graph.dot", SolChkCG, SolPtrTypCG); @@ -351,9 +336,9 @@ bool Constraints::graphBasedSolve() { // Now solve PtrType constraints if (Res && _3COpts.AllTypes) { Env.doCheckedSolve(false); - bool RegularSolve = !(OnlyGreatestSol || OnlyLeastSol); + bool RegularSolve = !(_3COpts.OnlyGreatestSol || _3COpts.OnlyLeastSol); - if (OnlyLeastSol) { + if (_3COpts.OnlyLeastSol) { // Do only least solution. // First reset ptr solution to NTArr. Env.resetSolution( @@ -363,7 +348,7 @@ bool Constraints::graphBasedSolve() { }, getNTArr()); Res = doSolve(SolPtrTypCG, Env, this, true, nullptr, Conflicts); - } else if (OnlyGreatestSol) { + } else if (_3COpts.OnlyGreatestSol) { // Do only greatest solution Res = doSolve(SolPtrTypCG, Env, this, false, nullptr, Conflicts); } else { @@ -481,13 +466,13 @@ bool Constraints::graphBasedSolve() { // an empty. If the system could not be solved, the constraints in conflict // are returned in the first position. void Constraints::solve() { - if (DebugSolver) { + if (_3COpts.DebugSolver) { errs() << "constraints beginning solve\n"; dump(); } graphBasedSolve(); - if (DebugSolver) { + if (_3COpts.DebugSolver) { errs() << "solution, when done solving\n"; Environment.dump(); } diff --git a/clang/lib/3C/IntermediateToolHook.cpp b/clang/lib/3C/IntermediateToolHook.cpp index 69b7b77434f2..ce46f4af1022 100644 --- a/clang/lib/3C/IntermediateToolHook.cpp +++ b/clang/lib/3C/IntermediateToolHook.cpp @@ -10,21 +10,16 @@ #include "clang/3C/IntermediateToolHook.h" #include "clang/3C/ArrayBoundsInferenceConsumer.h" +#include "clang/3C/3CGlobalOptions.h" #include "clang/AST/RecursiveASTVisitor.h" using namespace llvm; using namespace clang; -extern cl::OptionCategory ArrBoundsInferCat; -static cl::opt - DisableArrH("disable-arr-hu", - cl::desc("Disable Array Bounds Inference Heuristics."), - cl::init(false), cl::cat(ArrBoundsInferCat)); - void IntermediateToolHook::HandleTranslationUnit(ASTContext &Context) { Info.enterCompilationUnit(Context); Info.getPerfStats().startArrayBoundsInferenceTime(); - handleArrayVariablesBoundsDetection(&Context, Info, !DisableArrH); + handleArrayVariablesBoundsDetection(&Context, Info, !_3COpts.DisableArrH); Info.getPerfStats().endArrayBoundsInferenceTime(); Info.exitCompilationUnit(); } diff --git a/clang/lib/3C/ProgramInfo.cpp b/clang/lib/3C/ProgramInfo.cpp index 3bad8a95a28c..f89d2a5bd4b7 100644 --- a/clang/lib/3C/ProgramInfo.cpp +++ b/clang/lib/3C/ProgramInfo.cpp @@ -237,7 +237,6 @@ void ProgramInfo::printAggregateStats(const std::set &F, void ProgramInfo::printStats(const std::set &F, raw_ostream &O, bool OnlySummary, bool JsonFormat) { if (!OnlySummary && !JsonFormat) { - O << "Enable itype propagation:" << _3COpts.EnablePropThruIType << "\n"; O << "Sound handling of var args functions:" << _3COpts.HandleVARARGS << "\n"; } diff --git a/clang/tools/3c/3CStandalone.cpp b/clang/tools/3c/3CStandalone.cpp index 08a63c4d4979..5a6b7f6573c4 100644 --- a/clang/tools/3c/3CStandalone.cpp +++ b/clang/tools/3c/3CStandalone.cpp @@ -75,15 +75,19 @@ are "#include"-d by that file and it is an error if any other file changes. // Skip the 2 initial newlines. static cl::extrahelp MoreHelp(MoreHelpStr + 2); -static cl::opt OptDumpIntermediate("dump-intermediate", - cl::desc("Dump intermediate " - "information"), - cl::init(false), cl::cat(_3CCategory)); +// Letting clang-format reflow these declarations gives very inconsistent +// formatting between options. +// clang-format off -static cl::opt OptVerbose("verbose", - cl::desc("Print verbose " - "information"), - cl::init(false), cl::cat(_3CCategory)); +static cl::opt OptDumpIntermediate( + "dump-intermediate", + cl::desc("Dump intermediate information"), + cl::init(false), cl::cat(_3CCategory)); + +static cl::opt OptVerbose( + "verbose", + cl::desc("Print verbose information"), + cl::init(false), cl::cat(_3CCategory)); static cl::opt OptOutputPostfix( "output-postfix", @@ -98,82 +102,72 @@ static cl::opt OptOutputDir( "relative paths as the originals under the -base-dir"), cl::init(""), cl::cat(_3CCategory)); -static cl::opt - OptMalloc("use-malloc", - cl::desc("Allows for the usage of user-specified " - "versions of function allocators"), - cl::init(""), cl::cat(_3CCategory)); - -static cl::opt - OptConstraintOutputJson("constraint-output", - cl::desc("Path to the file where all the analysis " - "information will be dumped as json"), - cl::init("constraint_output.json"), - cl::cat(_3CCategory)); - -static cl::opt - OptStatsOutputJson("stats-output", - cl::desc("Path to the file where all the stats " - "will be dumped as json"), - cl::init("TotalConstraintStats.json"), - cl::cat(_3CCategory)); -static cl::opt - OptWildPtrInfoJson("wildptrstats-output", - cl::desc("Path to the file where all the info " - "related to WILD ptr grouped by reason" - " will be dumped as json"), - cl::init("WildPtrStats.json"), cl::cat(_3CCategory)); +static cl::opt OptMalloc( + "use-malloc", + cl::desc("Allows for the usage of user-specified versions of function " + "allocators"), + cl::init(""), cl::cat(_3CCategory)); + +static cl::opt OptConstraintOutputJson( + "constraint-output", + cl::desc("Path to the file where all the analysis information will be " + "dumped as json"), + cl::init("constraint_output.json"), cl::cat(_3CCategory)); + +static cl::opt OptStatsOutputJson( + "stats-output", + cl::desc("Path to the file where all the stats will be dumped as json"), + cl::init("TotalConstraintStats.json"), cl::cat(_3CCategory)); + +static cl::opt OptWildPtrInfoJson( + "wildptrstats-output", + cl::desc("Path to the file where all the info related to WILD ptr grouped " + "by reason will be dumped as json"), + cl::init("WildPtrStats.json"), cl::cat(_3CCategory)); static cl::opt OptPerPtrWILDInfoJson( "perptrstats-output", - cl::desc("Path to the file where all the info " - "related to each WILD ptr will be dumped as json"), + cl::desc("Path to the file where all the info related to each WILD ptr " + "will be dumped as json"), cl::init("PerWildPtrStats.json"), cl::cat(_3CCategory)); -static cl::opt OptDumpStats("dump-stats", cl::desc("Dump statistics"), - cl::init(false), cl::cat(_3CCategory)); - -static cl::opt OptHandleVARARGS("handle-varargs", - cl::desc("Enable handling of varargs " - "in a " - "sound manner"), - cl::init(false), cl::cat(_3CCategory)); - -static cl::opt - OptEnablePropThruIType("enable-itypeprop", - cl::desc("Enable propagation of " - "constraints through ityped " - "parameters/returns."), - cl::init(false), cl::cat(_3CCategory)); - -static cl::opt OptAllTypes("alltypes", - cl::desc("Consider all Checked C types for " - "conversion"), - cl::init(false), cl::cat(_3CCategory)); - -static cl::opt OptAddCheckedRegions("addcr", - cl::desc("Add Checked " - "Regions"), - cl::init(false), - cl::cat(_3CCategory)); +static cl::opt OptDumpStats( + "dump-stats", + cl::desc("Dump statistics"), + cl::init(false), cl::cat(_3CCategory)); + +static cl::opt OptHandleVARARGS( + "handle-varargs", + cl::desc("Enable handling of varargs in a sound manner"), + cl::init(false), cl::cat(_3CCategory)); + +static cl::opt OptAllTypes( + "alltypes", + cl::desc("Consider all Checked C types for conversion"), + cl::init(false), cl::cat(_3CCategory)); + +static cl::opt OptAddCheckedRegions( + "addcr", + cl::desc("Add Checked Regions"), + cl::init(false), cl::cat(_3CCategory)); static cl::opt OptEnableCCTypeChecker( "enccty", cl::desc( - "Enable the Checked C type checker. 3c normally disables it (via the " - "equivalent of `clang -f3c-tool`) so that 3c can operate on partially " - "converted programs that may have Checked C type errors."), + "Enable the Checked C type checker. 3c normally disables it (via the " + "equivalent of `clang -f3c-tool`) so that 3c can operate on partially " + "converted programs that may have Checked C type errors."), cl::init(false), cl::cat(_3CCategory)); static cl::opt OptBaseDir( "base-dir", cl::desc( - "Ancestor directory defining the set of files that 3c " - "is allowed to modify (default: the working " - "directory). All source files specified on the command line must be " - "under this directory. You can use " - "this option to let 3c modify your project's own header files but not " - "those of libraries outside your control."), + "Ancestor directory defining the set of files that 3c " + "is allowed to modify (default: the working " + "directory). All source files specified on the command line must be " + "under this directory. You can use " + "this option to let 3c modify your project's own header files but not " + "those of libraries outside your control."), cl::init(""), cl::cat(_3CCategory)); static cl::opt OptAllowSourcesOutsideBaseDir( @@ -189,11 +183,11 @@ static cl::opt OptWarnRootCause( cl::desc("Emit warnings indicating root causes of unchecked pointers."), cl::init(false), cl::cat(_3CCategory)); -static cl::opt - OptWarnAllRootCause("warn-all-root-cause", - cl::desc("Emit warnings for all root causes, " - "even those unlikely to be interesting."), - cl::init(false), cl::cat(_3CCategory)); +static cl::opt OptWarnAllRootCause( + "warn-all-root-cause", + cl::desc("Emit warnings for all root causes, even those unlikely to be " + "interesting."), + cl::init(false), cl::cat(_3CCategory)); // In the future, we may enhance this to write the output to individual files. // For now, the user has to copy and paste the correct portions of stderr. @@ -229,30 +223,70 @@ static cl::opt OptAllowRewriteFailures( cl::init(false), cl::cat(_3CCategory)); static cl::opt OptItypesForExtern( - "itypes-for-extern", - cl::desc("All functions with external linkage will be rewritten to use itypes" - "instead checked types. This does not apply to static functions which" - "continue to have itypes only when the function is internally" - "unsafe."), - cl::init(false), cl::cat(_3CCategory)); + "itypes-for-extern", + cl::desc("All functions with external linkage will be rewritten to use " + "itypes instead checked types. This does not apply to static " + "functions which continue to have itypes only when the function " + "is internally unsafe."), + cl::init(false), cl::cat(_3CCategory)); static cl::opt OptInferTypesForUndef( - "infer-types-for-undefs", - cl::desc("Enable type inference for undefined functions. Under this flag, " - "types for undefined functions are inferred according to the same " - "rules as defined functions with the caveat that an undefined " - "function will only solve to an itype and not a fully checked type. " - "Because 3c is not able to examine the body of the function, the " - "inferred pointer types (and array bounds) may not be consistent " - "with the actual implementation. By default, the Checked C compiler " - "trusts the declared itypes and will not detect a spatial memory " - "safety violation if the function is used in a way that is " - "consistent with the itypes but not the assumptions actually made " - "by the implementation. Thus, if you want to guarantee spatial " - "memory safety, you must manually check the inferred types against " - "your understanding of what the function actually does (or any " - "available documentation)."), - cl::init(false), cl::cat(_3CCategory)); + "infer-types-for-undefs", + cl::desc("Enable type inference for undefined functions. Under this flag, " + "types for undefined functions are inferred according to the same " + "rules as defined functions with the caveat that an undefined " + "function will only solve to an itype and not a fully checked " + "type. Because 3c is not able to examine the body of the " + "function, the inferred pointer types (and array bounds) may not " + "be consistent with the actual implementation. By default, the " + "Checked C compiler trusts the declared itypes and will not " + "detect a spatial memory safety violation if the function is used " + "in a way that is consistent with the itypes but not the " + "assumptions actually made by the implementation. Thus, if you " + "want to guarantee spatial memory safety, you must manually " + "check the inferred types against your understanding of what the " + "function actually does (or any available documentation)."), + cl::init(false), cl::cat(_3CCategory)); + +static cl::opt OptDebugSolver( + "debug-solver", + cl::desc("Dump intermediate solver state"), + cl::init(false), cl::cat(_3CCategory)); + +static cl::opt OptOnlyGreatestSol( + "only-g-sol", + cl::desc("Perform only greatest solution for Pty Constrains."), + cl::init(false), cl::cat(_3CCategory)); + +static cl::opt OptOnlyLeastSol( + "only-l-sol", + cl::desc("Perform only least solution for Pty Constrains."), + cl::init(false), cl::cat(_3CCategory)); + +static llvm::cl::opt OptDisableRDs( + "disable-rds", + llvm::cl::desc("Disable reverse edges for Checked Constraints."), + llvm::cl::init(false), cl::cat(_3CCategory)); + +static llvm::cl::opt OptDisableFunctionEdges( + "disable-fnedgs", + llvm::cl::desc("Disable reverse edges for external functions."), + llvm::cl::init(false), cl::cat(_3CCategory)); + +static cl::opt OptDisableArrH( + "disable-arr-hu", + cl::desc("Disable Array Bounds Inference Heuristics."), + cl::init(false), cl::cat(_3CCategory)); + +static cl::opt DebugArrSolver( + "debug-arr-solver", + cl::desc("Dump array bounds inference graph"), + cl::init(false), cl::cat(_3CCategory)); + +static cl::opt OptDisableInfDecls( + "disable-arr-missd", + cl::desc("Disable ignoring of missed bounds from declarations."), + cl::init(false), cl::cat(_3CCategory)); #ifdef FIVE_C static cl::opt OptRemoveItypes( @@ -266,6 +300,8 @@ static cl::opt OptForceItypes( cl::init(false), cl::cat(_3CCategory)); #endif +// clang-format on + int main(int argc, const char **argv) { sys::PrintStackTraceOnErrorSignal(argv[0]); @@ -300,7 +336,6 @@ int main(int argc, const char **argv) { struct _3COptions CcOptions; CcOptions.BaseDir = OptBaseDir.getValue(); CcOptions.AllowSourcesOutsideBaseDir = OptAllowSourcesOutsideBaseDir; - CcOptions.EnablePropThruIType = OptEnablePropThruIType; CcOptions.HandleVARARGS = OptHandleVARARGS; CcOptions.DumpStats = OptDumpStats; CcOptions.OutputPostfix = OptOutputPostfix.getValue(); @@ -321,6 +356,14 @@ int main(int argc, const char **argv) { CcOptions.AllowRewriteFailures = OptAllowRewriteFailures; CcOptions.ItypesForExtern = OptItypesForExtern; CcOptions.InferTypesForUndefs = OptInferTypesForUndef; + CcOptions.DebugSolver = OptDebugSolver; + CcOptions.OnlyGreatestSol = OptOnlyGreatestSol; + CcOptions.OnlyLeastSol = OptOnlyLeastSol; + CcOptions.DisableRDs = OptDisableRDs; + CcOptions.DisableFunctionEdges = OptDisableFunctionEdges; + CcOptions.DisableInfDecls = OptDisableInfDecls; + CcOptions.DisableArrH = OptDisableArrH; + CcOptions.DebugArrSolver = DebugArrSolver; #ifdef FIVE_C CcOptions.RemoveItypes = OptRemoveItypes; From abd7a002b626093b3729f9a10fbe12dfa9941d67 Mon Sep 17 00:00:00 2001 From: "Matt McCutchen (Correct Computation)" Date: Mon, 22 Nov 2021 16:50:41 -0500 Subject: [PATCH 36/38] Multi-decl overhaul (including inline struct fixes) (#657) The most important changes: - Clean up the multi-decl detection and rewriting code, and unify the code paths for global variables, fields, and local variables. - Automatically generate names for unnamed inline structs if they need to be rewritten, and remove 3C's previous workarounds that tried to avoid needing to rewrite multi-decls with unnamed inline structs that 3C couldn't handle correctly. - Inside a struct, when rewriting a field multi-decl that contains an inline struct, correctly detect the presence of the inline struct to avoid losing it completely, and additionally move it to the top level to avoid a compiler warning. - Add support for typedef multi-decls on par with variable and field multi-decls. See https://github.com/correctcomputation/checkedc-clang/pull/657 for more details. --- clang/include/clang/3C/ConstraintVariables.h | 14 +- clang/include/clang/3C/Constraints.h | 1 + clang/include/clang/3C/DeclRewriter.h | 49 +- clang/include/clang/3C/MappingVisitor.h | 19 +- clang/include/clang/3C/MultiDecls.h | 216 ++++++++ clang/include/clang/3C/ProgramInfo.h | 9 +- clang/include/clang/3C/RewriteUtils.h | 67 +-- clang/include/clang/3C/StructInit.h | 3 +- clang/include/clang/3C/Utils.h | 24 + clang/lib/3C/3C.cpp | 12 + clang/lib/3C/CMakeLists.txt | 1 + clang/lib/3C/ConstraintBuilder.cpp | 176 +----- clang/lib/3C/ConstraintVariables.cpp | 26 +- clang/lib/3C/DeclRewriter.cpp | 552 +++++++++---------- clang/lib/3C/MappingVisitor.cpp | 49 +- clang/lib/3C/MultiDecls.cpp | 296 ++++++++++ clang/lib/3C/ProgramInfo.cpp | 13 +- clang/lib/3C/RewriteUtils.cpp | 123 +++-- clang/lib/3C/StructInit.cpp | 32 +- clang/lib/3C/Utils.cpp | 48 +- clang/test/3C/basic_checks.c | 3 +- clang/test/3C/inline_anon_structs.c | 245 +++++++- clang/test/3C/inline_anon_structs_cross_tu.c | 69 +++ clang/test/3C/itypes_for_extern.c | 9 +- clang/test/3C/macro_end_of_decl.c | 4 +- clang/test/3C/multivardecls.c | 66 +++ clang/test/3C/multivardecls_complex_types.c | 105 ++++ clang/test/3C/partial_checked.c | 15 + clang/test/3C/qualifiers.c | 4 +- clang/test/3C/root_cause.c | 17 +- 30 files changed, 1531 insertions(+), 736 deletions(-) create mode 100644 clang/include/clang/3C/MultiDecls.h create mode 100644 clang/lib/3C/MultiDecls.cpp create mode 100644 clang/test/3C/inline_anon_structs_cross_tu.c create mode 100644 clang/test/3C/multivardecls_complex_types.c diff --git a/clang/include/clang/3C/ConstraintVariables.h b/clang/include/clang/3C/ConstraintVariables.h index 65b0974decb7..fa1d9f14ef87 100644 --- a/clang/include/clang/3C/ConstraintVariables.h +++ b/clang/include/clang/3C/ConstraintVariables.h @@ -26,6 +26,7 @@ #define LLVM_CLANG_3C_CONSTRAINTVARIABLES_H #include "clang/3C/Constraints.h" +#include "clang/3C/MultiDecls.h" #include "clang/3C/OptionalParams.h" #include "clang/3C/ProgramVar.h" #include "clang/AST/ASTContext.h" @@ -355,16 +356,17 @@ class PointerVariableConstraint : public ConstraintVariable { // declaration's base type. To preserve macros, this we first try to take // the type directly from source code. Where that is not possible, the type // is regenerated from the type in the clang AST. - static std::string extractBaseType(DeclaratorDecl *D, TypeSourceInfo *TSI, - QualType QT, const Type *Ty, - const ASTContext &C); + static std::string extractBaseType(MultiDeclMemberDecl *MMD, + TypeSourceInfo *TSI, QualType QT, + const Type *Ty, const ASTContext &C, + ProgramInfo &Info); // Try to extract string representation of the base type for a declaration // from the source code. If the base type cannot be extracted from source, an // empty string is returned instead. - static std::string tryExtractBaseType(DeclaratorDecl *D, TypeSourceInfo *TSI, - QualType QT, const Type *Ty, - const ASTContext &C); + static std::string tryExtractBaseType(MultiDeclMemberDecl *MMD, + TypeSourceInfo *TSI, QualType QT, + const Type *Ty, const ASTContext &C); // Flag to indicate that this constraint is a part of function prototype // e.g., Parameters or Return. diff --git a/clang/include/clang/3C/Constraints.h b/clang/include/clang/3C/Constraints.h index 85f8274b0445..be46f093284a 100644 --- a/clang/include/clang/3C/Constraints.h +++ b/clang/include/clang/3C/Constraints.h @@ -43,6 +43,7 @@ class ConstraintsGraph; #define SPECIAL_REASON(spec) (std::string("Special case for ") + (spec)) #define STRING_LITERAL_REASON "The type of a string literal" #define MACRO_REASON "Pointer in Macro declaration." +#define UNION_FIELD_REASON "Union field encountered" template struct PComp { bool operator()(const T Lhs, const T Rhs) const { return *Lhs < *Rhs; } diff --git a/clang/include/clang/3C/DeclRewriter.h b/clang/include/clang/3C/DeclRewriter.h index 8ebc9f037f49..ed41d8feb952 100644 --- a/clang/include/clang/3C/DeclRewriter.h +++ b/clang/include/clang/3C/DeclRewriter.h @@ -26,8 +26,8 @@ using namespace clang; class DeclRewriter { public: - DeclRewriter(Rewriter &R, ASTContext &A, GlobalVariableGroups &GP) - : R(R), A(A), GP(GP) {} + DeclRewriter(Rewriter &R, ProgramInfo &Info, ASTContext &A) + : R(R), Info(Info), A(A) {} // The publicly accessible interface for performing declaration rewriting. // All declarations for variables with checked types in the variable map of @@ -40,41 +40,24 @@ class DeclRewriter { ArrayBoundsRewriter &ABR); private: - static RecordDecl *LastRecordDecl; - static std::map VDToRDMap; - static std::set InlineVarDecls; Rewriter &R; + ProgramInfo &Info; ASTContext &A; - GlobalVariableGroups &GP; - // This set contains declarations that have already been rewritten as part of - // a prior declaration that was in the same multi-declaration. It is checked - // before rewriting in order to avoid rewriting a declaration more than once. - // It is not used with individual declarations outside of multi-declarations - // because these declarations are seen exactly once, rather than every time a - // declaration in the containing multi-decl is visited. - std::set VisitedMultiDeclMembers; + // List of TagDecls that were split from multi-decls and should be moved out + // of an enclosing RecordDecl to avoid a compiler warning. Filled during + // multi-decl rewriting and processed by denestTagDecls. + std::vector TagDeclsToDenest; // Visit each Decl in ToRewrite and apply the appropriate pointer type // to that Decl. ToRewrite is the set of all declarations to rewrite. void rewrite(RSet &ToRewrite); - // Rewrite a specific variable declaration using the replacement string in the - // DAndReplace structure. Each of these functions is specialized to handling - // one subclass of declarations. - template - void rewriteFieldOrVarDecl(DRType *N, RSet &ToRewrite); - void rewriteMultiDecl(DeclReplacement *N, RSet &ToRewrite, - std::vector SameLineDecls, - bool ContainsInlineStruct); - void rewriteSingleDecl(DeclReplacement *N, RSet &ToRewrite); + void rewriteMultiDecl(MultiDeclInfo &MDI, RSet &ToRewrite); void doDeclRewrite(SourceRange &SR, DeclReplacement *N); void rewriteFunctionDecl(FunctionDeclReplacement *N); - void rewriteTypedefDecl(TypedefDeclReplacement *TDT, RSet &ToRewrite); - void getDeclsOnSameLine(DeclReplacement *N, std::vector &Decls); - bool isSingleDeclaration(DeclReplacement *N); - SourceRange getNextCommaOrSemicolon(SourceLocation L); - static void detectInlineStruct(Decl *D, SourceManager &SM); + SourceRange getNextComma(SourceLocation L); + void denestTagDecls(); }; // Visits function declarations and adds entries with their new rewritten @@ -119,16 +102,4 @@ class FunctionDeclBuilder : public RecursiveASTVisitor { bool inParamMultiDecl(const ParmVarDecl *PVD); }; - -class FieldFinder : public RecursiveASTVisitor { -public: - FieldFinder(GlobalVariableGroups &GVG) : GVG(GVG) {} - - bool VisitFieldDecl(FieldDecl *FD); - - static void gatherSameLineFields(GlobalVariableGroups &GVG, Decl *D); - -private: - GlobalVariableGroups &GVG; -}; #endif // LLVM_CLANG_3C_DECLREWRITER_H diff --git a/clang/include/clang/3C/MappingVisitor.h b/clang/include/clang/3C/MappingVisitor.h index 71c9e07cf3e9..da6c8dae449f 100644 --- a/clang/include/clang/3C/MappingVisitor.h +++ b/clang/include/clang/3C/MappingVisitor.h @@ -21,37 +21,26 @@ #include "clang/AST/ASTConsumer.h" #include "clang/AST/RecursiveASTVisitor.h" -typedef std::tuple StmtDecl; -typedef std::map SourceToDeclMapType; -typedef std::pair - MappingResultsType; +typedef std::map SourceToDeclMapType; class MappingVisitor : public clang::RecursiveASTVisitor { public: MappingVisitor(std::set S, clang::ASTContext &C) : SourceLocs(S), Context(C) {} - bool VisitDeclStmt(clang::DeclStmt *S); - bool VisitDecl(clang::Decl *D); - MappingResultsType getResults() { - return std::pair, - VariableDecltoStmtMap>(PSLtoSDT, DeclToDeclStmt); - } + const SourceToDeclMapType &getResults() { return PSLtoSDT; } private: - // A map from a PersistentSourceLoc to a tuple describing a statement, decl - // or type. + // A map from a PersistentSourceLoc to a Decl at that location. SourceToDeclMapType PSLtoSDT; // The set of PersistentSourceLoc's this instance of MappingVisitor is tasked - // with re-instantiating as either a Stmt, Decl or Type. + // with re-instantiating as a Decl. std::set SourceLocs; // The ASTContext for the particular AST that the MappingVisitor is // traversing. clang::ASTContext &Context; - // A mapping of individual Decls to the DeclStmt that contains them. - VariableDecltoStmtMap DeclToDeclStmt; }; #endif diff --git a/clang/include/clang/3C/MultiDecls.h b/clang/include/clang/3C/MultiDecls.h new file mode 100644 index 000000000000..b6f1e0eca4f3 --- /dev/null +++ b/clang/include/clang/3C/MultiDecls.h @@ -0,0 +1,216 @@ +//=--MultiDecls.h-------------------------------------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Code to deal with "multi-decls": constructs in which one or more identifiers +// are declared in a comma-separated list based on a single type "on the left". +// A simple example: +// +// struct my_struct x, *p; +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_3C_MULTIDECLS_H +#define LLVM_CLANG_3C_MULTIDECLS_H + +#include "clang/3C/PersistentSourceLoc.h" +#include "clang/AST/Decl.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/AST/Type.h" +#include "llvm/ADT/Optional.h" + +using namespace clang; + +// Some more information about multi-decls in the context of 3C: +// +// The "members" of a given multi-decl may be ordinary variables (VarDecls), +// struct/union fields (FieldDecls), or typedefs (TypedefDecls), but all members +// of a given multi-decl are of the same kind. +// +// If the "left type" of a multi-decl is a TagDecl, it may have an inline +// definition; if it does, then the TagDecl may be unnamed. Examples: +// +// struct my_struct { int *y; } x, *p; +// struct { int *y; } x, *p; +// +// Multi-decls (especially those with inline TagDecls) have historically been +// tricky for 3C to rewrite. If the type of one member becomes a _Ptr (or +// similar), then the left type of the members is no longer the same, so the +// multi-decl must be broken up, for example: +// +// struct my_struct x; +// _Ptr p; +// +// To keep the logic simpler, if 3C needs to change the type of at least one +// member of a multi-decl, it breaks up all members of the multi-decl into +// separate declarations. To preserve SourceLocations as much as possible and +// avoid interfering with rewrites to any other constructs in the multi-decl +// (e.g., within existing initializer expressions), this breakup is performed by +// replacing the commas with semicolons in place and inserting additional +// occurrences of the left type and any common qualifiers as needed. +// +// If there is an inline TagDecl, it is separated too and moved out of any +// containing RecordDecl to avoid a compiler warning, and if the TagDecl is +// unnamed, it is given an automatically generated name so that it can be +// referenced by the new, separate declarations of the multi-decl members. +// Example: +// +// static struct { int *y; } x, *p: +// +// -> +// +// struct x_struct_1 { _Ptr y; }; +// static struct x_struct_1 x; +// static _Ptr p; +// +// Exception: In a typedef multi-decl, if the _first_ member refers to the +// TagDecl itself (not a pointer to it, etc.), then 3C uses that name for the +// TagDecl rather than generating a new one. This produces nicer output for the +// idiom: +// +// typedef struct { int *y; } FOO, *PFOO; +// +// -> +// +// typedef struct { _Ptr y; } FOO; +// typedef _Ptr PFOO; +// +// The multi-decl code is used even for "multi-decls" of VarDecls, FieldDecls, +// or TypedefDecls that have only a single member to avoid having to maintain a +// separate code path for them. But a multi-decl always has at least one member; +// a pure TagDecl such as `struct my_struct { int *y; };` is _not_ considered a +// multi-decl. ParmVarDecls are handled differently. In fact, ParmVarDecls with +// inline TagDecls are known to be handled poorly, but that's a rare and poor +// practice and it's not easy to handle them better. + +// Currently, we automatically generate a name for every unnamed TagDecl defined +// in a multi-decl and use the name in ConstraintVariables, but we only insert +// the name into the definition if the multi-decl gets rewritten for some other +// reason. This solves the common case of allowing the types of all the +// multi-decl members to refer to the TagDecl, but it doesn't address cases in +// which 3C might need to insert a reference to the unnamed TagDecl elsewhere +// even if the multi-decl isn't being rewritten. In these cases, 3C typically +// uses the generated name even though it is not defined, causing a compile +// error that the user has to correct manually. The problematic cases include: +// +// - Type argument insertion. TypeVariableEntry has a check for +// `isTypeAnonymous`, but it has at least one bug (it misses double pointers). +// +// - Cast insertion, potentially. I was unable to find an example, but that +// doesn't mean it will never happen, especially with future changes to the +// code. +// +// - Typedef itype insertion. +// +// One approach to try to rule out all of these bugs at once would be to +// preemptively rewrite all multi-decls containing unnamed TagDecls, but those +// changes might be undesirable or could even cause errors in the presence of +// macros, etc. Or we could try to add the necessary code so that insertion of a +// reference to an unnamed TagDecl would trigger insertion of the name into the +// definition. For now, we don't deal with the problem. + +// Implementation note: The Clang AST does not represent multi-decls explicitly +// (except in functions, where they are represented by DeclStmts). In other +// contexts, we detect them based on the property that the beginning +// SourceLocation of all the members is the same. And as long as we are making +// this assumption, we use it in functions too rather than having a separate +// code path that looks for DeclStmts. + +// NamedDecl is the nearest common superclass of all Decl subtypes that can be +// multi-decl members. There is no enforcement that a MultiDeclMemberDecl is +// actually one of the allowed subtypes, so use of the MultiDeclMemberDecl +// typedef serves as documentation only. (If we wanted to enforce it, we'd need +// a wrapper object of some kind, which currently seems to be more trouble than +// it's worth.) +typedef NamedDecl MultiDeclMemberDecl; + +// Returns D if it can be a multi-decl member, otherwise null. +MultiDeclMemberDecl *getAsMultiDeclMember(Decl *D); + +// Helpers to cope with the different APIs to do corresponding things with a +// TypedefDecl or DeclaratorDecl. +QualType getTypeOfMultiDeclMember(MultiDeclMemberDecl *MMD); +TypeSourceInfo *getTypeSourceInfoOfMultiDeclMember(MultiDeclMemberDecl *MMD); + +struct MultiDeclInfo { + // The TagDecl that is defined inline in the multi-decl and needs to be split + // from it during rewriting, if any, otherwise null. In a case like + // `typedef struct { ... } T`, there is an inline tag definition but we don't + // need to split it out, so this will be null. + TagDecl *TagDefToSplit = nullptr; + + // True if the base type was an unnamed TagDecl defined inline for which we + // are using a new name. Note that TagDefToSplit can be nonnull and + // BaseTypeRenamed can be false if the inline TagDecl was named, and the + // reverse can occur in the `typedef struct { ... } T` case. + bool BaseTypeRenamed = false; + + // The members of the multi-decl in their original order. + std::vector Members; + + // Set by DeclRewriter::rewriteMultiDecl after it rewrites the entire + // multi-decl to ensure that it doesn't try to do so more than once if + // multiple members needed changes. + bool AlreadyRewritten = false; +}; + +struct TUMultiDeclsInfo { + // All multi-decls, keyed by the common beginning source location of their + // members. Note that the beginning source location of TagDefToSplit may be + // later if there is a keyword such as `static` or `typedef` in between. + std::map MultiDeclsByBeginLoc; + + // Map from a tag definition to its containing multi-decl (if it is part of + // one). Note that the TagDefToSplit of the MultiDeclInfo is not guaranteed to + // equal the TagDecl: it may be null in the `typedef struct { ... } T` case. + // + // Note that the MultiDeclInfo pointers remain valid for as long as the + // MultiDeclInfo objects remain in MultiDeclsByBeginLoc: see + // https://en.cppreference.com/w/cpp/container#Iterator_invalidation. + std::map ContainingMultiDeclOfTagDecl; +}; + +class ProgramMultiDeclsInfo { +private: + // Set of TagDecl names already used at least once in the program, so we can + // avoid colliding with them. + std::set UsedTagNames; + + // Information about an originally unnamed tag definition in a multi-decl for + // which we're using a new name. + struct RenamedTagDefInfo { + // The new string that should be used to refer to the type of the TagDecl. + // Unlike UsedTagNames, this includes the tag kind keyword (such as + // `struct`), except when we use an existing typedef (which doesn't require + // a tag keyword). + std::string AssignedTypeStr; + // Whether the TagDecl should be split from the multi-decl. True except when + // we use an existing typedef. + bool ShouldSplit; + }; + + // Map from PSL of a TagDecl to its RenamedTagDefInfo, to ensure that we + // handle the TagDecl consistently when 3C naively rewrites the same header + // file multiple times as part of different translation units (see + // https://github.com/correctcomputation/checkedc-clang/issues/374#issuecomment-804283984). + std::map RenamedTagDefs; + + std::map TUInfos; + + // Recursive helpers. + void findUsedTagNames(DeclContext *DC); + void findMultiDecls(DeclContext *DC, ASTContext &Context); + +public: + void findUsedTagNames(ASTContext &Context); + void findMultiDecls(ASTContext &Context); + llvm::Optional getTypeStrOverride(const Type *Ty, + const ASTContext &C); + MultiDeclInfo *findContainingMultiDecl(MultiDeclMemberDecl *MMD); + MultiDeclInfo *findContainingMultiDecl(TagDecl *TD); + bool wasBaseTypeRenamed(Decl *D); +}; + +#endif // LLVM_CLANG_3C_MULTIDECLS_H diff --git a/clang/include/clang/3C/ProgramInfo.h b/clang/include/clang/3C/ProgramInfo.h index a208458039ab..192fa56885cb 100644 --- a/clang/include/clang/3C/ProgramInfo.h +++ b/clang/include/clang/3C/ProgramInfo.h @@ -16,6 +16,7 @@ #include "clang/3C/3CStats.h" #include "clang/3C/AVarBoundsInfo.h" #include "clang/3C/ConstraintVariables.h" +#include "clang/3C/MultiDecls.h" #include "clang/3C/PersistentSourceLoc.h" #include "clang/3C/Utils.h" #include "clang/AST/ASTConsumer.h" @@ -34,8 +35,8 @@ class ProgramVariableAdder { virtual bool seenTypedef(PersistentSourceLoc PSL) = 0; - virtual void addTypedef(PersistentSourceLoc PSL, bool CanRewriteDef, - TypedefDecl *TD, ASTContext &C) = 0; + virtual void addTypedef(PersistentSourceLoc PSL, TypedefDecl *TD, + ASTContext &C) = 0; protected: virtual AVarBoundsInfo &getABoundsInfo() = 0; @@ -170,7 +171,7 @@ class ProgramInfo : public ProgramVariableAdder { bool seenTypedef(PersistentSourceLoc PSL) override; - void addTypedef(PersistentSourceLoc PSL, bool CanRewriteDef, TypedefDecl *TD, + void addTypedef(PersistentSourceLoc PSL, TypedefDecl *TD, ASTContext &C) override; // Store mapping from ASTContexts to a unique index in the ASTs vector in @@ -179,6 +180,8 @@ class ProgramInfo : public ProgramVariableAdder { void registerTranslationUnits( const std::vector> &ASTs); + ProgramMultiDeclsInfo TheMultiDeclsInfo; + private: // List of constraint variables for declarations, indexed by their location in // the source. This information persists across invocations of the constraint diff --git a/clang/include/clang/3C/RewriteUtils.h b/clang/include/clang/3C/RewriteUtils.h index a571ba27e026..8930327700f2 100644 --- a/clang/include/clang/3C/RewriteUtils.h +++ b/clang/include/clang/3C/RewriteUtils.h @@ -24,18 +24,14 @@ class DeclReplacement { public: virtual Decl *getDecl() const = 0; - DeclStmt *getStatement() const { return Statement; } - std::string getReplacement() const { return Replacement; } virtual SourceRange getSourceRange(SourceManager &SM) const; // Discriminator for LLVM-style RTTI (dyn_cast<> et al.). enum DRKind { - DRK_VarDecl, + DRK_MultiDeclMember, DRK_FunctionDecl, - DRK_FieldDecl, - DRK_TypedefDecl }; DRKind getKind() const { return Kind; } @@ -43,11 +39,7 @@ class DeclReplacement { virtual ~DeclReplacement() {} protected: - explicit DeclReplacement(DeclStmt *S, std::string R, DRKind K) - : Statement(S), Replacement(R), Kind(K) {} - - // The Stmt, if it exists (may be nullptr). - DeclStmt *Statement; + explicit DeclReplacement(std::string R, DRKind K) : Replacement(R), Kind(K) {} // The string to replace the declaration with. std::string Replacement; @@ -59,8 +51,8 @@ class DeclReplacement { template class DeclReplacementTempl : public DeclReplacement { public: - explicit DeclReplacementTempl(DeclT *D, DeclStmt *DS, std::string R) - : DeclReplacement(DS, R, K), Decl(D) {} + explicit DeclReplacementTempl(DeclT *D, std::string R) + : DeclReplacement(R, K), Decl(D) {} DeclT *getDecl() const override { return Decl; } @@ -70,12 +62,8 @@ class DeclReplacementTempl : public DeclReplacement { DeclT *Decl; }; -typedef DeclReplacementTempl - VarDeclReplacement; -typedef DeclReplacementTempl - FieldDeclReplacement; -typedef DeclReplacementTempl - TypedefDeclReplacement; +typedef DeclReplacementTempl + MultiDeclMemberReplacement; class FunctionDeclReplacement : public DeclReplacementTempl RSet; -// This class is used to figure out which global variables are part of -// multi-variable declarations. For local variables, all variables in a single -// multi declaration are grouped together in a DeclStmt object. This is not the -// case for global variables, so this class is required to correctly group -// global variable declarations. Declarations in the same multi-declarations -// have the same beginning source locations, so it is used to group variables. -class GlobalVariableGroups { -public: - GlobalVariableGroups(SourceManager &SourceMgr) : SM(SourceMgr) {} - void addGlobalDecl(Decl *VD, std::vector *VDVec = nullptr); - - std::vector &getVarsOnSameLine(Decl *VD); - - virtual ~GlobalVariableGroups(); - -private: - SourceManager &SM; - std::map *> GlobVarGroups; -}; +// Generate a string for the declaration based on the given PVConstraint. +// Includes the storage qualifier, type, name, and bounds string (as +// applicable), or generates an itype declaration if required due to +// ItypesForExtern. Does not include a trailing semicolon or an initializer, so +// it can be used in combination with getDeclSourceRangeWithAnnotations with +// IncludeInitializer = false to preserve an existing initializer. +std::string mkStringForPVDecl(MultiDeclMemberDecl *MMD, PVConstraint *PVC, + ProgramInfo &Info); + +// Generate a string like mkStringForPVDecl, but for a declaration whose type is +// known not to have changed (except possibly for a base type rename) and that +// may not have a PVConstraint if the type is not a pointer or array type. +// +// For similar reasons as in the comment in DeclRewriter::buildItypeDecl, this +// will get the string from Clang instead of mkString if the base type hasn't +// been renamed (hence the need to assume the rest of the type has not changed). +// Yet another possible approach would be to combine the new base type name with +// the original source for the rest of the declaration, but that may run into +// problems with macros and the like, so we might still need some fallback. For +// now, we don't implement this "original source" approach. +std::string mkStringForDeclWithUnchangedType(MultiDeclMemberDecl *D, + ProgramInfo &Info); // Class that handles rewriting bounds information for all the // detected array variables. diff --git a/clang/include/clang/3C/StructInit.h b/clang/include/clang/3C/StructInit.h index 7bf6974e3244..5ee4cc3c14a0 100644 --- a/clang/include/clang/3C/StructInit.h +++ b/clang/include/clang/3C/StructInit.h @@ -34,11 +34,10 @@ class StructVariableInitializer explicit StructVariableInitializer(ASTContext *C, ProgramInfo &I, RSet &R) : Context(C), I(I), RewriteThese(R), RecordsWithCPointers() {} - bool VisitDeclStmt(DeclStmt *S); + bool VisitVarDecl(VarDecl *VD); private: bool hasCheckedMembers(DeclaratorDecl *DD); - void insertVarDecl(VarDecl *VD, DeclStmt *S); ASTContext *Context; ProgramInfo &I; diff --git a/clang/include/clang/3C/Utils.h b/clang/include/clang/3C/Utils.h index b14041381605..8480a8b543c1 100644 --- a/clang/include/clang/3C/Utils.h +++ b/clang/include/clang/3C/Utils.h @@ -230,6 +230,14 @@ int64_t getStmtIdWorkaround(const clang::Stmt *St, clang::SourceLocation getCheckedCAnnotationsEnd(const clang::Decl *D); +// Get the source range for a declaration including Checked C annotations. +// Optionally, any initializer can be excluded from the range in order to avoid +// interfering with other rewrites inside an existing initializer +// (https://github.com/correctcomputation/checkedc-clang/issues/267). If the +// declaration has no initializer, then IncludeInitializer has no effect. +clang::SourceRange getDeclSourceRangeWithAnnotations(const clang::Decl *D, + bool IncludeInitializer); + // Shortcut for the getCustomDiagID + Report sequence to report a custom // diagnostic as we currently do in 3C. // @@ -257,4 +265,20 @@ inline const clang::DiagnosticBuilder &operator<<( return DB; } +// Marker for conditions that we might want to make into non-fatal assertions +// once we have an API design for them +// (https://github.com/correctcomputation/checkedc-clang/issues/745). An inline +// function would work just as well, but macros have an LLVM naming convention +// and syntax highlighting that make call sites easier to read, in Matt's +// opinion. +#define NONFATAL_ASSERT_PLACEHOLDER(_cond) (_cond) + +// Variant for conditions that the caller doesn't actually test because no +// separate recovery path is currently implemented. We want to check that the +// condition compiles but not evaluate it at runtime (until non-fatal assertions +// are actually implemented, and then only when assertions are enabled in the +// build configuration), and we don't want "unused code" compiler warnings. +// TODO: Is there a better way to achieve this? +#define NONFATAL_ASSERT_PLACEHOLDER_UNUSED(_cond) ((void)sizeof(_cond)) + #endif diff --git a/clang/lib/3C/3C.cpp b/clang/lib/3C/3C.cpp index 7eebc2edd660..267082ae0db8 100644 --- a/clang/lib/3C/3C.cpp +++ b/clang/lib/3C/3C.cpp @@ -516,6 +516,18 @@ bool _3CInterface::addVariables() { std::lock_guard Lock(InterfaceMutex); + // Find multi-decls and assign names to unnamed inline TagDecls now so that + // the assigned type names are available when we construct ConstraintVariables + // for the multi-decl members in the "Add Variables" step below. + for (auto &TU : ASTs) + GlobalProgramInfo.TheMultiDeclsInfo.findUsedTagNames(TU->getASTContext()); + if (!isSuccessfulSoFar()) + return false; + for (auto &TU : ASTs) + GlobalProgramInfo.TheMultiDeclsInfo.findMultiDecls(TU->getASTContext()); + if (!isSuccessfulSoFar()) + return false; + // 1. Add Variables. VariableAdderConsumer VA = VariableAdderConsumer(GlobalProgramInfo, nullptr); for (auto &TU : ASTs) diff --git a/clang/lib/3C/CMakeLists.txt b/clang/lib/3C/CMakeLists.txt index 91733f687ba9..7a57f8e93ff7 100644 --- a/clang/lib/3C/CMakeLists.txt +++ b/clang/lib/3C/CMakeLists.txt @@ -44,6 +44,7 @@ add_clang_library(clang3C DeclRewriter.cpp IntermediateToolHook.cpp MappingVisitor.cpp + MultiDecls.cpp PersistentSourceLoc.cpp ProgramInfo.cpp ProgramVar.cpp diff --git a/clang/lib/3C/ConstraintBuilder.cpp b/clang/lib/3C/ConstraintBuilder.cpp index 37442ca12546..902acb400c8a 100644 --- a/clang/lib/3C/ConstraintBuilder.cpp +++ b/clang/lib/3C/ConstraintBuilder.cpp @@ -21,106 +21,6 @@ using namespace llvm; using namespace clang; -// This class is intended to locate inline struct definitions -// in order to mark them wild or signal a warning as appropriate. -class InlineStructDetector { -public: - explicit InlineStructDetector() : LastRecordDecl(nullptr) {} - - void processRecordDecl(RecordDecl *Declaration, ProgramInfo &Info, - ASTContext *Context, ConstraintResolver CB) { - LastRecordDecl = Declaration; - if (RecordDecl *Definition = Declaration->getDefinition()) { - auto LastRecordLocation = Definition->getBeginLoc(); - FullSourceLoc FL = Context->getFullLoc(Definition->getBeginLoc()); - if (FL.isValid()) { - SourceManager &SM = Context->getSourceManager(); - FileID FID = FL.getFileID(); - const FileEntry *FE = SM.getFileEntryForID(FID); - - // Detect whether this RecordDecl is part of an inline struct. - bool IsInLineStruct = false; - Decl *D = Declaration->getNextDeclInContext(); - if (VarDecl *VD = dyn_cast_or_null(D)) { - auto VarTy = VD->getType(); - auto BeginLoc = VD->getBeginLoc(); - auto EndLoc = VD->getEndLoc(); - SourceManager &SM = Context->getSourceManager(); - IsInLineStruct = - !isPtrOrArrayType(VarTy) && !VD->hasInit() && - SM.isPointWithin(LastRecordLocation, BeginLoc, EndLoc); - } - - if (FE && FE->isValid()) { - // We only want to re-write a record if it contains - // any pointer types, to include array types. - for (const auto &F : Definition->fields()) { - auto FieldTy = F->getType(); - // If the RecordDecl is a union or in a system header - // and this field is a pointer, we need to mark it wild. - bool FieldInUnionOrSysHeader = - (FL.isInSystemHeader() || Definition->isUnion()); - // Mark field wild if the above is true and the field is a pointer. - if (isPtrOrArrayType(FieldTy) && - (FieldInUnionOrSysHeader || IsInLineStruct)) { - std::string Rsn = "Union or external struct field encountered"; - CVarOption CV = Info.getVariable(F, Context); - CB.constraintCVarToWild(CV, Rsn); - } - } - } - } - } - } - - void processVarDecl(VarDecl *VD, ProgramInfo &Info, ASTContext *Context, - ConstraintResolver CB) { - // If the last seen RecordDecl is non-null and coincides with the current - // VarDecl (i.e. via an inline struct), we proceed as follows: - // If the struct is named, do nothing. - // If the struct is anonymous: - // When alltypes is on, do nothing, but signal a warning to - // the user indicating its presence. - // When alltypes is off, mark the VarDecl WILD in order to - // ensure the converted program compiles. - if (LastRecordDecl != nullptr) { - auto LastRecordLocation = LastRecordDecl->getBeginLoc(); - auto BeginLoc = VD->getBeginLoc(); - auto EndLoc = VD->getEndLoc(); - auto VarTy = VD->getType(); - SourceManager &SM = Context->getSourceManager(); - bool IsInLineStruct = - SM.isPointWithin(LastRecordLocation, BeginLoc, EndLoc) && - isPtrOrArrayType(VarTy); - bool IsNamedInLineStruct = - IsInLineStruct && LastRecordDecl->getNameAsString() != ""; - if (IsInLineStruct && !IsNamedInLineStruct) { - if (!_3COpts.AllTypes) { - CVarOption CV = Info.getVariable(VD, Context); - CB.constraintCVarToWild(CV, "Inline struct encountered."); - } else { - reportCustomDiagnostic(Context->getDiagnostics(), - DiagnosticsEngine::Warning, - "\n Rewriting failed" - "for %q0 because an inline " - "or anonymous struct instance " - "was detected.\n Consider manually " - "rewriting by inserting the struct " - "definition inside the _Ptr " - "annotation.\n " - "EX. struct {int *a; int *b;} x; " - "_Ptr b;}>;", - VD->getLocation()) - << VD; - } - } - } - } - -private: - RecordDecl *LastRecordDecl; -}; - // This class visits functions and adds constraints to the // Constraints instance assigned to it. // Each VisitXXX method is responsible for looking inside statements @@ -128,27 +28,10 @@ class InlineStructDetector { class FunctionVisitor : public RecursiveASTVisitor { public: explicit FunctionVisitor(ASTContext *C, ProgramInfo &I, FunctionDecl *FD) - : Context(C), Info(I), Function(FD), CB(Info, Context), - ISD() {} + : Context(C), Info(I), Function(FD), CB(Info, Context) {} // T x = e bool VisitDeclStmt(DeclStmt *S) { - // Introduce variables as needed. - for (const auto &D : S->decls()) { - if (RecordDecl *RD = dyn_cast(D)) { - ISD.processRecordDecl(RD, Info, Context, CB); - } - if (VarDecl *VD = dyn_cast(D)) { - if (VD->isLocalVarDecl()) { - FullSourceLoc FL = Context->getFullLoc(VD->getBeginLoc()); - SourceRange SR = VD->getSourceRange(); - if (SR.isValid() && FL.isValid() && isPtrOrArrayType(VD->getType())) { - ISD.processVarDecl(VD, Info, Context, CB); - } - } - } - } - // Process inits even for non-pointers because structs and union values // can contain pointers for (const auto &D : S->decls()) { @@ -442,48 +325,6 @@ class FunctionVisitor : public RecursiveASTVisitor { ProgramInfo &Info; FunctionDecl *Function; ConstraintResolver CB; - InlineStructDetector ISD; -}; - -class PtrToStructDef : public RecursiveASTVisitor { -public: - explicit PtrToStructDef(TypedefDecl *TDT) : TDT(TDT) {} - - bool VisitPointerType(clang::PointerType *PT) { - IsPointer = true; - return true; - } - - bool VisitRecordType(RecordType *RT) { - auto *Decl = RT->getDecl(); - auto DeclRange = Decl->getSourceRange(); - auto TypedefRange = TDT->getSourceRange(); - bool DeclContained = (TypedefRange.getBegin() < DeclRange.getBegin()) && - !(TypedefRange.getEnd() < DeclRange.getEnd()); - if (DeclContained) { - StructDefInTD = true; - return false; - } - return true; - } - - bool VisitFunctionProtoType(FunctionProtoType *FPT) { - IsPointer = true; - return true; - } - - bool getResult(void) { return StructDefInTD; } - - static bool containsPtrToStructDef(TypedefDecl *TDT) { - PtrToStructDef Traverser(TDT); - Traverser.TraverseDecl(TDT); - return Traverser.getResult(); - } - -private: - TypedefDecl *TDT = nullptr; - bool IsPointer = false; - bool StructDefInTD = false; }; // This class visits a global declaration, generating constraints @@ -491,7 +332,7 @@ class PtrToStructDef : public RecursiveASTVisitor { class ConstraintGenVisitor : public RecursiveASTVisitor { public: explicit ConstraintGenVisitor(ASTContext *Context, ProgramInfo &I) - : Context(Context), Info(I), CB(Info, Context), ISD() {} + : Context(Context), Info(I), CB(Info, Context) {} bool VisitVarDecl(VarDecl *G) { @@ -499,7 +340,6 @@ class ConstraintGenVisitor : public RecursiveASTVisitor { if (G->hasInit()) { CB.constrainLocalAssign(nullptr, G, G->getInit(), Same_to_Same); } - ISD.processVarDecl(G, Info, Context, CB); } return true; } @@ -546,16 +386,10 @@ class ConstraintGenVisitor : public RecursiveASTVisitor { return true; } - bool VisitRecordDecl(RecordDecl *Declaration) { - ISD.processRecordDecl(Declaration, Info, Context, CB); - return true; - } - private: ASTContext *Context; ProgramInfo &Info; ConstraintResolver CB; - InlineStructDetector ISD; }; // Some information about variables in the program is required by the type @@ -579,10 +413,8 @@ class VariableAdderVisitor : public RecursiveASTVisitor { // typedef map. If we have seen it before, and we need to preserve the // constraints contained within it if (!VarAdder.seenTypedef(PSL)) - // Add this typedef to the program info, if it contains a ptr to - // an anonymous struct we mark as not being rewritable - VarAdder.addTypedef(PSL, !PtrToStructDef::containsPtrToStructDef(TD), TD, - *Context); + // Add this typedef to the program info. + VarAdder.addTypedef(PSL, TD, *Context); return true; } diff --git a/clang/lib/3C/ConstraintVariables.cpp b/clang/lib/3C/ConstraintVariables.cpp index da065bfe0c0b..c27f0df8076c 100644 --- a/clang/lib/3C/ConstraintVariables.cpp +++ b/clang/lib/3C/ConstraintVariables.cpp @@ -503,7 +503,7 @@ PointerVariableConstraint::PointerVariableConstraint( TSInfo); // Get a string representing the type without pointer and array indirection. - BaseType = extractBaseType(D, TSInfo, QT, Ty, C); + BaseType = extractBaseType(D, TSInfo, QT, Ty, C, I); // check if the type is some depth of pointers to void // TODO: is this what the field should mean? do we want to include other @@ -538,11 +538,9 @@ PointerVariableConstraint::PointerVariableConstraint( } } -std::string PointerVariableConstraint::tryExtractBaseType(DeclaratorDecl *D, - TypeSourceInfo *TSI, - QualType QT, - const Type *Ty, - const ASTContext &C) { +std::string PointerVariableConstraint::tryExtractBaseType( + MultiDeclMemberDecl *D, TypeSourceInfo *TSI, QualType QT, const Type *Ty, + const ASTContext &C) { // Implicit parameters declarations from typedef function declarations will // still have valid and non-empty source ranges, but implicit declarations // aren't written in the source, so extracting the base type from this range @@ -553,7 +551,7 @@ std::string PointerVariableConstraint::tryExtractBaseType(DeclaratorDecl *D, return ""; if (!TSI) - TSI = D->getTypeSourceInfo(); + TSI = getTypeSourceInfoOfMultiDeclMember(D); if (!QT->isOrContainsCheckedType() && !Ty->getAs() && TSI) { // Try to extract the type from original source to preserve defines TypeLoc TL = TSI->getTypeLoc(); @@ -566,7 +564,7 @@ std::string PointerVariableConstraint::tryExtractBaseType(DeclaratorDecl *D, return ""; TL = TL.getAs().getReturnLoc(); } else { - FoundBaseTypeInSrc = D->getType() == QT; + FoundBaseTypeInSrc = getTypeOfMultiDeclMember(D) == QT; } if (!TL.isNull()) { TypeLoc BaseLoc = getBaseTypeLoc(TL); @@ -583,11 +581,13 @@ std::string PointerVariableConstraint::tryExtractBaseType(DeclaratorDecl *D, return ""; } -std::string PointerVariableConstraint::extractBaseType(DeclaratorDecl *D, - TypeSourceInfo *TSI, - QualType QT, - const Type *Ty, - const ASTContext &C) { +std::string PointerVariableConstraint::extractBaseType( + MultiDeclMemberDecl *D, TypeSourceInfo *TSI, QualType QT, const Type *Ty, + const ASTContext &C, ProgramInfo &Info) { + if (llvm::Optional TypeStrOverride = + Info.TheMultiDeclsInfo.getTypeStrOverride(Ty, C)) + return *TypeStrOverride; + std::string BaseTypeStr = tryExtractBaseType(D, TSI, QT, Ty, C); // Fall back to rebuilding the base type based on type passed to constructor if (BaseTypeStr.empty()) diff --git a/clang/lib/3C/DeclRewriter.cpp b/clang/lib/3C/DeclRewriter.cpp index 72ae4289df83..07257ca1dd14 100644 --- a/clang/lib/3C/DeclRewriter.cpp +++ b/clang/lib/3C/DeclRewriter.cpp @@ -52,23 +52,35 @@ void DeclRewriter::buildItypeDecl(PVConstraint *Defn, DeclaratorDecl *Decl, // of IsUncheckedTypedef because both require the variable type be defined // by a typedef. The checked typedef is expanded using unchecked types in the // unchecked portion of the itype. The typedef is used directly in the checked - // portion of the itype. + // portion of the itype. TODO: Maybe we shouldn't do that if the solution for + // the typedef doesn't fully equal the solution for the variable + // (https://github.com/correctcomputation/checkedc-clang/issues/705)? bool IsCheckedTypedef = Defn->isTypedef() && !IsUncheckedTypedef; + bool BaseTypeRenamed = + Decl && Info.TheMultiDeclsInfo.wasBaseTypeRenamed(Decl); + // It should in principle be possible to always generate the unchecked portion - // of the itype by going through mkString. For practical reason, this doesn't - // always work, so we instead use the original type string as generated by - // clang so that we emit valid syntax in more cases. For examples and - // discussion refer to issue correctcomputation/checkedc-clang#703. - if (IsCheckedTypedef || Defn->getFV()) { - // Generate the type string from PVC if we need to unmask a typedef, this is - // a function pointer, or this is a constant size array. When unmasking a - // typedef, the expansion of the typedef does not exist in the original - // source, so it must be constructed. For function pointers, a function - // pointer appearing in the unchecked portion of an itype must contain an - // extra set of parenthesis (e.g. `void ((*f)())` instead of `void (f*)()`) - // for the declaration to parse correctly. qtyToStr (which is used by - // getOriginalTypeWithName) does not support adding these parentheses. + // of the itype by going through mkString. However, mkString has bugs that + // lead to incorrect output in some less common cases + // (https://github.com/correctcomputation/checkedc-clang/issues/703). So we + // use the original type string generated by Clang (via qtyToStr or + // getOriginalTypeWithName) unless we know we have a special requirement that + // it doesn't meet, in which case we use mkString. Those cases are: + // - Unmasking a typedef. The expansion of the typedef does not exist in the + // original source, so it must be constructed. (TODO: Couldn't we just get + // the underlying type with TypedefDecl::getUnderlyingType and then use + // qtyToStr?) + // - A function pointer. For a function pointer with an itype to parse + // correctly, it needs an extra set of parentheses (e.g., + // `void ((*f)()) : itype(...)` instead of `void (*f)() : itype(...)`), and + // Clang won't know to add them. + // - When the base type is an unnamed TagDecl that 3C has renamed, Clang won't + // know the new name. + // - Possible future change: if the internal PVConstraint is partially checked + // and we want to use it + // (https://github.com/correctcomputation/checkedc-clang/issues/704). + if (IsCheckedTypedef || Defn->getFV() || BaseTypeRenamed) { Type = Defn->mkString(Info.getConstraints(), MKSTRING_OPTS(UnmaskTypedef = IsCheckedTypedef, ForItypeBase = true)); @@ -132,8 +144,8 @@ void DeclRewriter::rewriteDecls(ASTContext &Context, ProgramInfo &Info, getStorageQualifierString(D) + Var.mkString(Info.getConstraints(), MKSTRING_OPTS(UnmaskTypedef = true)); - RewriteThese.insert(std::make_pair( - TD, new TypedefDeclReplacement(TD, nullptr, NewTy))); + RewriteThese.insert( + std::make_pair(TD, new MultiDeclMemberReplacement(TD, NewTy))); } } } @@ -147,21 +159,11 @@ void DeclRewriter::rewriteDecls(ASTContext &Context, ProgramInfo &Info, for (const auto &I : Info.getVarMap()) Keys.insert(I.first); MappingVisitor MV(Keys, Context); - LastRecordDecl = nullptr; for (const auto &D : TUD->decls()) { MV.TraverseDecl(D); - detectInlineStruct(D, Context.getSourceManager()); - if (FunctionDecl *FD = dyn_cast(D)) { - if (FD->hasBody() && FD->isThisDeclarationADefinition()) { - for (auto &D : FD->decls()) { - detectInlineStruct(D, Context.getSourceManager()); - } - } - } } SourceToDeclMapType PSLMap; - VariableDecltoStmtMap VDLToStmtMap; - std::tie(PSLMap, VDLToStmtMap) = MV.getResults(); + PSLMap = MV.getResults(); // Add declarations from this map into the rewriting set for (const auto &V : Info.getVarMap()) { @@ -173,7 +175,7 @@ void DeclRewriter::rewriteDecls(ASTContext &Context, ProgramInfo &Info, // the variable name, not the typedef or #define that creates the // name of the type. PersistentSourceLoc PLoc = V.first; - if (Decl *D = std::get<1>(PSLMap[PLoc])) { + if (Decl *D = PSLMap[PLoc]) { ConstraintVariable *CV = V.second; PVConstraint *PV = dyn_cast(CV); bool PVChanged = @@ -184,60 +186,23 @@ void DeclRewriter::rewriteDecls(ASTContext &Context, ProgramInfo &Info, assert(!isa(D) && "Got a PVConstraint for a ParmVarDecl where " "isPartOfFunctionPrototype returns false?"); - DeclStmt *DS = nullptr; - if (VDLToStmtMap.find(D) != VDLToStmtMap.end()) - DS = VDLToStmtMap[D]; - - std::string NewTy = getStorageQualifierString(D); - bool IsExternGlobalVar = - isa(D) && - cast(D)->getFormalLinkage() == Linkage::ExternalLinkage; - if (_3COpts.ItypesForExtern && - (isa(D) || IsExternGlobalVar)) { - // Give record fields and global variables itypes when using - // -itypes-for-extern. Note that we haven't properly implemented - // itypes for structures and globals. This just rewrites to an itype - // instead of a fully checked type when a checked type could have been - // used. This does provide most of the rewriting infrastructure that - // would be required to support these itypes if constraint generation - // is updated to handle structure/global itypes. - std::string Type, IType; - // VarDecl and FieldDecl subclass DeclaratorDecl, so the cast will - // always succeed. - DeclRewriter::buildItypeDecl(PV, cast(D), Type, IType, - Info, ABRewriter); - NewTy += Type + IType; - } else { - NewTy += PV->mkString(Info.getConstraints()) + - ABRewriter.getBoundsString(PV, D); - } - if (auto *VD = dyn_cast(D)) - RewriteThese.insert( - std::make_pair(VD, new VarDeclReplacement(VD, DS, NewTy))); - else if (auto *FD = dyn_cast(D)) - RewriteThese.insert( - std::make_pair(FD, new FieldDeclReplacement(FD, DS, NewTy))); - else - llvm_unreachable("Unrecognized declaration type."); + MultiDeclMemberDecl *MMD = getAsMultiDeclMember(D); + assert(MMD && "Unrecognized declaration type."); + std::string ReplacementText = mkStringForPVDecl(MMD, PV, Info); + RewriteThese.insert(std::make_pair( + MMD, new MultiDeclMemberReplacement(MMD, ReplacementText))); } } } - // Build sets of variables that are declared in the same statement so we can - // rewrite things like int x, *y, **z; - GlobalVariableGroups GVG(R.getSourceMgr()); - for (const auto &D : TUD->decls()) { - GVG.addGlobalDecl(dyn_cast(D)); - //Search through the AST for fields that occur on the same line - FieldFinder::gatherSameLineFields(GVG, D); - } - // Do the declaration rewriting - DeclRewriter DeclR(R, Context, GVG); + DeclRewriter DeclR(R, Info, Context); DeclR.rewrite(RewriteThese); for (auto Pair : RewriteThese) delete Pair.second; + + DeclR.denestTagDecls(); } void DeclRewriter::rewrite(RSet &ToRewrite) { @@ -252,181 +217,246 @@ void DeclRewriter::rewrite(RSet &ToRewrite) { } // Exact rewriting procedure depends on declaration type - if (auto *VR = dyn_cast(N)) { - rewriteFieldOrVarDecl(VR, ToRewrite); + if (auto *MR = dyn_cast(N)) { + MultiDeclInfo *MDI = + Info.TheMultiDeclsInfo.findContainingMultiDecl(MR->getDecl()); + assert("Missing MultiDeclInfo for multi-decl member" && MDI); + // A multi-decl can only be rewritten as a unit. If at least one member + // needs rewriting, then the first MultiDeclMemberReplacement in iteration + // order of ToRewrite (which need not have anything to do with member + // order of the multi-decl) triggers rewriting of the entire multi-decl, + // and rewriteMultiDecl checks ToRewrite for a MultiDeclMemberReplacement + // for each member of the multi-decl and applies it if found. + if (!MDI->AlreadyRewritten) + rewriteMultiDecl(*MDI, ToRewrite); } else if (auto *FR = dyn_cast(N)) { rewriteFunctionDecl(FR); - } else if (auto *FdR = dyn_cast(N)) { - rewriteFieldOrVarDecl(FdR, ToRewrite); - } else if (auto *TDR = dyn_cast(N)) { - rewriteTypedefDecl(TDR, ToRewrite); } else { assert(false && "Unknown replacement type"); } } } -void DeclRewriter::rewriteTypedefDecl(TypedefDeclReplacement *TDR, - RSet &ToRewrite) { - rewriteSingleDecl(TDR, ToRewrite); -} - -// In alltypes mode we need to handle inline structs inside functions specially. -// Because both the recorddecl and vardecl are inside one DeclStmt, the -// SourceLocations will be generated incorrectly if we rewrite it as a -// normal multidecl. -bool isInlineStruct(std::vector &InlineDecls) { - if (InlineDecls.size() >= 2 && _3COpts.AllTypes) - return isa(InlineDecls[0]) && - std::all_of(InlineDecls.begin() + 1, InlineDecls.end(), - [](Decl *D) { return isa(D); }); - return false; -} - -template -void DeclRewriter::rewriteFieldOrVarDecl(DRType *N, RSet &ToRewrite) { - static_assert(std::is_same::value || - std::is_same::value, - "Method expects variable or field declaration replacement."); - - bool IsVisitedMultiDeclMember = (VisitedMultiDeclMembers.find(N->getDecl()) != - VisitedMultiDeclMembers.end()); - if (InlineVarDecls.find(N->getDecl()) != InlineVarDecls.end() && - !IsVisitedMultiDeclMember) { - std::vector SameLineDecls; - getDeclsOnSameLine(N, SameLineDecls); - if (std::find(SameLineDecls.begin(), SameLineDecls.end(), - VDToRDMap[N->getDecl()]) == SameLineDecls.end()) - SameLineDecls.insert(SameLineDecls.begin(), VDToRDMap[N->getDecl()]); - rewriteMultiDecl(N, ToRewrite, SameLineDecls, true); - } else if (isSingleDeclaration(N)) { - rewriteSingleDecl(N, ToRewrite); - } else if (!IsVisitedMultiDeclMember) { - std::vector SameLineDecls; - getDeclsOnSameLine(N, SameLineDecls); - if (isInlineStruct(SameLineDecls)) - SameLineDecls.erase(SameLineDecls.begin()); - rewriteMultiDecl(N, ToRewrite, SameLineDecls, false); - } else { - // Anything that reaches this case should be a multi-declaration that has - // already been rewritten. - assert("Declaration should have been rewritten." && - !isSingleDeclaration(N) && IsVisitedMultiDeclMember); +void DeclRewriter::denestTagDecls() { + // When there are multiple levels of nested TagDecls, we need to process + // all the children of a TagDecl TD before TD itself so that (1) the + // definitions of the children end up before the definition of TD (since the + // rewriter preserves order of insertions) and (2) the definitions of the + // children have been removed from the body of TD before we read the body of + // TD to move it. In effect, we want to process the TagDecls in postorder. + // The easiest way to achieve this is to process them in order of their _end_ + // locations. + std::sort(TagDeclsToDenest.begin(), TagDeclsToDenest.end(), + [&](TagDecl *TD1, TagDecl *TD2) { + return A.getSourceManager().isBeforeInTranslationUnit( + TD1->getEndLoc(), TD2->getEndLoc()); + }); + for (TagDecl *TD : TagDeclsToDenest) { + // rewriteMultiDecl replaced the final "}" in the original source range with + // "};\n", so the new content of the source range should include the ";\n", + // which is what we want here. Except the rewriter has a bug where it + // adjusts the token range to include the final token _after_ mapping the + // offset to account for previous edits (it should be before). We work + // around the bug by adjusting the token range before calling the rewriter + // at all. + CharSourceRange CSR = Lexer::makeFileCharRange( + CharSourceRange::getTokenRange(TD->getSourceRange()), R.getSourceMgr(), + R.getLangOpts()); + std::string DefinitionStr = R.getRewrittenText(CSR); + // Delete the definition from the old location. + rewriteSourceRange(R, CSR, ""); + // We want to find the highest ancestor DeclContext of TD that is a TagDecl + // (call it TopTagDecl) and insert TD just before TopTagDecl. + // + // As of this writing, it seems that if TD is named, `TD->getDeclContext()` + // returns the parent of TopTagDecl due to the code at + // https://github.com/correctcomputation/checkedc-clang/blob/fd4d8af4383d40af10ee8bc92b7bf88061a11035/clang/lib/Sema/SemaDecl.cpp#L16980-L16981, + // But that code doesn't run if TD is unnamed (which makes some sense + // because name visibility isn't an issue for TagDecls that have no name), + // and we want to de-nest TagDecls with names we assigned just like ones + // that were originally named, so we can't just use `TD->getDeclContext()`. + // In any event, maybe we wouldn't want to rely on this kind of internal + // Clang behavior. + TagDecl *TopTagDecl = TD; + while (TagDecl *ParentTagDecl = + dyn_cast(TopTagDecl->getLexicalDeclContext())) + TopTagDecl = ParentTagDecl; + // If TopTagDecl is preceded by qualifiers, ideally we'd like to insert TD + // before those qualifiers. If TopTagDecl is actually part of a multi-decl + // with at least one member, then we can just use the begin location of that + // multi-decl as the insertion point. + // + // If there are no members (so the qualifiers have no effect and generate a + // compiler warning), then there isn't an easy way for us to find the source + // location before the qualifiers. In that case, we just insert TD at the + // begin location of TopTagDecl (after the qualifiers) and hope for the + // best. In the cases we're aware of so far (storage qualifiers, including + // `typedef`), this just means that the qualifiers end up applying to the + // first TagDecl de-nested from TopTagDecl instead of to TopTagDecl itself, + // and they generate the same compiler warning as before but on a different + // TagDecl. However, we haven't confirmed that there aren't any obscure + // cases that could lead to an error, such as if a qualifier is valid on one + // kind of TagDecl but not another. + SourceLocation InsertLoc; + if (MultiDeclInfo *MDI = + Info.TheMultiDeclsInfo.findContainingMultiDecl(TopTagDecl)) + InsertLoc = MDI->Members[0]->getBeginLoc(); + else + InsertLoc = TopTagDecl->getBeginLoc(); + // TODO: Use a wrapper like rewriteSourceRange that tries harder with + // macros, reports failure, etc. + // (https://github.com/correctcomputation/checkedc-clang/issues/739) + R.InsertText(InsertLoc, DefinitionStr); } } -void DeclRewriter::rewriteSingleDecl(DeclReplacement *N, RSet &ToRewrite) { - bool IsSingleDecl = - dyn_cast(N->getDecl()) || isSingleDeclaration(N); - assert("Declaration is not a single declaration." && IsSingleDecl); - // This is the easy case, we can rewrite it locally, at the declaration. - SourceManager &SM = N->getDecl()->getASTContext().getSourceManager(); - SourceRange TR = N->getSourceRange(SM); - doDeclRewrite(TR, N); -} - -void DeclRewriter::rewriteMultiDecl(DeclReplacement *N, RSet &ToRewrite, - std::vector SameLineDecls, - bool ContainsInlineStruct) { - // Rewriting is more difficult when there are multiple variables declared in a - // single statement. When this happens, we need to find all the declaration - // replacement for this statement and apply them at the same time. We also - // need to avoid rewriting any of these declarations twice by updating the - // Skip set to include the processed declarations. - - // For each decl in the original, build up a new string. If the - // original decl was re-written, write that out instead. Existing - // initializers are preserved, any declarations that an initializer to - // be valid checked-c are given one. +void DeclRewriter::rewriteMultiDecl(MultiDeclInfo &MDI, RSet &ToRewrite) { + // Rewrite a "multi-decl" consisting of one or more variables, fields, or + // typedefs declared in a comma-separated list based on a single type "on the + // left". See the comment at the top of clang/include/clang/3C/MultiDecls.h + // for a detailed description of the design that is implemented here. As + // mentioned in MultiDecls.h, this code is used even for "multi-decls" that + // have only a single member to avoid having to maintain a separate code path + // for them. + // + // Due to the overlap between members, a multi-decl can only be rewritten as a + // unit, visiting the members in source code order from left to right. For + // each member, we check whether it has a replacement in ToRewrite. If so, we + // use it; if not, we generate a declaration equivalent to the original. + // Existing initializers are preserved, and declarations that need an + // initializer to be valid Checked C are given one. + SourceManager &SM = A.getSourceManager(); bool IsFirst = true; SourceLocation PrevEnd; - for (const auto &DL : SameLineDecls) { - std::string ReplaceText = ";\n"; - // Find the declaration replacement object for the current declaration. - DeclReplacement *SameLineReplacement; - bool Found = false; - auto It = ToRewrite.find(DL); - if (It != ToRewrite.end()) { - SameLineReplacement = It->second; - Found = true; - VisitedMultiDeclMembers.insert(DL); + + if (MDI.TagDefToSplit != nullptr) { + TagDecl *TD = MDI.TagDefToSplit; + // `static struct T { ... } x;` -> `struct T { ... }; static struct T x;` + // A storage qualifier such as `static` applies to the members but is not + // meaningful on TD after it is split, and we need to remove it to avoid a + // compiler warning. The beginning location of the first member should be + // the `static` and the beginning location of TD should be the `struct`, so + // we just remove anything between those locations. (Can other things appear + // there? We hope it makes sense to remove them too.) + // + // We use `getCharRange` to get a range that excludes the first token of TD, + // unlike the default conversion of a SourceRange to a "token range", which + // would include it. + rewriteSourceRange(R, + CharSourceRange::getCharRange( + MDI.Members[0]->getBeginLoc(), TD->getBeginLoc()), + ""); + if (TD->getName().empty()) { + // If the record is unnamed, insert the name that we assigned it: + // `struct {` -> `struct T {` + PersistentSourceLoc PSL = PersistentSourceLoc::mkPSL(TD, A); + // This will assert if we can't find the new name. Is that what we want? + std::string NewTypeStr = *Info.TheMultiDeclsInfo.getTypeStrOverride( + A.getTagDeclType(TD).getTypePtr(), A); + // This token should be the tag kind, e.g., `struct`. + std::string ExistingToken = + getSourceText(SourceRange(TD->getBeginLoc()), A); + if (NONFATAL_ASSERT_PLACEHOLDER(ExistingToken == TD->getKindName())) { + rewriteSourceRange(R, TD->getBeginLoc(), NewTypeStr); + } + } + // Make a note if the TagDecl needs to be de-nested later. + if (isa(TD->getLexicalDeclContext())) + TagDeclsToDenest.push_back(TD); + // `struct T { ... } foo;` -> `struct T { ... };\nfoo;` + rewriteSourceRange(R, TD->getEndLoc(), "};\n"); + IsFirst = false; + // Offset by one to skip past what we've just added so it isn't overwritten. + PrevEnd = TD->getEndLoc().getLocWithOffset(1); + } + + for (auto MIt = MDI.Members.begin(); MIt != MDI.Members.end(); MIt++) { + MultiDeclMemberDecl *DL = *MIt; + + // If we modify this member in any way, this is the original source range + // for the member that we expect to overwrite, before PrevEnd adjustment. + // + // We do want to overwrite existing Checked C annotations. Other than + // ItypesForExtern, 3C currently doesn't have real itype support for + // multi-decl members as opposed to function parameters and returns + // (https://github.com/correctcomputation/checkedc-clang/issues/744), but we + // are probably still better off overwriting the annotations with a + // complete, valid declaration than mixing and matching a declaration + // generated by 3C with existing annotations. + // + // If the variable has an initializer, we want this rewrite to end + // before the initializer to avoid interfering with any other rewrites + // that 3C needs to make inside the initializer expression + // (https://github.com/correctcomputation/checkedc-clang/issues/267). + SourceRange ReplaceSR = + getDeclSourceRangeWithAnnotations(DL, /*IncludeInitializer=*/false); + + // Look for a declaration replacement object for the current declaration. + MultiDeclMemberReplacement *Replacement = nullptr; + auto TRIt = ToRewrite.find(DL); + if (TRIt != ToRewrite.end()) { + Replacement = cast(TRIt->second); + // We can't expect multi-decl rewriting to work properly on a source range + // different from ReplaceSR above; for example, doDeclRewrite might insert + // an initializer in the wrong place. This assertion should pass as long + // as the implementation of DeclReplacement::getSourceRange matches + // ReplaceSR above. If someone changes DeclReplacement::getSourceRange, + // thinking that they can replace a different source range that way, we + // want to fail fast. + // + // This is awkward and makes me wonder if we should just remove + // DeclReplacement::getSourceRange since 3C currently only calls + // getSourceRange on an object already known to be a + // FunctionDeclReplacement. But after drafting that, I wasn't convinced + // that it was better than the status quo. + assert(Replacement->getSourceRange(SM) == ReplaceSR); } - if (IsFirst && ContainsInlineStruct) { - // If it is an inline struct, the first thing we have to do - // is separate the RecordDecl from the VarDecl. - ReplaceText = "};\n"; - } else if (IsFirst) { + if (IsFirst) { // Rewriting the first declaration is easy. Nothing should change if its - // type does not to be rewritten. When rewriting is required, it is - // essentially the same as the single declaration case. + // type does not to be rewritten. IsFirst = false; - if (Found) { - SourceRange SR(DL->getBeginLoc(), DL->getEndLoc()); - doDeclRewrite(SR, SameLineReplacement); + if (Replacement) { + doDeclRewrite(ReplaceSR, Replacement); } } else { + // ReplaceSR.getBegin() is the beginning of the whole multi-decl. We only + // want to replace the text starting after the previous multi-decl member, + // which is given by PrevEnd. + ReplaceSR.setBegin(PrevEnd); + // The subsequent decls are more complicated because we need to insert a // type string even if the variables type hasn't changed. - if (Found) { + if (Replacement) { // If the type has changed, the DeclReplacement object has a replacement // string stored in it that should be used. - SourceRange SR(PrevEnd, DL->getEndLoc()); - doDeclRewrite(SR, SameLineReplacement); + doDeclRewrite(ReplaceSR, Replacement); } else { // When the type hasn't changed, we still need to insert the original // type for the variable. - - // This is a bit of trickery needed to get a string representation of - // the declaration without the initializer. We don't want to rewrite to - // initializer because this causes problems when rewriting casts and - // generic function calls later on. (issue 267) - auto *VD = dyn_cast(DL); - Expr *Init = nullptr; - if (VD && VD->hasInit()) { - Init = VD->getInit(); - VD->setInit(nullptr); - } - - // Dump the declaration (without the initializer) to a string. Printing - // the AST node gives the full declaration including the base type which - // is not present in the multi-decl source code. - std::string DeclStr = ""; - raw_string_ostream DeclStream(DeclStr); - DL->print(DeclStream); - assert("Original decl string empty." && !DeclStream.str().empty()); - - // Do the replacement. PrevEnd is setup to be the source location of the - // comma after the previous declaration in the multi-decl. getEndLoc is - // either the end of the declaration or just before the initializer if - // one is present. - SourceRange SR(PrevEnd, DL->getEndLoc()); - rewriteSourceRange(R, SR, DeclStream.str()); - - // Undo prior trickery. This need to happen so that the PSL for the decl - // is not changed since the PSL is used as a map key in a few places. - if (VD && Init) - VD->setInit(Init); + std::string NewDeclStr = mkStringForDeclWithUnchangedType(DL, Info); + rewriteSourceRange(R, ReplaceSR, NewDeclStr); } } - SourceRange End; - // In the event that IsFirst was not set to false, that implies we are - // separating the RecordDecl and VarDecl, so instead of searching for - // the next comma, we simply specify the end of the RecordDecl. - if (IsFirst) { - IsFirst = false; - End = DL->getEndLoc(); - } - // Variables in a mutli-decl are delimited by commas. The rewritten decls + // Variables in a multi-decl are delimited by commas. The rewritten decls // are separate statements separated by a semicolon and a newline. - else - End = getNextCommaOrSemicolon(DL->getEndLoc()); - rewriteSourceRange(R, End, ReplaceText); - // Offset by one to skip past what we've just added so it isn't overwritten. - PrevEnd = End.getEnd().getLocWithOffset(1); + bool IsLast = (MIt + 1 == MDI.Members.end()); + if (!IsLast) { + // This differs from ReplaceSR in that we want to advance past the entire + // multi-decl member, _including_ any existing initializer. + SourceRange SkipSR = + getDeclSourceRangeWithAnnotations(DL, /*IncludeInitializer=*/true); + SourceRange Comma = getNextComma(SkipSR.getEnd()); + rewriteSourceRange(R, Comma, ";\n"); + // Offset by one to skip past what we've just added so it isn't + // overwritten. + PrevEnd = Comma.getEnd().getLocWithOffset(1); + } } + + MDI.AlreadyRewritten = true; } // Common rewriting logic used to replace a single decl either on its own or as @@ -434,17 +464,15 @@ void DeclRewriter::rewriteMultiDecl(DeclReplacement *N, RSet &ToRewrite, // invoking the rewriter) is to add any required initializer expression. void DeclRewriter::doDeclRewrite(SourceRange &SR, DeclReplacement *N) { std::string Replacement = N->getReplacement(); - if (isa(N->getDecl())) - Replacement = "typedef " + Replacement; if (auto *VD = dyn_cast(N->getDecl())) { - if (VD->hasInit()) { - // Make sure we preserve any existing initializer - SR.setEnd(VD->getInitializerStartLoc()); - Replacement += " ="; - } else { + if (!VD->hasInit()) { // There is no initializer. Add it if we need one. // MWH -- Solves issue 43. Should make it so we insert NULL if stdlib.h or // stdlib_checked.h is included + // TODO: Centralize initialization logic for all types: + // https://github.com/correctcomputation/checkedc-clang/issues/645#issuecomment-876474200 + // TODO: Don't add unnecessary initializers to global variables: + // https://github.com/correctcomputation/checkedc-clang/issues/741 if (VD->getStorageClass() != StorageClass::SC_Extern) { const std::string NullPtrStr = "((void *)0)"; if (isPointerType(VD)) { @@ -466,79 +494,19 @@ void DeclRewriter::rewriteFunctionDecl(FunctionDeclReplacement *N) { N->getReplacement()); } -// A function to detect the presence of inline struct declarations -// by tracking VarDecls and RecordDecls and populating data structures -// later used in rewriting. - -// These variables are duplicated in the header file and here because static -// vars need to be initialized in the cpp file where the class is defined. -/*static*/ RecordDecl *DeclRewriter::LastRecordDecl = nullptr; -/*static*/ std::map DeclRewriter::VDToRDMap; -/*static*/ std::set DeclRewriter::InlineVarDecls; -void DeclRewriter::detectInlineStruct(Decl *D, SourceManager &SM) { - RecordDecl *RD = dyn_cast(D); - if (RD != nullptr && - // With -fms-extensions (default on Windows), Clang injects an implicit - // `struct _GUID` with an invalid location, which would cause an assertion - // failure in SM.isPointWithin below. - RD->getBeginLoc().isValid()) { - LastRecordDecl = RD; - } - if (VarDecl *VD = dyn_cast(D)) { - if (LastRecordDecl != nullptr) { - auto LastRecordLocation = LastRecordDecl->getBeginLoc(); - auto Begin = VD->getBeginLoc(); - auto End = VD->getEndLoc(); - bool IsInLineStruct = SM.isPointWithin(LastRecordLocation, Begin, End); - bool IsNamedInLineStruct = - IsInLineStruct && LastRecordDecl->getNameAsString() != ""; - if (IsNamedInLineStruct) { - VDToRDMap[VD] = LastRecordDecl; - InlineVarDecls.insert(VD); - } - } - } -} - -// Uses clangs lexer to find the location of the next comma or semicolon after +// Uses clangs lexer to find the location of the next comma after // the given source location. This is used to find the end of each declaration // within a multi-declaration. -SourceRange DeclRewriter::getNextCommaOrSemicolon(SourceLocation L) { +SourceRange DeclRewriter::getNextComma(SourceLocation L) { SourceManager &SM = A.getSourceManager(); auto Tok = Lexer::findNextToken(L, SM, A.getLangOpts()); while (Tok.hasValue() && !Tok->is(clang::tok::eof)) { - if (Tok->is(clang::tok::comma) || Tok->is(clang::tok::semi)) + if (Tok->is(clang::tok::comma)) return SourceRange(Tok->getLocation(), Tok->getLocation()); Tok = Lexer::findNextToken(Tok->getEndLoc(), A.getSourceManager(), A.getLangOpts()); } - llvm_unreachable("Unable to find comma or semicolon at source location."); -} - -bool DeclRewriter::isSingleDeclaration(DeclReplacement *N) { - DeclStmt *Stmt = N->getStatement(); - if (Stmt == nullptr) { - auto &VDGroup = GP.getVarsOnSameLine(N->getDecl()); - return VDGroup.size() == 1; - } - return Stmt->isSingleDecl(); -} - -void DeclRewriter::getDeclsOnSameLine(DeclReplacement *N, - std::vector &Decls) { - if (N->getStatement() != nullptr) { - Decls.insert(Decls.begin(), N->getStatement()->decls().begin(), - N->getStatement()->decls().end()); - } else { - std::vector GlobalLine = GP.getVarsOnSameLine(N->getDecl()); - Decls.insert(Decls.begin(), GlobalLine.begin(), GlobalLine.end()); - } - - assert("Invalid ordering in same line decls" && - std::is_sorted(Decls.begin(), Decls.end(), [&](Decl *D0, Decl *D1) { - return A.getSourceManager().isBeforeInTranslationUnit( - D0->getEndLoc(), D1->getEndLoc()); - })); + llvm_unreachable("Unable to find comma at source location."); } // This function checks how to re-write a function declaration. @@ -881,13 +849,3 @@ bool FunctionDeclBuilder::inParamMultiDecl(const ParmVarDecl *PVD) { } return false; } - -bool FieldFinder::VisitFieldDecl(FieldDecl *FD) { - GVG.addGlobalDecl(FD); - return true; -} - -void FieldFinder::gatherSameLineFields(GlobalVariableGroups &GVG, Decl *D) { - FieldFinder FF(GVG); - FF.TraverseDecl(D); -} diff --git a/clang/lib/3C/MappingVisitor.cpp b/clang/lib/3C/MappingVisitor.cpp index 3d1f6219fc4a..febf3ec7c7d2 100644 --- a/clang/lib/3C/MappingVisitor.cpp +++ b/clang/lib/3C/MappingVisitor.cpp @@ -14,57 +14,12 @@ using namespace clang; -bool MappingVisitor::VisitDeclStmt(DeclStmt *S) { - PersistentSourceLoc PSL = PersistentSourceLoc::mkPSL(S, Context); - - if (PSL.valid()) { - - // Check to see if the source location as described by the current location - // of S appears in the set of PersistentSourceLocs we are tasked to - // resolve. If it is, then create a mapping mapping the current - // PersistentSourceLocation to the Stmt object S. - std::set::iterator I = SourceLocs.find(PSL); - if (I != SourceLocs.end()) { - Decl *D = nullptr; - Stmt *So = nullptr; - std::tie(So, D) = PSLtoSDT[PSL]; - if (So != nullptr && _3COpts.Verbose) { - llvm::errs() << "\nOverriding "; - S->dump(); - llvm::errs() << "\n"; - llvm::errs() << "With "; - So->dump(); - llvm::errs() << "\n"; - llvm::errs() << " at "; - PSL.dump(); - llvm::errs() << "\n"; - } - - if (So == nullptr) - PSLtoSDT[PSL] = StmtDecl(S, D); - } - - if (DeclStmt *DL = dyn_cast(S)) { - if (DL->isSingleDecl()) { - if (VarDecl *VD = dyn_cast(DL->getSingleDecl())) - DeclToDeclStmt[VD] = DL; - } else - for (auto *I : DL->decls()) - DeclToDeclStmt[I] = DL; - } - } - - return true; -} - bool MappingVisitor::VisitDecl(Decl *D) { PersistentSourceLoc PSL = PersistentSourceLoc::mkPSL(D, Context); if (PSL.valid()) { std::set::iterator I = SourceLocs.find(PSL); if (I != SourceLocs.end()) { - Decl *Do = nullptr; - Stmt *S = nullptr; - std::tie(S, Do) = PSLtoSDT[PSL]; + Decl *Do = PSLtoSDT[PSL]; if (Do != nullptr && _3COpts.Verbose) { llvm::errs() << "Overriding "; Do->dump(); @@ -75,7 +30,7 @@ bool MappingVisitor::VisitDecl(Decl *D) { } if (Do == nullptr) - PSLtoSDT[PSL] = StmtDecl(S, D); + PSLtoSDT[PSL] = D; } } diff --git a/clang/lib/3C/MultiDecls.cpp b/clang/lib/3C/MultiDecls.cpp new file mode 100644 index 000000000000..26a76a7a92da --- /dev/null +++ b/clang/lib/3C/MultiDecls.cpp @@ -0,0 +1,296 @@ +//=--MultiDecls.cpp-----------------------------------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang/3C/MultiDecls.h" +#include "clang/3C/Utils.h" + +MultiDeclMemberDecl *getAsMultiDeclMember(Decl *D) { + // XXX: Is this the best place for this check? + if (D->getLocation().isInvalid()) + return nullptr; + + // A FunctionDecl can be part of a multi-decl in C, but 3C currently doesn't + // handle this + // (https://github.com/correctcomputation/checkedc-clang/issues/659 part (a)). + + // While K&R parameter declarations can be in multi-decls in the input + // program, we don't use any of the regular multi-decl infrastructure for + // them; the rewriter blows them away and generates a prototype. + if (isa(D)) + return nullptr; + + if (VarDecl *VD = dyn_cast(D)) + return VD; + if (FieldDecl *FD = dyn_cast(D)) + return FD; + if (TypedefDecl *TD = dyn_cast(D)) + return TD; + return nullptr; +} + +QualType getTypeOfMultiDeclMember(MultiDeclMemberDecl *MMD) { + if (DeclaratorDecl *DD = dyn_cast(MMD)) + return DD->getType(); + if (TypedefDecl *TD = dyn_cast(MMD)) + return TD->getUnderlyingType(); + llvm_unreachable("Unexpected declaration type"); +} + +TypeSourceInfo *getTypeSourceInfoOfMultiDeclMember(MultiDeclMemberDecl *MMD) { + if (DeclaratorDecl *DD = dyn_cast(MMD)) + return DD->getTypeSourceInfo(); + if (TypedefDecl *TD = dyn_cast(MMD)) + return TD->getTypeSourceInfo(); + llvm_unreachable("Unexpected declaration type"); +} + +void ProgramMultiDeclsInfo::findUsedTagNames(DeclContext *DC) { + // We do our own traversal via `decls` rather than using RecursiveASTVisitor. + // This has the advantage of visiting TagDecls in function parameters, which + // RecursiveASTVisitor doesn't do by default, though such TagDecls are + // potentially problematic for 3C anyway. + for (Decl *D : DC->decls()) { + if (TagDecl *TD = dyn_cast(D)) { + if (!TD->getName().empty()) { + // Multiple TagDecls may have the same name if the same physical + // declaration is seen in multiple translation units or different + // TagDecls with the same name are used in different scopes. That is not + // a problem for us here: we're simply making a list of all the names we + // don't want to collide with. + UsedTagNames.insert(std::string(TD->getName())); + } + } + if (DeclContext *NestedDC = dyn_cast(D)) { + findUsedTagNames(NestedDC); + } + } +} + +void ProgramMultiDeclsInfo::findUsedTagNames(ASTContext &Context) { + findUsedTagNames(Context.getTranslationUnitDecl()); +} + +static const Type *unelaborateType(const Type *Ty) { + if (const ElaboratedType *ETy = dyn_cast(Ty)) { + QualType QT = ETy->getNamedType(); + Ty = QT.getTypePtr(); + // Can an ElaboratedType add qualifiers to its underlying type in C? I don't + // think so, but if it does, we don't want to silently lose them. + NONFATAL_ASSERT_PLACEHOLDER_UNUSED(QualType(Ty, 0) == QT); + } + return Ty; +} + +void ProgramMultiDeclsInfo::findMultiDecls(DeclContext *DC, + ASTContext &Context) { + // This will automatically create a new, empty map for the TU if needed. + TUMultiDeclsInfo &TUInfo = TUInfos[&Context]; + TagDecl *LastTagDef = nullptr; + + // Variables related to the current multi-decl. + MultiDeclInfo *CurrentMultiDecl = nullptr; + SourceLocation CurrentBeginLoc; + PersistentSourceLoc TagDefPSL; + bool TagDefNeedsName; + llvm::Optional RenameInfo; + bool AppliedRenameInfo; + + for (Decl *D : DC->decls()) { + TagDecl *TagD = dyn_cast(D); + if (TagD && TagD->isCompleteDefinition() && + // With -fms-extensions (default on Windows), Clang injects an implicit + // `struct _GUID` with an invalid location. + TagD->getBeginLoc().isValid()) { + LastTagDef = TagD; + } + if (MultiDeclMemberDecl *MMD = getAsMultiDeclMember(D)) { + if (CurrentMultiDecl == nullptr || + MMD->getBeginLoc() != CurrentBeginLoc) { + // We are starting a new multi-decl. + CurrentBeginLoc = MMD->getBeginLoc(); + CurrentMultiDecl = &TUInfo.MultiDeclsByBeginLoc[CurrentBeginLoc]; + assert(CurrentMultiDecl->Members.empty() && + "Multi-decl members are not consecutive in traversal order"); + TagDefNeedsName = false; + RenameInfo = llvm::None; + AppliedRenameInfo = false; + + // Check for an inline tag definition. + // Wanted: CurrentBeginLoc <= LastTagDef->getBeginLoc(). + // Implemented as: !(LastTagDef->getBeginLoc() < CurrentBeginLoc). + if (LastTagDef != nullptr && + !Context.getSourceManager().isBeforeInTranslationUnit( + LastTagDef->getBeginLoc(), CurrentBeginLoc)) { + CurrentMultiDecl->TagDefToSplit = LastTagDef; + TUInfo.ContainingMultiDeclOfTagDecl[LastTagDef] = CurrentMultiDecl; + + // Do we need to automatically name the TagDefToSplit? + if (LastTagDef->getName().empty()) { + // A RecordDecl that is declared as the type of one or more + // variables shouldn't be "anonymous", but if it somehow is, we + // don't want to try to give it a name. + NONFATAL_ASSERT_PLACEHOLDER_UNUSED( + !(isa(LastTagDef) && + cast(LastTagDef)->isAnonymousStructOrUnion())); + TagDefPSL = PersistentSourceLoc::mkPSL(LastTagDef, Context); + auto Iter = RenamedTagDefs.find(TagDefPSL); + if (Iter != RenamedTagDefs.end()) + RenameInfo = Iter->second; + else if (canWrite(TagDefPSL.getFileName())) + TagDefNeedsName = true; + } + } + } else { + // Adding another member to an existing multi-decl. + assert(Context.getSourceManager().isBeforeInTranslationUnit( + CurrentMultiDecl->Members.back()->getEndLoc(), + MMD->getEndLoc()) && + "Multi-decl traversal order inconsistent " + "with source location order"); + } + + CurrentMultiDecl->Members.push_back(MMD); + + std::string MemberName; + if (TagDefNeedsName && + NONFATAL_ASSERT_PLACEHOLDER( + !(MemberName = std::string(MMD->getName())).empty())) { + // Special case: If the first member of the multi-decl is a typedef + // whose type is exactly the TagDecl type (`typedef struct { ... } T`), + // then we refer to the TagDecl via that typedef. (The typedef must be + // the first member so that it is defined in time for other members to + // refer to it.) + // + // An argument could be made for using the typedef name in the types of + // other multi-decl members even if the TagDecl has a name: + // `typedef struct T_struct { ... } T, *PT;` would convert to + // `typedef struct T_struct { ... } T; typedef _Ptr PT;` instead of + // `struct T_struct { ... }; typedef struct T_struct T; + // typedef _Ptr PT;`. But it would be tricky to ensure + // that any existing references to `struct T_struct` aren't accidentally + // replaced with `T`, so absent a decision that this feature is + // important enough to justify either solving or ignoring this problem, + // we don't try to implement the feature. + TypedefDecl *TyD; + QualType Underlying; + if (CurrentMultiDecl->Members.size() == 1 && + (TyD = dyn_cast(MMD)) != nullptr && + // XXX: This is a terrible mess. Figure out how we should be + // handling the difference between Type and QualType. + !(Underlying = TyD->getUnderlyingType()).hasLocalQualifiers() && + QualType(unelaborateType(Underlying.getTypePtr()), 0) == + Context.getTagDeclType(LastTagDef)) { + // In this case, ShouldSplit = false: the tag definition should not be + // moved out of the typedef. + RenameInfo = RenamedTagDefInfo{MemberName, false}; + } else { + // Otherwise, just generate a new tag name based on the member name. + // Example: `struct { ... } foo;` -> + // `struct foo_struct_1 { ... }; struct foo_struct_1 foo;` + // If `foo_struct_1` is already taken, use `foo_struct_2`, etc. + std::string KindName = std::string(LastTagDef->getKindName()); + std::string NewName; + for (int Num = 1;; Num++) { + NewName = MemberName + "_" + KindName + "_" + std::to_string(Num); + if (UsedTagNames.find(NewName) == UsedTagNames.end()) + break; + } + RenameInfo = RenamedTagDefInfo{KindName + " " + NewName, true}; + // Consider this name taken and ensure that other automatically + // generated names do not collide with it. + // + // If the multi-decl doesn't end up getting rewritten, this name + // ultimately may not be used, creating a gap in the numbering in 3C's + // output. But this cosmetic inconsistency is a small price to pay for + // the architectural convenience of being able to store the assigned + // names in the PointerVariableConstraints when they are constructed + // rather than trying to assign and store the names after we know + // which multi-decls will be rewritten. + UsedTagNames.insert(NewName); + } + RenamedTagDefs.insert(std::make_pair(TagDefPSL, *RenameInfo)); + TagDefNeedsName = false; + } + + // To help avoid bugs, use the same code whether the RenameInfo was just + // assigned or was saved from a previous translation unit. + if (RenameInfo && !AppliedRenameInfo) { + CurrentMultiDecl->BaseTypeRenamed = true; + if (!RenameInfo->ShouldSplit) + CurrentMultiDecl->TagDefToSplit = nullptr; + AppliedRenameInfo = true; + } + } + + if (DeclContext *NestedDC = dyn_cast(D)) { + findMultiDecls(NestedDC, Context); + } + } +} + +void ProgramMultiDeclsInfo::findMultiDecls(ASTContext &Context) { + findMultiDecls(Context.getTranslationUnitDecl(), Context); +} + +llvm::Optional +ProgramMultiDeclsInfo::getTypeStrOverride(const Type *Ty, const ASTContext &C) { + Ty = unelaborateType(Ty); + if (const TagType *TTy = dyn_cast(Ty)) { + TagDecl *TD = TTy->getDecl(); + if (TD->getName().empty()) { + PersistentSourceLoc PSL = PersistentSourceLoc::mkPSL(TD, C); + auto Iter = RenamedTagDefs.find(PSL); + if (Iter != RenamedTagDefs.end()) + return Iter->second.AssignedTypeStr; + // We should have named all unnamed TagDecls in writable code. + NONFATAL_ASSERT_PLACEHOLDER_UNUSED(!canWrite(PSL.getFileName())); + } + } + return llvm::None; +} + +MultiDeclInfo * +ProgramMultiDeclsInfo::findContainingMultiDecl(MultiDeclMemberDecl *MMD) { + auto &MultiDeclsByLoc = TUInfos[&MMD->getASTContext()].MultiDeclsByBeginLoc; + // Look for a MultiDeclInfo for the beginning location of MMD, then check that + // the MultiDeclInfo actually contains MMD. + auto It = MultiDeclsByLoc.find(MMD->getBeginLoc()); + if (It == MultiDeclsByLoc.end()) + return nullptr; + MultiDeclInfo &MDI = It->second; + // Hope we don't have multi-decls with so many members that this becomes a + // performance problem. + if (std::find(MDI.Members.begin(), MDI.Members.end(), MMD) != + MDI.Members.end()) + return &MDI; + return nullptr; +} + +MultiDeclInfo *ProgramMultiDeclsInfo::findContainingMultiDecl(TagDecl *TD) { + auto &MDOfTD = TUInfos[&TD->getASTContext()].ContainingMultiDeclOfTagDecl; + auto It = MDOfTD.find(TD); + if (It == MDOfTD.end()) + return nullptr; + return It->second; +} + +bool ProgramMultiDeclsInfo::wasBaseTypeRenamed(Decl *D) { + // We assume that the base type was renamed if and only if D belongs to a + // multi-decl marked as having the base type renamed. It might be better to + // actually extract the base type from D and look it up in RenamedTagDefs, + // but that's more work. + MultiDeclMemberDecl *MMD = getAsMultiDeclMember(D); + if (!MMD) + return false; + MultiDeclInfo *MDI = findContainingMultiDecl(MMD); + // We expect to have a MultiDeclInfo for every MultiDeclMemberDecl in the + // program. + if (!NONFATAL_ASSERT_PLACEHOLDER(MDI)) + return false; + return MDI->BaseTypeRenamed; +} diff --git a/clang/lib/3C/ProgramInfo.cpp b/clang/lib/3C/ProgramInfo.cpp index f89d2a5bd4b7..ce93d0bf0ff6 100644 --- a/clang/lib/3C/ProgramInfo.cpp +++ b/clang/lib/3C/ProgramInfo.cpp @@ -695,6 +695,11 @@ void ProgramInfo::addVariable(clang::DeclaratorDecl *D, unifyIfTypedef(QT, *AstContext, P); NewCV = P; NewCV->setValidDecl(); + if (FlD->getParent()->isUnion()) { + auto Rsn = ReasonLoc(UNION_FIELD_REASON, PLoc); + NewCV->equateWithItype(*this, Rsn); + NewCV->constrainToWild(CS, Rsn); + } } } else llvm_unreachable("unknown decl type"); @@ -1156,18 +1161,14 @@ bool ProgramInfo::seenTypedef(PersistentSourceLoc PSL) { return TypedefVars.count(PSL) != 0; } -void ProgramInfo::addTypedef(PersistentSourceLoc PSL, bool CanRewriteDef, - TypedefDecl *TD, ASTContext &C) { +void ProgramInfo::addTypedef(PersistentSourceLoc PSL, TypedefDecl *TD, + ASTContext &C) { ConstraintVariable *V = nullptr; if (isa(TD->getUnderlyingType())) V = new FunctionVariableConstraint(TD, *this, C); else V = new PointerVariableConstraint(TD, *this, C); - if (!CanRewriteDef) - V->constrainToWild(this->getConstraints(), - ReasonLoc("Unable to rewrite a typedef with multiple names", PSL)); - if (!canWrite(PSL.getFileName())) V->constrainToWild(this->getConstraints(), ReasonLoc(UNWRITABLE_REASON, PSL)); diff --git a/clang/lib/3C/RewriteUtils.cpp b/clang/lib/3C/RewriteUtils.cpp index 90ac3561f5eb..83fd8815dd08 100644 --- a/clang/lib/3C/RewriteUtils.cpp +++ b/clang/lib/3C/RewriteUtils.cpp @@ -21,41 +21,94 @@ using namespace llvm; using namespace clang; -void GlobalVariableGroups::addGlobalDecl(Decl *VD, std::vector *VDVec) { - if (VD && GlobVarGroups.find(VD) == GlobVarGroups.end()) { - if (VDVec == nullptr) - VDVec = new std::vector(); - assert("Decls in group are not ordered correctly." && - (VDVec->empty() || - SM.isBeforeInTranslationUnit(VDVec->back()->getEndLoc(), - VD->getEndLoc()))); - VDVec->push_back(VD); - GlobVarGroups[VD] = VDVec; - // Process the next decl. - Decl *NDecl = VD->getNextDeclInContext(); - if (isa_and_nonnull(NDecl) || isa_and_nonnull(NDecl)) - if (VD->getBeginLoc() == NDecl->getBeginLoc()) - addGlobalDecl(dyn_cast(NDecl), VDVec); +std::string mkStringForPVDecl(MultiDeclMemberDecl *MMD, PVConstraint *PVC, + ProgramInfo &Info) { + // Currently, it's cheap to keep recreating the ArrayBoundsRewriter. If that + // ceases to be true, we should pass it along as another argument. + ArrayBoundsRewriter ABRewriter{Info}; + std::string NewDecl = getStorageQualifierString(MMD); + bool IsExternGlobalVar = + isa(MMD) && + cast(MMD)->getFormalLinkage() == Linkage::ExternalLinkage; + if (_3COpts.ItypesForExtern && (isa(MMD) || IsExternGlobalVar) && + // isSolutionChecked can return false here when splitting out an unchanged + // multi-decl member. + PVC->isSolutionChecked(Info.getConstraints().getVariables())) { + // Give record fields and global variables itypes when using + // -itypes-for-extern. Note that we haven't properly implemented itypes for + // structures and globals + // (https://github.com/correctcomputation/checkedc-clang/issues/744). This + // just rewrites to an itype instead of a fully checked type when a checked + // type could have been used. This does provide most of the rewriting + // infrastructure that would be required to support these itypes if + // constraint generation is updated to handle structure/global itypes. + std::string Type, IType; + // VarDecl and FieldDecl subclass DeclaratorDecl, so the cast will + // always succeed. + DeclRewriter::buildItypeDecl(PVC, cast(MMD), Type, IType, + Info, ABRewriter); + NewDecl += Type + IType; + } else { + NewDecl += PVC->mkString(Info.getConstraints()) + + ABRewriter.getBoundsString(PVC, MMD); } + return NewDecl; } -std::vector &GlobalVariableGroups::getVarsOnSameLine(Decl *D) { - assert(GlobVarGroups.find(D) != GlobVarGroups.end() && - "Expected to find the group."); - return *(GlobVarGroups[D]); -} +std::string mkStringForDeclWithUnchangedType(MultiDeclMemberDecl *MMD, + ProgramInfo &Info) { + ASTContext &Context = MMD->getASTContext(); + + bool BaseTypeRenamed = Info.TheMultiDeclsInfo.wasBaseTypeRenamed(MMD); + if (!BaseTypeRenamed) { + // As far as we know, we can let Clang generate the declaration string. + PrintingPolicy Policy = Context.getPrintingPolicy(); + Policy.SuppressInitializers = true; + std::string DeclStr = ""; + raw_string_ostream DeclStream(DeclStr); + MMD->print(DeclStream, Policy); + assert("Original decl string empty." && !DeclStr.empty()); + return DeclStr; + } -GlobalVariableGroups::~GlobalVariableGroups() { - std::set *> VVisited; - // Free each of the group. - for (auto &CurrV : GlobVarGroups) { - // Avoid double free by caching deleted sets. - if (VVisited.find(CurrV.second) != VVisited.end()) - continue; - VVisited.insert(CurrV.second); - delete (CurrV.second); + // OK, we have to use mkString. + QualType DType = getTypeOfMultiDeclMember(MMD); + if (isPtrOrArrayType(DType)) { + CVarOption CVO = + (isa(MMD) + ? Info.lookupTypedef(PersistentSourceLoc::mkPSL(MMD, Context)) + : Info.getVariable(MMD, &Context)); + assert(CVO.hasValue() && + "Missing ConstraintVariable for unchanged multi-decl member"); + // A function currently can't be a multi-decl member, so this should always + // be a PointerVariableConstraint. + PVConstraint *PVC = cast(&CVO.getValue()); + // Currently, we benefit from the ItypesForExtern handling in + // mkStringForPVDecl in one very unusual case: an unchanged multi-decl + // member with a renamed TagDecl and an existing implicit itype coming from + // a bounds annotation will keep the itype and not be changed to a fully + // checked type. DeclRewriter::buildItypeDecl will detect the base type + // rename and generate the unchecked side using mkString instead of + // Decl::print in order to pick up the new name. + // + // As long as 3C lacks real support for itypes on variables, this is + // probably the behavior we want with -itypes-for-extern. If we don't care + // about this case, we could alternatively inline the few lines of + // mkStringForPVDecl that would still be relevant. + return mkStringForPVDecl(MMD, PVC, Info); } - GlobVarGroups.clear(); + + // If the type is not a pointer or array, then it should just equal the base + // type except for top-level qualifiers, and it can't have itypes or bounds. + llvm::Optional BaseTypeNewNameOpt = + Info.TheMultiDeclsInfo.getTypeStrOverride(DType.getTypePtr(), Context); + assert(BaseTypeNewNameOpt && + "BaseTypeRenamed is true but we couldn't get the new name"); + std::string QualifierPrefix = DType.getQualifiers().getAsString(); + if (!QualifierPrefix.empty()) + QualifierPrefix += " "; + return getStorageQualifierString(MMD) + QualifierPrefix + + *BaseTypeNewNameOpt + " " + std::string(MMD->getName()); } // Test to see if we can rewrite a given SourceRange. @@ -429,14 +482,8 @@ class TypeArgumentAdder : public clang::RecursiveASTVisitor { }; SourceRange DeclReplacement::getSourceRange(SourceManager &SM) const { - SourceRange SR = getDecl()->getSourceRange(); - SourceLocation OldEnd = SR.getEnd(); - SourceLocation NewEnd = getCheckedCAnnotationsEnd(getDecl()); - if (NewEnd.isValid() && - (!OldEnd.isValid() || SM.isBeforeInTranslationUnit(OldEnd, NewEnd))) - SR.setEnd(NewEnd); - - return SR; + return getDeclSourceRangeWithAnnotations(getDecl(), + /*IncludeInitializer=*/false); } SourceRange FunctionDeclReplacement::getSourceRange(SourceManager &SM) const { diff --git a/clang/lib/3C/StructInit.cpp b/clang/lib/3C/StructInit.cpp index 886b584d26d4..fcc53f641f23 100644 --- a/clang/lib/3C/StructInit.cpp +++ b/clang/lib/3C/StructInit.cpp @@ -11,6 +11,7 @@ #include "clang/3C/StructInit.h" #include "clang/3C/MappingVisitor.h" +#include "clang/3C/RewriteUtils.h" #include "clang/Tooling/Transformer/SourceCode.h" #include @@ -53,31 +54,18 @@ bool StructVariableInitializer::hasCheckedMembers(DeclaratorDecl *DD) { // Insert the declaration and correct replacement text for the declaration into // the set of required rewritings. -void StructVariableInitializer::insertVarDecl(VarDecl *VD, DeclStmt *S) { +bool StructVariableInitializer::VisitVarDecl(VarDecl *VD) { // Check if we need to add an initializer. - bool IsVarExtern = VD->getStorageClass() == StorageClass::SC_Extern; - if (!IsVarExtern && !VD->hasInit() && hasCheckedMembers(VD)) { + // TODO: Centralize initialization logic for all types: + // https://github.com/correctcomputation/checkedc-clang/issues/645#issuecomment-876474200 + + // The first two conditions are the same as in Sema::ActOnUninitializedDecl. + if (VD->hasLocalStorage() && !isa(VD) && !VD->hasInit() && + hasCheckedMembers(VD)) { // Create replacement declaration text with an initializer. - const clang::Type *Ty = VD->getType().getTypePtr(); - std::string TQ = VD->getType().getQualifiers().getAsString(); - if (!TQ.empty()) - TQ += " "; - std::string ToReplace = getStorageQualifierString(VD) + TQ + tyToStr(Ty) + - " " + VD->getName().str() + " = {}"; + std::string ToReplace = mkStringForDeclWithUnchangedType(VD, I) + " = {}"; RewriteThese.insert( - std::make_pair(VD, new VarDeclReplacement(VD, S, ToReplace))); - } -} - -// Check to see if this variable require an initialization. -bool StructVariableInitializer::VisitDeclStmt(DeclStmt *S) { - if (S->isSingleDecl()) { - if (VarDecl *VD = dyn_cast(S->getSingleDecl())) - insertVarDecl(VD, S); - } else { - for (const auto &D : S->decls()) - if (VarDecl *VD = dyn_cast(D)) - insertVarDecl(VD, S); + std::make_pair(VD, new MultiDeclMemberReplacement(VD, ToReplace))); } return true; } diff --git a/clang/lib/3C/Utils.cpp b/clang/lib/3C/Utils.cpp index 84ede9a2f98e..7da01da04cb6 100644 --- a/clang/lib/3C/Utils.cpp +++ b/clang/lib/3C/Utils.cpp @@ -120,7 +120,8 @@ static std::string storageClassToString(StorageClass SC) { } // This method gets the storage qualifier for the -// provided declaration i.e., static, extern, etc. +// provided declaration including any trailing space, i.e., "static ", +// "extern ", etc., or "" if none. std::string getStorageQualifierString(Decl *D) { if (FunctionDecl *FD = dyn_cast(D)) { return storageClassToString(FD->getStorageClass()); @@ -128,6 +129,12 @@ std::string getStorageQualifierString(Decl *D) { if (VarDecl *VD = dyn_cast(D)) { return storageClassToString(VD->getStorageClass()); } + if (isa(D)) { + // `typedef` goes in the same syntactic position as a storage qualifier and + // needs to be inserted when breaking up a multi-decl, just like a real + // storage qualifier. + return "typedef "; + } return ""; } @@ -598,3 +605,42 @@ SourceLocation getCheckedCAnnotationsEnd(const Decl *D) { return End; } + +SourceRange getDeclSourceRangeWithAnnotations(const clang::Decl *D, + bool IncludeInitializer) { + SourceManager &SM = D->getASTContext().getSourceManager(); + SourceRange SR; + const VarDecl *VD; + // Only a VarDecl can have an initializer. VarDecl's implementation of the + // getSourceRange virtual method includes the initializer, but we can manually + // call DeclaratorDecl's implementation, which excludes the initializer. + if (!IncludeInitializer && (VD = dyn_cast(D)) != nullptr) + SR = VD->DeclaratorDecl::getSourceRange(); + else + SR = D->getSourceRange(); + if (!SR.isValid()) + return SR; + SourceLocation DeclEnd = SR.getEnd(); + + // Partial workaround for a compiler bug where if D has certain checked + // pointer types such as `_Ptr` (seen in the partial_checked.c + // regression test), D->getSourceRange() returns only the _Ptr token. (As of + // this writing on 2021-11-18, no bug report has been filed against the + // compiler, but https://github.com/correctcomputation/checkedc-clang/pull/723 + // tracks our work on the bug.) + // + // Always extend the range at least through the name (given by + // D->getLocation()). That fixes the `_Ptr x` case but not cases + // with additional syntax after the name, such as `_Ptr x[10]`. + SourceLocation DeclLoc = D->getLocation(); + if (SM.isBeforeInTranslationUnit(DeclEnd, DeclLoc)) + DeclEnd = DeclLoc; + + SourceLocation AnnotationsEnd = getCheckedCAnnotationsEnd(D); + if (AnnotationsEnd.isValid() && + SM.isBeforeInTranslationUnit(DeclEnd, AnnotationsEnd)) + DeclEnd = AnnotationsEnd; + + SR.setEnd(DeclEnd); + return SR; +} diff --git a/clang/test/3C/basic_checks.c b/clang/test/3C/basic_checks.c index b94ec1e49916..7fcec48335ed 100644 --- a/clang/test/3C/basic_checks.c +++ b/clang/test/3C/basic_checks.c @@ -33,12 +33,13 @@ typedef struct _A { int a; int b; } A, *PA; +//CHECK: typedef _Ptr PA; void mut_pa(PA p) { p->a = 0; p->b = 1; } -//CHECK: void mut_pa(PA p : itype(_Ptr)) { +//CHECK: void mut_pa(PA p) { void pa_driver(void) { A a = {0}; diff --git a/clang/test/3C/inline_anon_structs.c b/clang/test/3C/inline_anon_structs.c index 9b1406157285..e26e64403e65 100644 --- a/clang/test/3C/inline_anon_structs.c +++ b/clang/test/3C/inline_anon_structs.c @@ -1,5 +1,6 @@ // RUN: rm -rf %t* // RUN: 3c -base-dir=%S -alltypes -addcr %s -- | FileCheck -match-full-lines -check-prefixes="CHECK_ALL","CHECK" %s +// RUN: 3c -base-dir=%S -alltypes -addcr %s -- | %clang -c -fcheckedc-extension -x c -o /dev/null - // RUN: 3c -base-dir=%S -addcr %s -- | FileCheck -match-full-lines -check-prefixes="CHECK_NOALL","CHECK" %s // RUN: 3c -base-dir=%S -addcr %s -- | %clang -c -fcheckedc-extension -x c -o /dev/null - // RUN: 3c -base-dir=%S -output-dir=%t.checked %s -- @@ -11,13 +12,29 @@ an inlinestruct and its associated VarDecl have different locations*/ int valuable; +// When -alltypes is on, the addition of _Checked to the array triggers +// rewriting of the multi-decl, including splitting of the struct definition. +// When it is off, the multi-decl as such is not rewritten, but in either case, +// the fields of the struct are rewritten as appropriate. static struct foo { + // When an inline struct definition is separated from variable declarations, + // if there was a `static` keyword that applied to the variables, we should + // remove it from the separated struct (where it is not meaningful). + //CHECK_NOALL: static struct foo { + //CHECK_ALL: struct foo { const char *name; + // See https://github.com/correctcomputation/checkedc-clang/issues/470. //CHECK_NOALL: const char *name; //CHECK_ALL: _Ptr name; int *p_valuable; //CHECK: _Ptr p_valuable; } array[] = {{"mystery", &valuable}}; +// {{...}} in a CHECK directive delimits a regular expression +// (https://llvm.org/docs/CommandGuide/FileCheck.html#filecheck-regex-matching-syntax) +// and there isn't a way to escape that construct itself, so we use a regular +// expression and escape the contents as needed. +//CHECK_NOALL: {{\} array\[\] = \{\{"mystery", &valuable\}\};}} +//CHECK_ALL: {{static struct foo array _Checked\[\] = \{\{"mystery", &valuable\}\};}} /*This code is a series of more complex tests for inline structs*/ /* a, b, c below all stay as WILD pointers; d can be a _Ptr<...>*/ @@ -67,15 +84,14 @@ void foo(void) { /*This code tests anonymous structs */ struct { + //CHECK: struct x_struct_1 { /*the fields of the anonymous struct are free to be marked checked*/ int *data; //CHECK_NOALL: int *data; - - /* but the actual pointer can't be when alltypes is disabled */ - /* when alltypes is enabled, this whole structure is rewritten - improperly, but that's OK, because we signal a warning to the user*/ + //CHECK_ALL: _Array_ptr data : count(4); } * x; -//CHECK_ALL: _Ptr x = ((void *)0); +//CHECK: }; +//CHECK-NEXT: _Ptr x = ((void *)0); /*ensure trivial conversion*/ void foo1(int *w) { @@ -93,15 +109,25 @@ struct alpha *al[4]; //CHECK_NOALL: struct alpha *al[4]; //CHECK_ALL: _Ptr al _Checked[4] = {((void *)0)}; -/*be should be made wild, whereas a should be converted*/ +// A similar test with an unnamed struct. struct { int *a; - //CHECK_NOALL: _Ptr a; + //CHECK: _Ptr a; } * be[4]; +//CHECK_NOALL: } * be[4]; +//CHECK_ALL: }; +//CHECK_ALL-NEXT: _Ptr be _Checked[4] = {((void *)0)}; + +// The following is explained in second_tu_fn in inline_anon_structs_cross_tu.c. +struct { int x; } *cross_tu_numbering_test; +//CHECK_AB: struct cross_tu_numbering_test_struct_1 { int x; }; +//CHECK_AB-NEXT: _Ptr cross_tu_numbering_test = ((void *)0); +//CHECK_BA: struct cross_tu_numbering_test_struct_2 { int x; }; +//CHECK_BA-NEXT: _Ptr cross_tu_numbering_test = ((void *)0); /*this code checks inline structs withiin functions*/ void foo2(int *x) { - //CHECK: void foo2(_Ptr x) { + //CHECK: void foo2(_Ptr x) _Checked { struct bar { int *x; } *y = 0; @@ -110,29 +136,35 @@ void foo2(int *x) { //CHECK-NEXT: }; //CHECK-NEXT: _Ptr y = 0; - /*A non-pointer struct without an init will be marked wild*/ + // A similar test with an automatically added struct initializer. struct something { int *x; } z; //CHECK: struct something { - //CHECK-NEXT: int *x; - //CHECK-NEXT: } z; + //CHECK-NEXT: _Ptr x; + //CHECK-NEXT: }; + //CHECK-NEXT: struct something z = {}; - /*so will ones that are anonymous*/ + // Ditto with an anonymous struct. struct { int *x; } a; - //CHECK: struct { - //CHECK-NEXT: int *x; - //CHECK-NEXT: } a; + //CHECK: struct a_struct_1 { + //CHECK-NEXT: _Ptr x; + //CHECK-NEXT: }; + //CHECK-NEXT: struct a_struct_1 a = {}; - /*if it have an initializer, the rewriter won't have trouble*/ + // If the variable already has an initializer, then there is no initializer + // addition to trigger rewriting and splitting of the struct. struct { int *c; } b = {}; //CHECK: struct { //CHECK-NEXT: _Ptr c; //CHECK-NEXT: } b = {}; + + // Additional regression tests (only checking compilation with no crash) from + // https://github.com/correctcomputation/checkedc-clang/pull/497. struct { int *i; } * f; @@ -140,3 +172,184 @@ void foo2(int *x) { int *il } * g, *h, *i; } + +// Tests of new functionality from +// https://github.com/correctcomputation/checkedc-clang/pull/657. + +// Test that 3C doesn't mangle the code by attempting to split a forward +// declaration of a struct out of a multi-decl as if it were a definition +// (https://github.com/correctcomputation/checkedc-clang/issues/644). +struct fwd *p; +//CHECK: _Ptr p = ((void *)0); + +// Test the handling of inline TagDecls when the containing multi-decl is +// rewritten: +// - TagDecls are de-nested in postorder whether or not they were originally +// named, and this does not interfere with rewrites inside those TagDecls +// (https://github.com/correctcomputation/checkedc-clang/issues/531). +// - Storage and type qualifiers preceding an inline TagDecl are not copied to +// the split TypeDecl (where they wouldn't be meaningful) but remain +// associated with the fields to which they originally applied +// (https://github.com/correctcomputation/checkedc-clang/issues/647). +// - Unnamed TagDecls are automatically named +// (https://github.com/correctcomputation/checkedc-clang/issues/542). +// This big combined test may be a bit hard to understand, but it may provide +// better coverage of any interactions among these features than separate tests +// would. + +// Use up the c_struct_1 name. +struct c_struct_1 {}; + +static struct A { + const struct B { + struct { + int *c2i; + } *c; + } *ab, ab_arr[2]; + volatile struct D { + struct { + int *c3i; + } *c; + struct E { + int *ei; + } *de; + const enum F { F_0, F_1 } *df; + enum { G_0, G_1 } *dg; + struct H { + int *hi; + } *dh; + union U { + int *ui; + } *du; + union { + int *vi; + } *dv; + } *ad; +} *global_a, global_a_arr[2]; + +void constrain_dh(void) { + struct D d; + d.dh = (struct H *)1; +} + +// Points of note: +// - We have two unnamed inline structs that get automatically named after a +// field `c`, but the name `c_struct_1` is taken, so the names `c_struct_2` +// and `c_struct_3` are assigned. +// - All kinds of TagDecls (structs, unions, and enums) can be moved out of a +// containing struct and automatically named if needed. In principle, 3C +// should be able to move TagDecls out of a union, but I couldn't find any way +// to force a union field to be rewritten in order to demonstrate this, since +// 3C constrains union fields to wild. As far as I know, TagDecls can't be +// nested in an enum in C. +// - `struct H` does not get moved because there is nothing to trigger rewriting +// of the containing multi-decl since `dh` is constrained wild by +// `constrain_dh`. + +//CHECK: struct c_struct_2 { +//CHECK-NEXT: _Ptr c2i; +//CHECK-NEXT: }; +//CHECK-NEXT: struct B { +//CHECK-NEXT: _Ptr c; +//CHECK-NEXT: }; +//CHECK-NEXT: struct c_struct_3 { +//CHECK-NEXT: _Ptr c3i; +//CHECK-NEXT: }; +//CHECK-NEXT: struct E { +//CHECK-NEXT: _Ptr ei; +//CHECK-NEXT: }; +//CHECK-NEXT: enum F { F_0, F_1 }; +//CHECK-NEXT: enum dg_enum_1 { G_0, G_1 }; +//CHECK-NEXT: union U { +//CHECK-NEXT: int *ui; +//CHECK-NEXT: }; +//CHECK-NEXT: union dv_union_1 { +//CHECK-NEXT: int *vi; +//CHECK-NEXT: }; +//CHECK-NEXT: struct D { +//CHECK-NEXT: _Ptr c; +//CHECK-NEXT: _Ptr de; +//CHECK-NEXT: _Ptr df; +//CHECK-NEXT: _Ptr dg; +//CHECK-NEXT: struct H { +//CHECK-NEXT: _Ptr hi; +//CHECK-NEXT: } *dh; +//CHECK-NEXT: _Ptr du; +//CHECK-NEXT: _Ptr dv; +//CHECK-NEXT: }; +//CHECK-NEXT: struct A { +//CHECK-NEXT: _Ptr ab; +//CHECK_NOALL-NEXT: const struct B ab_arr[2]; +//CHECK_ALL-NEXT: const struct B ab_arr _Checked[2]; +//CHECK-NEXT: _Ptr ad; +//CHECK-NEXT: }; +//CHECK-NEXT: static _Ptr global_a = ((void *)0); +//CHECK_NOALL-NEXT: static struct A global_a_arr[2]; +//CHECK_ALL-NEXT: static struct A global_a_arr _Checked[2]; + +// This case is not intentionally supported but works "by accident" because 3C +// deletes everything between the start location of the first member and the +// start of the TagDecl (here, `_Ptr<`) and replaces everything between the end +// location of the TagDecl and the end of the first member (here, +// `> *chkptr_struct_var`) with the new text of the first member. It may well be +// the right decision to break this case in the future, but it would be nice to +// be aware that we're doing so, and we might want to keep a test that this case +// merely produces wrong output and doesn't crash the rewriter. +_Ptr *chkptr_struct_var; +//CHECK: struct chkptr_struct { _Ptr x; }; +//CHECK-NEXT: _Ptr<_Ptr> chkptr_struct_var = ((void *)0); + +// Tests of the special case where we use the first member of a typedef +// multi-decl as the name of the inline TagDecl. + +typedef struct { int *x; } SFOO, *PSFOO; +//CHECK: typedef struct { _Ptr x; } SFOO; +//CHECK-NEXT: typedef _Ptr PSFOO; + +// The other way around, we can't do it because SBAR would be defined too late +// to use it in the definition of PSBAR. +typedef struct { int *x; } *PSBAR, SBAR; +//CHECK: struct PSBAR_struct_1 { _Ptr x; }; +//CHECK-NEXT: typedef _Ptr PSBAR; +//CHECK-NEXT: typedef struct PSBAR_struct_1 SBAR; + +// Borderline case: Since SFOO_CONST has a qualifier, we don't use it as the +// name of the inline struct. If we did, we'd end up with `typedef +// _Ptr PSFOO_CONST`, which still expands to +// `_Ptr`, but the duplicate `const` may be a bit +// confusing to the reader, so we don't do that. +typedef const struct { int *x; } SFOO_CONST, *PSFOO_CONST; +//CHECK: struct SFOO_CONST_struct_1 { _Ptr x; }; +//CHECK-NEXT: typedef const struct SFOO_CONST_struct_1 SFOO_CONST; +//CHECK-NEXT: typedef _Ptr PSFOO_CONST; + +// Test that when the outer struct is preceded by a qualifier, de-nesting +// inserts inner structs before the qualifier, not between the qualifier and the +// outer `struct` keyword. This is moot if the outer struct is split as part of +// multi-decl rewriting because multi-decl rewriting will delete the qualifier +// and add it before the first member, but the problem can happen if the +// multi-decl isn't rewritten or the outer struct isn't split because we have a +// typedef for it. + +typedef struct { struct q_not_rewritten_inner {} *x; } *q_not_rewritten_outer; +q_not_rewritten_outer onr = (q_not_rewritten_outer)1; +//CHECK: struct q_not_rewritten_inner {}; +//CHECK-NEXT: typedef struct { _Ptr x; } *q_not_rewritten_outer; + +typedef struct { + struct q_typedef_inner {} *x; +} q_typedef_outer, *q_typedef_outer_trigger_rewrite; +//CHECK: struct q_typedef_inner {}; +//CHECK-NEXT: typedef struct { +//CHECK-NEXT: _Ptr x; +//CHECK-NEXT: } q_typedef_outer; +//CHECK-NEXT: typedef _Ptr q_typedef_outer_trigger_rewrite; + +// As noted in the comment in DeclRewriter::denestTagDecls, when the outer +// struct isn't part of a multi-decl, we don't have an easy way to find the +// location before the (useless) qualifier, so the output is a bit weird but +// still has only a compiler warning, though in a different place than it +// should. +typedef struct { struct q_pointless_inner {} *x; }; +//CHECK: typedef struct q_pointless_inner {}; +//CHECK-NEXT: struct { _Ptr x; }; diff --git a/clang/test/3C/inline_anon_structs_cross_tu.c b/clang/test/3C/inline_anon_structs_cross_tu.c new file mode 100644 index 000000000000..b83d2027c932 --- /dev/null +++ b/clang/test/3C/inline_anon_structs_cross_tu.c @@ -0,0 +1,69 @@ +// Regression tests for two bugs seen during the development of +// https://github.com/correctcomputation/checkedc-clang/pull/657 with multiple +// translation units: some information about automatically named TagDecls was +// not propagated across translation units. +// +// Our translation units are inline_anon_structs.c and +// inline_anon_structs_cross_tu.c; the latter `#include`s the former and has +// some code of its own. So inline_anon_structs.c effectively plays the role of +// a shared header file, but we just use the .c file instead of moving the code +// to a .h file and making another .c file that does nothing but `#include` the +// .h file. +// +// We test 3C on both orders of the translation units. Remember that when the +// same file is rewritten as part of multiple translation units, the last +// translation unit wins +// (https://github.com/correctcomputation/checkedc-clang/issues/374#issuecomment-804283984). +// So the "inline_anon_structs.c, inline_anon_structs_cross_tu.c" order should +// catch most problems that occur when 3C makes the decisions in one translation +// unit but does the rewriting in another. We still test the other order for +// completeness. + +// RUN: rm -rf %t* + +// Tests of the "inline_anon_structs.c, inline_anon_structs_cross_tu.c" order +// (called "AB" for short). +// +// Note about check prefixes: We currently don't bother with a Cartesian product +// of {ALL,NOALL} x {AB,BA} because we don't have any tests whose results depend +// on both variables. We do pass CHECK_NOALL and CHECK_ALL to +// inline_anon_structs_cross_tu.c for uniformity, even though that file +// currently doesn't use either. +// +// RUN: 3c -base-dir=%S -addcr -alltypes -output-dir=%t.checkedALL_AB %S/inline_anon_structs.c %s -- +// RUN: FileCheck -match-full-lines -check-prefixes="CHECK_ALL","CHECK","CHECK_AB" --input-file %t.checkedALL_AB/inline_anon_structs.c %S/inline_anon_structs.c +// RUN: FileCheck -match-full-lines -check-prefixes="CHECK_ALL","CHECK","CHECK_AB" --input-file %t.checkedALL_AB/inline_anon_structs_cross_tu.c %s +// RUN: 3c -base-dir=%S -addcr -output-dir=%t.checkedNOALL_AB %S/inline_anon_structs.c %s -- +// RUN: FileCheck -match-full-lines -check-prefixes="CHECK_NOALL","CHECK","CHECK_AB" --input-file %t.checkedNOALL_AB/inline_anon_structs.c %S/inline_anon_structs.c +// RUN: FileCheck -match-full-lines -check-prefixes="CHECK_NOALL","CHECK","CHECK_AB" --input-file %t.checkedNOALL_AB/inline_anon_structs_cross_tu.c %s + +// Tests of the "inline_anon_structs_cross_tu.c, inline_anon_structs.c" order +// (called "BA"). +// +// RUN: 3c -base-dir=%S -addcr -alltypes -output-dir=%t.checkedALL_BA %s %S/inline_anon_structs.c -- +// RUN: FileCheck -match-full-lines -check-prefixes="CHECK_ALL","CHECK","CHECK_BA" --input-file %t.checkedALL_BA/inline_anon_structs.c %S/inline_anon_structs.c +// RUN: FileCheck -match-full-lines -check-prefixes="CHECK_ALL","CHECK","CHECK_BA" --input-file %t.checkedALL_BA/inline_anon_structs_cross_tu.c %s +// RUN: 3c -base-dir=%S -addcr -output-dir=%t.checkedNOALL_BA %s %S/inline_anon_structs.c -- +// RUN: FileCheck -match-full-lines -check-prefixes="CHECK_NOALL","CHECK","CHECK_BA" --input-file %t.checkedNOALL_BA/inline_anon_structs.c %S/inline_anon_structs.c +// RUN: FileCheck -match-full-lines -check-prefixes="CHECK_NOALL","CHECK","CHECK_BA" --input-file %t.checkedNOALL_BA/inline_anon_structs_cross_tu.c %s + +void second_tu_fn(void) { + // In the AB order, the global `cross_tu_numbering_test` variable in + // inline_anon_structs.c will be seen first and its struct will be named + // cross_tu_numbering_test_struct_1, and this struct will be named + // cross_tu_numbering_test_struct_2. In the BA order, this code is seen before + // the `#include "inline_anon_structs.c"`, so this struct will take the + // cross_tu_numbering_test_struct_1 name and the inline_anon_structs.c one + // will be cross_tu_numbering_test_struct_2. + // + // Note that in order to have two different variables with the same name (in + // order to produce a collision in struct names), we have to put the variables + // in different scopes: in this case, global and function-local. + struct { int y; } *cross_tu_numbering_test; + //CHECK_AB: struct cross_tu_numbering_test_struct_2 { int y; }; + //CHECK_AB-NEXT: _Ptr cross_tu_numbering_test = ((void *)0); + //CHECK_BA: struct cross_tu_numbering_test_struct_1 { int y; }; + //CHECK_BA-NEXT: _Ptr cross_tu_numbering_test = ((void *)0); +} + +#include "inline_anon_structs.c" diff --git a/clang/test/3C/itypes_for_extern.c b/clang/test/3C/itypes_for_extern.c index 6566cc1e25bb..650b926b8c31 100644 --- a/clang/test/3C/itypes_for_extern.c +++ b/clang/test/3C/itypes_for_extern.c @@ -46,9 +46,12 @@ void fn_typedef_test(fn f) {} struct foo { int *a; void (*fn)(int *); + int *b, **c; }; //CHECK: int *a : itype(_Ptr); //CHECK: void ((*fn)(int *)) : itype(_Ptr)>); +//CHECK: int *b : itype(_Ptr); +//CHECK: int **c : itype(_Ptr<_Ptr>); int *glob = 0; extern int *extern_glob = 0; @@ -91,6 +94,9 @@ void has_itype1(int *a : itype(_Ptr)) { a = 0; } // part of the type (the array size) occurs after the name of the variable // being declared. This complicates rewriting. These examples caused errors in // libjpeg. +// +// multivardecls_complex_types.c has tests of some similar cases as part of +// multi-decls, with and without -itypes-for-extern. int const_arr0[10]; //CHECK_ALL: int const_arr0[10] : itype(int _Checked[10]); @@ -117,7 +123,8 @@ void const_arr_fn(int a[10]) {} // Rewriting an existing itype or bounds expression on a global variable. Doing // this correctly requires replacing text until the end of the Checked C -// annotation expression. +// annotation expression. The m_* and s_* tests in multivardecls_complex_types.c +// test some similar cases in combination with multi-decls. int *a : itype(_Ptr); int **b : itype(_Ptr); int *c : count(2); diff --git a/clang/test/3C/macro_end_of_decl.c b/clang/test/3C/macro_end_of_decl.c index 8cc7790ef82d..d4e7daa0efd7 100644 --- a/clang/test/3C/macro_end_of_decl.c +++ b/clang/test/3C/macro_end_of_decl.c @@ -34,13 +34,13 @@ int e SIZE[1]; #define EQ = int *f EQ 0; -//CHECK: _Ptr f = 0; +//CHECK: _Ptr f EQ 0; int(*g0) ARGS, g1 SIZE, *g2 EQ 0; //CHECK: _Ptr g0 = ((void *)0); //CHECK_NOALL: int g1[1]; //CHECK_ALL: int g1 _Checked SIZE; -//CHECK: _Ptr g2 = 0; +//CHECK: _Ptr g2 EQ 0; #define RPAREN ) int * h ( int *a RPAREN { diff --git a/clang/test/3C/multivardecls.c b/clang/test/3C/multivardecls.c index 629554c10c87..476ea1840676 100644 --- a/clang/test/3C/multivardecls.c +++ b/clang/test/3C/multivardecls.c @@ -130,7 +130,22 @@ int *d, e, **f; // CHECK: int e; // CHECK: _Ptr<_Ptr> f = ((void *)0); +// Simple test that storage and type qualifiers are preserved on both global and +// function-scope variables. + +static const int *sd, se, **sf; +// CHECK: static _Ptr sd = ((void *)0); +// CHECK: static const int se; +// CHECK: static _Ptr<_Ptr> sf = ((void *)0); + void test5() { + static const int *fsd, fse, **fsf; + // CHECK: static _Ptr fsd = ((void *)0); + // CHECK: static const int fse; + // CHECK: static _Ptr<_Ptr> fsf = ((void *)0); +} + +void test6() { int *a, *b; int *c, *e; struct foo { @@ -148,3 +163,54 @@ void test5() { // CHECK: _Ptr c; // CHECK: _Ptr d; // CHECK: }; + +void test7() { + // Test that variables that require struct initialization honor base type + // renames the same way as global variables. + struct { int *x; } s7; + //CHECK: struct s7_struct_1 { _Ptr x; }; + //CHECK: struct s7_struct_1 s7 = {}; +} + +// Test that getNextComma doesn't falsely trigger on commas inside a bounds +// annotation. The scan shouldn't start until after the declaration source +// range, which should include the bounds annotation, and it's unlikely that a +// change to 3C could break that without also breaking other tests, but it +// doesn't hurt to have a specific test for commas too. The extra nested comma +// expression `(0, lo)` was needed to trigger the bug in older versions of 3C: +// the lexer didn't seem to report the comma that is part of the `bounds` +// construct to getNextComma as a comma token. +// +// `p3` is needed to trigger the multi-decl to be broken up at all. +_Array_ptr lo, hi; +_Array_ptr p1 : bounds((0, lo), hi), p2 : bounds(lo, (0, hi)), *p3; +//CHECK: _Array_ptr p1 : bounds((0, lo), hi); +// The extra space after `0` seems to be because Decl::print treats the comma +// operator like any other binary operator such as `+` and adds spaces both +// before and after it. (TODO: Research whether this has already been discussed +// in upstream Clang and if not, file a bug there?) +//CHECK: _Array_ptr p2 : bounds(lo, (0 , hi)); +//CHECK: _Ptr<_Array_ptr> p3 = ((void *)0); + +// Simple tests of typedef multi-decls from +// https://github.com/correctcomputation/checkedc-clang/issues/651. +// inline_anon_structs.c has a few additional tests of typedef multi-decls +// involving inline structs. + +typedef int *A, *B; +// CHECK: typedef _Ptr A; +// CHECK: typedef _Ptr B; + +void foo(void) { + A a; + B b; +} + +typedef int *C, *D; +// CHECK: typedef _Ptr C; +// CHECK: typedef int *D; + +void bar(void) { + C c; + D d = (D)1; +} diff --git a/clang/test/3C/multivardecls_complex_types.c b/clang/test/3C/multivardecls_complex_types.c new file mode 100644 index 000000000000..2c5527a23a87 --- /dev/null +++ b/clang/test/3C/multivardecls_complex_types.c @@ -0,0 +1,105 @@ +// A few tests of multi-decls with complex types, with and without +// -itypes-for-extern. These tests cannot be included in multivardecls.c because +// compiling that entire file with -itypes-for-extern -addcr would produce an +// unrelated error in the typedef multi-decl test: see the first example in +// https://github.com/correctcomputation/checkedc-clang/issues/740. + +// RUN: rm -rf %t* + +// RUN: 3c -base-dir=%S -addcr -alltypes %s -- | FileCheck -match-full-lines -check-prefixes="CHECK_ALL","CHECK" %s +// RUN: 3c -base-dir=%S -addcr %s -- | FileCheck -match-full-lines -check-prefixes="CHECK_NOALL","CHECK" %s +// RUN: 3c -base-dir=%S -addcr %s -- | %clang -c -fcheckedc-extension -x c -o /dev/null - +// RUN: 3c -base-dir=%S -alltypes -output-dir=%t.checked %s -- +// RUN: 3c -base-dir=%t.checked -alltypes %t.checked/multivardecls_complex_types.c -- | diff %t.checked/multivardecls_complex_types.c - + +// RUN: 3c -base-dir=%S -itypes-for-extern -alltypes -addcr %s -- | FileCheck -match-full-lines -check-prefixes="CHECK_ITE_ALL","CHECK_ITE" %s +// RUN: 3c -base-dir=%S -itypes-for-extern -addcr %s -- | FileCheck -match-full-lines -check-prefixes="CHECK_ITE_NOALL","CHECK_ITE" %s +// RUN: 3c -base-dir=%S -itypes-for-extern -alltypes -addcr %s -- | %clang -c -fcheckedc-extension -x c -o /dev/null - +// RUN: 3c -base-dir=%S -itypes-for-extern -alltypes -output-dir=%t.checked_ITE %s -- +// RUN: 3c -base-dir=%t.checked_ITE -itypes-for-extern -alltypes %t.checked_ITE/multivardecls_complex_types.c -- | diff %t.checked_ITE/multivardecls_complex_types.c - + +// A multi-decl with a mix of pointers and arrays, including an "unchecked +// pointer to constant size array" member that would trigger a known bug in +// mkString (item 5 of +// https://github.com/correctcomputation/checkedc-clang/issues/703), +// demonstrating that unchanged multi-decl members whose base type wasn't +// renamed use Decl::print (which doesn't have this bug). +// +// `m_implicit_itype` and `m_change_with_bounds` together test that 3C includes +// Checked C annotations in the range to be replaced (for both changed and +// unchanged multi-decl members) rather than leaving them to duplicate the +// annotations in the newly inserted declaration. +int m_const_arr0[10], *m_const_arr1[10], (*m_p_const_arr_wild)[10] = 1, + (*m_p_const_arr_chk)[10], *m_implicit_itype : count(2), + **m_change_with_bounds : count(2); +//CHECK_ALL: int m_const_arr0 _Checked[10]; +//CHECK_NOALL: int m_const_arr0[10]; +//CHECK_ALL: _Ptr m_const_arr1 _Checked[10] = {((void *)0)}; +// The reason this isn't `_Ptr m_const_arr1[10]` is probably the "outer +// wild -> inner wild" constraint +// (https://github.com/correctcomputation/checkedc-clang/issues/656). +//CHECK_NOALL: int *m_const_arr1[10]; +//CHECK: int (*m_p_const_arr_wild)[10] = 1; +//CHECK_ALL: _Ptr m_p_const_arr_chk = ((void *)0); +//CHECK_NOALL: _Ptr m_p_const_arr_chk = ((void *)0); +// 3C doesn't have proper support for itypes on variables: if a variable has an +// existing itype, 3C uses the checked side as the variable's original type. So +// 3C treats m_implicit_itype as having original type _Array_ptr, but since +// the solved type is the same, 3C uses Decl::print for the unchanged multi-decl +// member and preserves the original declaration with the itype. When 3C gains +// proper itype support for variables, it should generate an actual rewrite to +// the fully checked type if nothing else in the program prevents it from doing +// so. +//CHECK: int *m_implicit_itype : count(2); +// In this case, the solved type changes and shows up in the output. +//CHECK: _Array_ptr<_Ptr> m_change_with_bounds : count(2) = ((void *)0); + +// Test the same multi-decl with -itypes-for-extern. +//CHECK_ITE_ALL: int m_const_arr0[10] : itype(int _Checked[10]); +//CHECK_ITE_NOALL: int m_const_arr0[10]; +//CHECK_ITE_ALL: int *m_const_arr1[10] : itype(_Ptr _Checked[10]) = {((void *)0)}; +//CHECK_ITE_NOALL: int *m_const_arr1[10]; +//CHECK_ITE: int (*m_p_const_arr_wild)[10] = 1; +//CHECK_ITE_ALL: int (*m_p_const_arr_chk)[10] : itype(_Ptr) = ((void *)0); +//CHECK_ITE_NOALL: int (*m_p_const_arr_chk)[10] : itype(_Ptr) = ((void *)0); +//CHECK_ITE: int *m_implicit_itype : count(2); +//CHECK_ITE: int **m_change_with_bounds : itype(_Array_ptr<_Ptr>) count(2) = ((void *)0); + +// A similar multi-decl with an unnamed inline struct, which forces the use of +// mkString. We can't include (*s_p_const_arr_*)[10] because it would trigger +// the previously mentioned mkString bug and produce output that doesn't +// compile. `s` serves just to give the struct a shorter generated name. +struct { int *x; } s, s_const_arr0[10], *s_const_arr1[10], + // The only way a variable of unnamed struct type can have an itype is if it + // comes implicitly from a bounds annotation, since we have no name to refer + // to the struct in a written itype. + *s_implicit_itype : count(2), **s_change_with_bounds : count(2); +//CHECK: struct s_struct_1 { _Ptr x; }; +//CHECK: struct s_struct_1 s; +//CHECK_ALL: struct s_struct_1 s_const_arr0 _Checked[10]; +//CHECK_NOALL: struct s_struct_1 s_const_arr0[10]; +//CHECK_ALL: _Ptr s_const_arr1 _Checked[10] = {((void *)0)}; +//CHECK_NOALL: struct s_struct_1 *s_const_arr1[10]; +// Like with m_implicit_itype above, 3C treats s_implicit_itype as having type +// _Array_ptr, but now 3C uses mkString and that type +// actually shows up in the output: not a great result, but at least we test +// that it isn't any worse and that the bounds annotation is preserved. Since +// s_implicit_itype is now the only member of its "multi-decl", if the user +// manually edits it back to an itype, there won't be another multi-decl breakup +// to cause 3C to mess it up again. +//CHECK: _Array_ptr s_implicit_itype : count(2); +//CHECK: _Array_ptr<_Ptr> s_change_with_bounds : count(2) = ((void *)0); + +// Test the same multi-decl with -itypes-for-extern. +//CHECK_ITE: struct s_struct_1 { int *x : itype(_Ptr); }; +//CHECK_ITE: struct s_struct_1 s; +//CHECK_ITE_ALL: struct s_struct_1 s_const_arr0[10] : itype(struct s_struct_1 _Checked[10]); +//CHECK_ITE_NOALL: struct s_struct_1 s_const_arr0[10]; +//CHECK_ITE_ALL: struct s_struct_1 *s_const_arr1[10] : itype(_Ptr _Checked[10]) = {((void *)0)}; +//CHECK_ITE_NOALL: struct s_struct_1 *s_const_arr1[10]; +// The type of s_implicit_type is still loaded as _Array_ptr, +// but it is downgraded back to an itype by -itypes-for-extern. As long as 3C +// lacks real support for itypes on variables, this is probably the behavior we +// want with -itypes-for-extern in this very unusual case. +//CHECK_ITE: struct s_struct_1 *s_implicit_itype : itype(_Array_ptr) count(2); +//CHECK_ITE: struct s_struct_1 **s_change_with_bounds : itype(_Array_ptr<_Ptr>) count(2) = ((void *)0); diff --git a/clang/test/3C/partial_checked.c b/clang/test/3C/partial_checked.c index 30ffca85563c..952ca2768f6b 100644 --- a/clang/test/3C/partial_checked.c +++ b/clang/test/3C/partial_checked.c @@ -62,6 +62,21 @@ void test4() { // CHECK: _Ptr<_Ptr (void)> n = 0; } +// Test the partial workaround in getDeclSourceRangeWithAnnotations for a +// compiler bug where DeclaratorDecl::getSourceRange gives the wrong answer for +// certain checked pointer types. Previously, if the variable had an +// initializer, 3C used the start of the initializer as the end location of the +// rewrite, which had the side effect of working around the bug for all +// variables with an initializer (such as `m` above). Any variable with an +// affected type and no initializer would trigger the bug; apparently we never +// noticed because 3C unnecessarily adds initializers to global variables +// (https://github.com/correctcomputation/checkedc-clang/issues/741). Now, for +// uniformity, 3C always uses DeclaratorDecl::getSourceRange to get the range +// excluding any initializer, so it needs a workaround specifically for the bug. +// See getDeclSourceRangeWithAnnotations for more information. +_Ptr gm; +// CHECK: _Ptr<_Ptr (void)> gm = ((void *)0); + void test5(_Ptr a, _Ptr b, _Ptr<_Ptr> c, int **d) { // CHECK: void test5(_Ptr<_Ptr> a, _Ptr b : itype(_Ptr<_Ptr>), _Ptr<_Ptr> c, _Ptr<_Ptr> d) { *b = 1; diff --git a/clang/test/3C/qualifiers.c b/clang/test/3C/qualifiers.c index b85b77e2b2a2..4d3ccde7d976 100644 --- a/clang/test/3C/qualifiers.c +++ b/clang/test/3C/qualifiers.c @@ -64,7 +64,7 @@ void structs() { const extern struct qualifier_struct d; } //CHECK: struct qualifier_struct a0 = {}; -//CHECK: static struct qualifier_struct a = {}; -//CHECK: static volatile struct qualifier_struct b = {}; +//CHECK: static struct qualifier_struct a; +//CHECK: volatile static struct qualifier_struct b; //CHECK: static _Ptr c = ((void *)0); //CHECK: const extern struct qualifier_struct d; diff --git a/clang/test/3C/root_cause.c b/clang/test/3C/root_cause.c index 17811a8c7820..74c5f86ffe03 100644 --- a/clang/test/3C/root_cause.c +++ b/clang/test/3C/root_cause.c @@ -46,9 +46,9 @@ void test1() { union u { // unwritable-expected-warning@+1 {{0 unchecked pointers: Source code in non-writable file}} - int *a; // expected-warning {{1 unchecked pointer: Union or external struct field encountered}} + int *a; // expected-warning {{1 unchecked pointer: Union field encountered}} // unwritable-expected-warning@+1 {{0 unchecked pointers: Source code in non-writable file}} - int *b; // expected-warning {{1 unchecked pointer: Union or external struct field encountered}} + int *b; // expected-warning {{1 unchecked pointer: Union field encountered}} }; void (*c)(void); // unwritable-expected-warning {{0 unchecked pointers: Source code in non-writable file}} @@ -75,19 +75,6 @@ void (*void_star_fptr)(void *); // expected-warning {{1 unchecked pointer: Defau // unwritable-expected-warning@+1 {{ 0 unchecked pointers: Source code in non-writable file}} void void_star_fn(void *p); // expected-warning {{1 unchecked pointer: Default void* type}} -typedef struct { - int x; - float f; -// unwritable-expected-warning@+1 {{0 unchecked pointers: Source code in non-writable file}} -} A, *PA; -// expected-warning@-1 {{2 unchecked pointers: Unable to rewrite a typedef with multiple names}} -// Two pointers affected by the above root cause. Do not count the typedef -// itself as an affected pointer even though that's where the star is written. -// Count each of the variables below even though no star is actually written. -// unwritable-expected-warning@+2 {{0 unchecked pointers: Source code in non-writable file}} -// unwritable-expected-warning@+1 {{0 unchecked pointers: Source code in non-writable file}} -PA pa_test0, pa_test1; - // unwritable-expected-warning@+2 {{0 unchecked pointers: Source code in non-writable file}} // expected-warning@+1 {{1 unchecked pointer: Internal constraint for generic function declaration, for which 3C currently does not support re-solving.}} _Itype_for_any(T) void remember(void *p : itype(_Ptr)) {} From 79804ee066f04e44583dd6df2dbe883b466aef5d Mon Sep 17 00:00:00 2001 From: John Kastner Date: Thu, 2 Dec 2021 11:07:25 -0500 Subject: [PATCH 37/38] Lower Bounds Inference (#718) This pull requests extends array bounds inference to support inferring lower bounds for array pointers and inserting using Checked C range bounds. For example: char simple_lower_bound(int *a, int l) { int *b = a; while (b - a < l && *b != 42) b++; return b - a < l; } 3C can now infer bounds for b even though a standard count bound would be invalidated by the increment b++. char simple_lower_bound(_Array_ptr a : count(l), int l) { _Array_ptr b : bounds(a, a + l) = a; while (b - a < l && *b != 42) b++; return b - a < l; } The inference is also able to automatically fatten pointers by generating lower bounds where none exists in the source code. Co-authored-by: Matt McCutchen (Correct Computation) --- clang/include/clang/3C/ABounds.h | 122 ++-- clang/include/clang/3C/AVarBoundsInfo.h | 117 +++- clang/include/clang/3C/DeclRewriter.h | 50 +- clang/include/clang/3C/LowerBoundAssignment.h | 83 +++ clang/include/clang/3C/ProgramInfo.h | 3 + clang/include/clang/3C/ProgramVar.h | 3 - clang/include/clang/3C/RewriteUtils.h | 63 +- clang/include/clang/3C/Utils.h | 15 +- clang/lib/3C/3C.cpp | 5 + clang/lib/3C/ABounds.cpp | 93 ++- clang/lib/3C/AVarBoundsInfo.cpp | 601 ++++++++++++------ clang/lib/3C/AVarGraph.cpp | 4 +- clang/lib/3C/ArrayBoundsInferenceConsumer.cpp | 2 +- clang/lib/3C/CMakeLists.txt | 1 + clang/lib/3C/ConstraintBuilder.cpp | 3 + clang/lib/3C/ConstraintResolver.cpp | 31 +- clang/lib/3C/CtxSensAVarBounds.cpp | 17 +- clang/lib/3C/DeclRewriter.cpp | 321 +++++++--- clang/lib/3C/LowerBoundAssignment.cpp | 151 +++++ clang/lib/3C/ProgramInfo.cpp | 8 + clang/lib/3C/RewriteUtils.cpp | 73 ++- clang/lib/3C/StructInit.cpp | 2 +- clang/lib/3C/Utils.cpp | 20 + clang/test/3C/basic.c | 2 +- clang/test/3C/generated_tests/arrboth.c | 5 +- clang/test/3C/generated_tests/arrbothmulti2.c | 5 +- clang/test/3C/generated_tests/arrcallee.c | 5 +- .../test/3C/generated_tests/arrcalleemulti2.c | 5 +- clang/test/3C/generated_tests/arrcaller.c | 5 +- .../test/3C/generated_tests/arrcallermulti1.c | 3 +- .../test/3C/generated_tests/arrcallermulti2.c | 2 +- .../test/3C/generated_tests/arrofstructboth.c | 3 +- .../generated_tests/arrofstructbothmulti2.c | 3 +- .../3C/generated_tests/arrofstructcallee.c | 3 +- .../generated_tests/arrofstructcalleemulti2.c | 3 +- .../3C/generated_tests/arrofstructcaller.c | 3 +- .../generated_tests/arrofstructcallermulti1.c | 3 +- .../3C/generated_tests/arrofstructprotoboth.c | 3 +- .../generated_tests/arrofstructprotocallee.c | 3 +- .../generated_tests/arrofstructprotocaller.c | 3 +- clang/test/3C/generated_tests/arrprotoboth.c | 5 +- .../test/3C/generated_tests/arrprotocallee.c | 5 +- .../test/3C/generated_tests/arrprotocaller.c | 5 +- clang/test/3C/generated_tests/arrprotosafe.c | 2 +- clang/test/3C/generated_tests/arrsafe.c | 2 +- clang/test/3C/generated_tests/arrsafemulti2.c | 2 +- clang/test/3C/generated_tests/arrstructboth.c | 3 +- .../3C/generated_tests/arrstructbothmulti2.c | 3 +- .../test/3C/generated_tests/arrstructcallee.c | 3 +- .../generated_tests/arrstructcalleemulti2.c | 3 +- .../test/3C/generated_tests/arrstructcaller.c | 3 +- .../generated_tests/arrstructcallermulti1.c | 3 +- .../3C/generated_tests/arrstructprotoboth.c | 3 +- .../3C/generated_tests/arrstructprotocallee.c | 3 +- .../3C/generated_tests/arrstructprotocaller.c | 3 +- clang/test/3C/generated_tests/fptrarrboth.c | 3 +- .../3C/generated_tests/fptrarrbothmulti2.c | 3 +- clang/test/3C/generated_tests/fptrarrcallee.c | 3 +- .../3C/generated_tests/fptrarrcalleemulti2.c | 3 +- clang/test/3C/generated_tests/fptrarrcaller.c | 3 +- .../3C/generated_tests/fptrarrcallermulti1.c | 3 +- .../3C/generated_tests/fptrarrprotoboth.c | 3 +- .../3C/generated_tests/fptrarrprotocallee.c | 3 +- .../3C/generated_tests/fptrarrprotocaller.c | 3 +- clang/test/3C/generated_tests/fptrsafeboth.c | 3 +- .../3C/generated_tests/fptrsafebothmulti2.c | 3 +- .../test/3C/generated_tests/fptrsafecallee.c | 3 +- .../3C/generated_tests/fptrsafecalleemulti2.c | 3 +- .../3C/generated_tests/fptrsafeprotoboth.c | 3 +- .../3C/generated_tests/fptrsafeprotocallee.c | 3 +- clang/test/3C/generated_tests/ptrTOptrboth.c | 3 +- .../3C/generated_tests/ptrTOptrbothmulti2.c | 3 +- .../test/3C/generated_tests/ptrTOptrcallee.c | 3 +- .../3C/generated_tests/ptrTOptrcalleemulti2.c | 3 +- .../test/3C/generated_tests/ptrTOptrcaller.c | 3 +- .../3C/generated_tests/ptrTOptrcallermulti1.c | 3 +- .../3C/generated_tests/ptrTOptrprotoboth.c | 3 +- .../3C/generated_tests/ptrTOptrprotocallee.c | 3 +- .../3C/generated_tests/ptrTOptrprotocaller.c | 3 +- .../test/3C/generated_tests/safefptrargboth.c | 3 +- .../generated_tests/safefptrargbothmulti2.c | 3 +- .../3C/generated_tests/safefptrargcallee.c | 3 +- .../generated_tests/safefptrargcalleemulti2.c | 3 +- .../3C/generated_tests/safefptrargcaller.c | 3 +- .../generated_tests/safefptrargcallermulti1.c | 3 +- .../3C/generated_tests/safefptrargprotoboth.c | 3 +- .../generated_tests/safefptrargprotocallee.c | 3 +- .../generated_tests/safefptrargprotocaller.c | 3 +- .../3C/generated_tests/unsafefptrargboth.c | 3 +- .../generated_tests/unsafefptrargbothmulti2.c | 3 +- .../3C/generated_tests/unsafefptrargcallee.c | 3 +- .../unsafefptrargcalleemulti2.c | 3 +- .../3C/generated_tests/unsafefptrargcaller.c | 3 +- .../unsafefptrargcallermulti1.c | 3 +- .../generated_tests/unsafefptrargprotoboth.c | 3 +- .../unsafefptrargprotocallee.c | 3 +- .../unsafefptrargprotocaller.c | 3 +- clang/test/3C/itypes_for_extern.c | 25 + clang/test/3C/liberal_itypes_ptr.c | 2 +- clang/test/3C/range_bounds.c | 257 ++++++++ clang/test/3C/range_bounds_flow.c | 203 ++++++ clang/test/3C/realloc.c | 2 +- clang/test/3C/realloc_complex.c | 4 +- clang/test/3C/return_not_least.c | 4 +- clang/test/3C/testgenerator.py | 41 +- 105 files changed, 2006 insertions(+), 553 deletions(-) create mode 100644 clang/include/clang/3C/LowerBoundAssignment.h create mode 100644 clang/lib/3C/LowerBoundAssignment.cpp create mode 100644 clang/test/3C/range_bounds.c create mode 100644 clang/test/3C/range_bounds_flow.c diff --git a/clang/include/clang/3C/ABounds.h b/clang/include/clang/3C/ABounds.h index 9b6d883b4c7d..015907eedbf6 100644 --- a/clang/include/clang/3C/ABounds.h +++ b/clang/include/clang/3C/ABounds.h @@ -32,17 +32,26 @@ class ABounds { CountPlusOneBoundKind, // Bounds that represent number of bytes. ByteBoundKind, - // Bounds that represent range. - RangeBoundKind, }; BoundsKind getKind() const { return Kind; } protected: + ABounds(BoundsKind K, BoundsKey L, BoundsKey B) : Kind(K), LengthKey(L), + LowerBoundKey(B) {} + ABounds(BoundsKind K, BoundsKey L) : ABounds(K, L, 0) {} + BoundsKind Kind; -protected: - ABounds(BoundsKind K) : Kind(K) {} - void addBoundsUsedKey(BoundsKey); + // Bounds key representing the length of the bounds from the base pointer of + // the range. The exact interpretation of this field varies by subclass. + BoundsKey LengthKey; + + // The base pointer representing the start of the range of the bounds. If this + // is not equal to 0, then this ABounds has a specific lower bound that should + // be used when emitting array pointer bounds. Otherwise, if it is 0, then the + // lower bound should implicitly be the pointer the bound is applied to. + BoundsKey LowerBoundKey; + // Get the variable name of the the given bounds key that corresponds // to the given declaration. static std::string getBoundsKeyStr(BoundsKey, AVarBoundsInfo *, @@ -51,15 +60,30 @@ class ABounds { public: virtual ~ABounds() {} - virtual std::string mkString(AVarBoundsInfo *, clang::Decl *D = nullptr) = 0; + // Make a string representation of this array bound. If the array has a + // defined lower bound pointer that is not the same as the pointer for which + // the bound string is being generated (passed as parameter BK), then a range + // bound is generated using that lower bound. Otherwise, a standard count + // bound is generated. + std::string + mkString(AVarBoundsInfo *ABI, clang::Decl *D = nullptr, BoundsKey BK = 0); + + // Make a string representation of this array bound always generating explicit + // lower bounds in range bounds expressions. + virtual std::string + mkStringWithLowerBound(AVarBoundsInfo *ABI, clang::Decl *D) = 0; + + // Make a string representation of this array bound ignoring any lower bound + // information. A standard count bound is always generated. + virtual std::string + mkStringWithoutLowerBound(AVarBoundsInfo *ABI, clang::Decl *D) = 0; + virtual bool areSame(ABounds *, AVarBoundsInfo *) = 0; - virtual BoundsKey getBKey() = 0; virtual ABounds *makeCopy(BoundsKey NK) = 0; - // Set that maintains all the bound keys that are used inin - // TODO: Is this still needed? - static std::set KeysUsedInBounds; - static bool isKeyUsedInBounds(BoundsKey ToCheck); + BoundsKey getLengthKey() const { return LengthKey; } + BoundsKey getLowerBoundKey() const { return LowerBoundKey; } + void setLowerBoundKey(BoundsKey LB) { LowerBoundKey = LB; } static ABounds *getBoundsInfo(AVarBoundsInfo *AVBInfo, BoundsExpr *BExpr, const ASTContext &C); @@ -67,34 +91,38 @@ class ABounds { class CountBound : public ABounds { public: - CountBound(BoundsKey Var) : ABounds(CountBoundKind), CountVar(Var) { - addBoundsUsedKey(Var); - } + CountBound(BoundsKey L, BoundsKey B) : ABounds(CountBoundKind, L, B) {} + CountBound(BoundsKey L) : ABounds(CountBoundKind, L) {} - ~CountBound() override {} + std::string + mkStringWithLowerBound(AVarBoundsInfo *ABI, clang::Decl *D) override; + std::string + mkStringWithoutLowerBound(AVarBoundsInfo *ABI, clang::Decl *D) override; - std::string mkString(AVarBoundsInfo *ABI, clang::Decl *D = nullptr) override; bool areSame(ABounds *O, AVarBoundsInfo *ABI) override; - BoundsKey getBKey() override; + ABounds *makeCopy(BoundsKey NK) override; static bool classof(const ABounds *S) { return S->getKind() == CountBoundKind; } - - BoundsKey getCountVar() { return CountVar; } - -protected: - BoundsKey CountVar; }; class CountPlusOneBound : public CountBound { public: - CountPlusOneBound(BoundsKey Var) : CountBound(Var) { + CountPlusOneBound(BoundsKey L, BoundsKey B) : CountBound(L, B) { this->Kind = CountPlusOneBoundKind; } - std::string mkString(AVarBoundsInfo *ABI, clang::Decl *D = nullptr) override; + CountPlusOneBound(BoundsKey L) : CountBound(L) { + this->Kind = CountPlusOneBoundKind; + } + + std::string + mkStringWithLowerBound(AVarBoundsInfo *ABI, clang::Decl *D) override; + std::string + mkStringWithoutLowerBound(AVarBoundsInfo *ABI, clang::Decl *D) override; + bool areSame(ABounds *O, AVarBoundsInfo *ABI) override; static bool classof(const ABounds *S) { @@ -104,54 +132,20 @@ class CountPlusOneBound : public CountBound { class ByteBound : public ABounds { public: - ByteBound(BoundsKey Var) : ABounds(ByteBoundKind), ByteVar(Var) { - addBoundsUsedKey(Var); - } + ByteBound(BoundsKey L, BoundsKey B) : ABounds(ByteBoundKind, L, B) {} + ByteBound(BoundsKey L) : ABounds(ByteBoundKind, L) {} - ~ByteBound() override {} + std::string + mkStringWithLowerBound(AVarBoundsInfo *ABI, clang::Decl *D) override; + std::string + mkStringWithoutLowerBound(AVarBoundsInfo *ABI, clang::Decl *D) override; - std::string mkString(AVarBoundsInfo *ABI, clang::Decl *D = nullptr) override; bool areSame(ABounds *O, AVarBoundsInfo *ABI) override; - BoundsKey getBKey() override; ABounds *makeCopy(BoundsKey NK) override; static bool classof(const ABounds *S) { return S->getKind() == ByteBoundKind; } - BoundsKey getByteVar() { return ByteVar; } - -private: - BoundsKey ByteVar; }; -class RangeBound : public ABounds { -public: - RangeBound(BoundsKey L, BoundsKey R) : ABounds(RangeBoundKind), LB(L), UB(R) { - addBoundsUsedKey(L); - addBoundsUsedKey(R); - } - - ~RangeBound() override {} - - std::string mkString(AVarBoundsInfo *ABI, clang::Decl *D = nullptr) override; - bool areSame(ABounds *O, AVarBoundsInfo *ABI) override; - - BoundsKey getBKey() override { - assert(false && "Not implemented."); - return 0; - } - - ABounds *makeCopy(BoundsKey NK) override { - assert(false && "Not Implemented"); - return nullptr; - } - - static bool classof(const ABounds *S) { - return S->getKind() == RangeBoundKind; - } - -private: - BoundsKey LB; - BoundsKey UB; -}; #endif // LLVM_CLANG_3C_ABOUNDS_H diff --git a/clang/include/clang/3C/AVarBoundsInfo.h b/clang/include/clang/3C/AVarBoundsInfo.h index bba1ee384d1f..34151501c63d 100644 --- a/clang/include/clang/3C/AVarBoundsInfo.h +++ b/clang/include/clang/3C/AVarBoundsInfo.h @@ -148,7 +148,7 @@ class AvarBoundsInference { // BoundsKey that failed the flow inference. std::set BKsFailedFlowInference; - static ABounds *getPreferredBound(const BndsKindMap &BKindMap); + ABounds *getPreferredBound(BoundsKey BK); }; // Class that maintains information about potential bounds for @@ -181,7 +181,8 @@ class AVarBoundsInfo { public: AVarBoundsInfo() : ProgVarGraph(this), CtxSensProgVarGraph(this), - RevCtxSensProgVarGraph(this), CSBKeyHandler(this) { + RevCtxSensProgVarGraph(this), CSBKeyHandler(this), + LowerBoundGraph(this) { BCount = 1; PVarInfo.clear(); InProgramArrPtrBoundsKeys.clear(); @@ -231,11 +232,9 @@ class AVarBoundsInfo { // Add Assignments between variables. These methods will add edges between // corresponding BoundsKeys - bool addAssignment(clang::Decl *L, clang::Decl *R); - bool addAssignment(clang::DeclRefExpr *L, clang::DeclRefExpr *R); bool addAssignment(BoundsKey L, BoundsKey R); - bool handlePointerAssignment(clang::Stmt *St, clang::Expr *L, clang::Expr *R, - ASTContext *C, ConstraintResolver *CR); + bool handlePointerAssignment(clang::Expr *L, clang::Expr *R, ASTContext *C, + ConstraintResolver *CR); bool handleAssignment(clang::Expr *L, const CVarSet &LCVars, const std::set &CSLKeys, clang::Expr *R, const CVarSet &RCVars, @@ -252,11 +251,40 @@ class AVarBoundsInfo { // for pointers that has pointer arithmetic performed on them. void recordArithmeticOperation(clang::Expr *E, ConstraintResolver *CR); - // Check if the given bounds key has a pointer arithmetic done on it. - bool hasPointerArithmetic(BoundsKey BK); + // Check if the given bounds key will need to be duplicated during rewriting + // to generate a fresh lower bound. This happens when a pointer is not a valid + // lower bounds due to pointer arithmetic, and lower bounds inference fails to + // find a consistent lower bound among existing pointers in the source code. + bool needsFreshLowerBound(BoundsKey BK); + bool needsFreshLowerBound(ConstraintVariable *CV); + + // Return true when a lower bound could be inferred for the array pointer + // corresponding to `BK`. This is the case either when `BK` was not + // invalidated as lower bound by pointer arithmetic meaning it is it's own + // lower bound, or when `BK` was invalidated, but a valid lower bound could be + // inferred. + bool hasLowerBound(BoundsKey BK); + + // Record that a pointer cannot be rewritten to use range bounds. This might + // be due to 3C rewriting limitations (assignments appearing inside macros), + // or it might be a Checked C limitation (the current style of range bounds + // can't properly initialized on global variables without error). + void markIneligibleForFreshLowerBound(BoundsKey BK); // Get the ProgramVar for the provided VarKey. - ProgramVar *getProgramVar(BoundsKey VK); + // This method can return `nullptr` if there is no corresponding ProgramVar. + // It's not obvious when a BoundsKey can be expected to have a ProgramVar, so + // callers should typically check for null. + ProgramVar *getProgramVar(BoundsKey VK) const; + + // Get the Scope of the provided BoundsKey. + // This method returns nullptr if `getProgramVar(BK)` would return nullptr. + const ProgramVarScope *getProgramVarScope(BoundsKey BK) const; + + // Return true when BoundsKey `To` can be accessed from the scope of `from`. + // Note that this returns false if either BoundsKey cannot be mapped to a + // ProgramVar (and therefore can't be mapped to a scope). + bool isInAccessibleScope(BoundsKey From, BoundsKey To); // Propagate the array bounds information for all array ptrs. void performFlowAnalysis(ProgramInfo *PI); @@ -294,6 +322,13 @@ class AVarBoundsInfo { void addConstantArrayBounds(ProgramInfo &I); + // This is the main entry point to start lower bound inference. It populates + // the map LowerBounds and set NeedFreshLowerBounds with the result of the + // analysis. LowerBounds is accessed during the rest of bounds inference, so + // this method must be executed before performFlowAnalysis which handles the + // majority of the work for length inference. + void inferLowerBounds(ProgramInfo *PI); + private: friend class AvarBoundsInference; friend class CtxSensitiveBoundsKeyHandler; @@ -315,8 +350,17 @@ class AVarBoundsInfo { // Set that contains BoundsKeys of variables which have invalid bounds. std::set InvalidBounds; // These are the bounds key of the pointers that has arithmetic operations - // performed on them. + // performed on them. These pointers cannot have the standard `count(n)` + // bounds and instead must use range bounds with an explict lower bound + // e.g., `bounds(p, p + n)`. std::set ArrPointersWithArithmetic; + + // Some pointers, however, cannot be automatically given range bounds. This + // includes global variables and structure fields. If a pointer is in both the + // above pointer arithmetic set and this set, then it cannot be assigned any + // bound. + std::set IneligibleForFreshLowerBound; + // Set of BoundsKeys that correspond to pointers. std::set PointerBoundsKey; // Set of BoundsKey that correspond to array pointers. @@ -342,6 +386,9 @@ class AVarBoundsInfo { // BiMap of function keys and BoundsKey for function return values. BiMap, BoundsKey> FuncDeclVarMap; + PVConstraint * + getConstraintVariable(const ProgramInfo *PI, BoundsKey BK) const; + // Graph of all program variables. AVarGraph ProgVarGraph; // Graph that contains only edges from normal BoundsKey to @@ -356,6 +403,31 @@ class AVarBoundsInfo { // Context-sensitive bounds key handler CtxSensitiveBoundsKeyHandler CSBKeyHandler; + // This graph is used of for determining which pointers are valid lower + // bounds, and so are eligible for use as their own lower bound (implicitly as + // a count bounds) or as the lower bound for another pointer in a range bound. + // It is also used to infer lower bounds for the pointers that are not + // eligible to be their own lower bound. + AVarGraph LowerBoundGraph; + // In the LowerBoundGraph the constant 0 is used to represent the global + // singleton invalid pointer. + const BoundsKey InvalidLowerBoundKey = 0; + + // BoundsKeys that that cannot be used as a lower bound. These are used in an + // update such as `a = a + 1`, or are transitively assigned from such a + // pointer. + std::set InvalidLowerBounds; + + // Mapping from pointers to their inferred lower bounds. A pointer maps to + // itself if it can use a simple count bound. Missing pointers have no valid + // lower bound, so no length should be inferred during bounds inference. + std::map LowerBounds; + + // Some variables have to valid lower bound in the original source code, but + // we are able to insert a temporary pointer variable to be the lower bound. + // Keep track of these for special handling during rewriting. + std::set NeedFreshLowerBounds; + // BoundsKey helper function: These functions help in getting bounds key from // various artifacts. bool hasVarKey(PersistentSourceLoc &PSL); @@ -389,6 +461,31 @@ class AVarBoundsInfo { void insertParamKey(ParamDeclType ParamDecl, BoundsKey NK); void dumpBounds(); + + // Compute which array pointers are not valid lower bounds. This includes any + // pointers directly updated in pointer arithmetic expression, as well as any + // pointers transitively assigned to from these pointers. This is computed + // using essentially the same algorithm as is used for solving the checked + // type constraint graph. + void computeInvalidLowerBounds(ProgramInfo *PI); + + // During lower bound inference it may be necessary to generate temporary + // pointers to act as lower bounds for arrays that otherwise don't have a + // consistent lower bound. This method takes a bounds key for an array pointer + // and returns a fresh bounds key that can be used as the lower bound for the + // array bounds of that pointer. + BoundsKey getFreshLowerBound(BoundsKey Arr); + + // Return true if the scope of the BoundsKey is one in which lower bounds + // can be inserted. BoundsKeys in context sensitive scope should not get lower + // bounds. The corresponding non-context-sensitive BoundsKey should instead. + bool scopeCanHaveLowerBound(BoundsKey BK); + + // Check if a fresh lower bound can be be inserted by 3C for the pointer + // corresponding to the bounds key. When a pointer needs a fresh lower bound, + // it is possible that 3C will not support inserting the new declaration. + // No array bounds can be inferred for such pointers. + bool isEligibleForFreshLowerBound(BoundsKey BK); }; #endif // LLVM_CLANG_3C_AVARBOUNDSINFO_H diff --git a/clang/include/clang/3C/DeclRewriter.h b/clang/include/clang/3C/DeclRewriter.h index ed41d8feb952..45f08a4b4310 100644 --- a/clang/include/clang/3C/DeclRewriter.h +++ b/clang/include/clang/3C/DeclRewriter.h @@ -34,10 +34,15 @@ class DeclRewriter { // Info parameter are rewritten. static void rewriteDecls(ASTContext &Context, ProgramInfo &Info, Rewriter &R); - static void - buildItypeDecl(PVConstraint *Defn, DeclaratorDecl *Decl, std::string &Type, - std::string &IType, ProgramInfo &Info, - ArrayBoundsRewriter &ABR); + static RewrittenDecl buildItypeDecl(PVConstraint *Defn, DeclaratorDecl *Decl, + std::string UseName, ProgramInfo &Info, + ArrayBoundsRewriter &ABR, + bool GenerateSDecls, bool SDeclChecked); + + static RewrittenDecl + buildCheckedDecl(PVConstraint *Defn, DeclaratorDecl *Decl, + std::string UseName, ProgramInfo &Info, + ArrayBoundsRewriter &ABR, bool GenerateSDecls); private: Rewriter &R; @@ -56,7 +61,14 @@ class DeclRewriter { void rewriteMultiDecl(MultiDeclInfo &MDI, RSet &ToRewrite); void doDeclRewrite(SourceRange &SR, DeclReplacement *N); void rewriteFunctionDecl(FunctionDeclReplacement *N); - SourceRange getNextComma(SourceLocation L); + // Emit supplementary declarations _after_ the token that begins at Loc. + // Inserts a newline before the first supplementary declaration but not after + // the last supplementary declaration. This is suitable if Loc is expected to + // be the last token on a line or if rewriteMultiDecl will insert a newline + // after the supplementary declarations later. + void emitSupplementaryDeclarations(const std::vector &SDecls, + SourceLocation Loc); + SourceLocation getNextCommaOrSemicolon(SourceLocation L); void denestTagDecls(); }; @@ -85,20 +97,20 @@ class FunctionDeclBuilder : public RecursiveASTVisitor { // Get existing itype string from constraint variables. std::string getExistingIType(ConstraintVariable *DeclC); - virtual void buildDeclVar(const FVComponentVariable *CV, - DeclaratorDecl *Decl, std::string &Type, - std::string &IType, std::string UseName, - bool &RewriteGen, bool &RewriteParm, - bool &RewriteRet, bool StaticFunc); - void buildCheckedDecl(PVConstraint *Defn, DeclaratorDecl *Decl, - std::string &Type, std::string &IType, - std::string UseName, bool &RewriteParm, - bool &RewriteRet); - void buildItypeDecl(PVConstraint *Defn, DeclaratorDecl *Decl, - std::string &Type, std::string &IType, bool &RewriteParm, - bool &RewriteRet); - - bool hasDeclWithTypedef(const FunctionDecl *FD); + virtual RewrittenDecl + buildDeclVar(const FVComponentVariable *CV, DeclaratorDecl *Decl, + std::string UseName, bool &RewriteGen, bool &RewriteParm, + bool &RewriteRet, bool StaticFunc, bool GenerateSDecls); + + RewrittenDecl + buildCheckedDecl(PVConstraint *Defn, DeclaratorDecl *Decl, + std::string UseName, bool &RewriteParm, bool &RewriteRet, + bool GenerateSDecls); + + RewrittenDecl buildItypeDecl(PVConstraint *Defn, DeclaratorDecl *Decl, + std::string UseName, bool &RewriteParm, + bool &RewriteRet, bool GenerateSDecls, + bool SDeclChecked); bool inParamMultiDecl(const ParmVarDecl *PVD); }; diff --git a/clang/include/clang/3C/LowerBoundAssignment.h b/clang/include/clang/3C/LowerBoundAssignment.h new file mode 100644 index 000000000000..23bb54476dd7 --- /dev/null +++ b/clang/include/clang/3C/LowerBoundAssignment.h @@ -0,0 +1,83 @@ +//=--LowerBoundAssignment.h---------------------------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Contains classes for detection and rewriting of assignment expression that +// would invalidate the bounds of pointers rewritten to use range bounds. +// For pointers fattend to use a fresh lower bound +// (`bounds(__3c_lower_bound_p, __3c_lower_bound_p + n)`), an +// assignment `p = q` effectively changes the lower bound of the range +// bounds, so that the new bounds of `p` are `bounds(q, q + n)` +// (assuming `q` has the same size as `p`). For this to not invalidate the +// bound, `__3c_lower_bound_p` must also be updated to be equal to `q`. +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_3C_LOWERBOUNDASSIGNMENT_H +#define LLVM_CLANG_3C_LOWERBOUNDASSIGNMENT_H + +#include "clang/AST/Decl.h" +#include "clang/AST/Stmt.h" +#include "clang/3C/ConstraintResolver.h" + +// Return true if an assignment LHS=RHS is the value of RHS is not derived form +// LHS. For example, an assignment `p = q` will return true (we assume `q` +// doesn't alias `p`), while `p = p + 1` will return false. +bool isLowerBoundAssignment(clang::Expr *LHS, clang::Expr *RHS); + +// A class to visit all lower bound assignment expression as detected by +// isLowerBoundAssignment. This class should be extended with +// visitLowerBoundAssignment overridden. +class LowerBoundAssignmentVisitor + : public RecursiveASTVisitor { +public: + explicit LowerBoundAssignmentVisitor() {} + + bool VisitBinaryOperator(BinaryOperator *O); + + // Override this method to define the operation that should be performed on + // each assignment. The LHS and RHS of the assignment expression are passed + // through. + virtual void visitLowerBoundAssignment(Expr *LHS, Expr *RHS) = 0; +}; + +// Visit each lower bound pointer expression and, if the LHS is a pointer +// variable that was rewritten to use range bounds, rewrite the assignment so +// that it doesn't not invalidate the bounds. e.g.: +// q = p; +// becomes +// __3c_lower_bound_q = p, q = __3c_lower_bound_q; +class LowerBoundAssignmentUpdater : public LowerBoundAssignmentVisitor { +public: + explicit LowerBoundAssignmentUpdater(ASTContext *C, ProgramInfo &I, + Rewriter &R) : ABInfo( + I.getABoundsInfo()), CR(I, C), R(R) {} + + void visitLowerBoundAssignment(Expr *LHS, Expr *RHS) override; + +private: + AVarBoundsInfo &ABInfo; + ConstraintResolver CR; + Rewriter &R; +}; + +// Visit each lower bound assignment expression and, if it is inside a macro, +// mark the LHS pointer as ineligible for range bounds. This is required +// because, if the pointer is given range bounds, then the assignment expression +// would need to be rewritten. The expression is a macro, so it cannot be +// rewritten. +class LowerBoundAssignmentFinder : public LowerBoundAssignmentVisitor { +public: + explicit LowerBoundAssignmentFinder(ASTContext *C, ProgramInfo &I) : ABInfo( + I.getABoundsInfo()), CR(I, C), C(C) {} + + void visitLowerBoundAssignment(Expr *LHS, Expr *RHS) override; + +private: + AVarBoundsInfo &ABInfo; + ConstraintResolver CR; + ASTContext *C; +}; +#endif // LLVM_CLANG_3C_LOWERBOUNDASSIGNMENT_H diff --git a/clang/include/clang/3C/ProgramInfo.h b/clang/include/clang/3C/ProgramInfo.h index 192fa56885cb..02c73142ec97 100644 --- a/clang/include/clang/3C/ProgramInfo.h +++ b/clang/include/clang/3C/ProgramInfo.h @@ -124,6 +124,9 @@ class ProgramInfo : public ProgramVariableAdder { // Retrieve a function's constraints by decl, or by name; nullptr if not found FVConstraint *getFuncConstraint(FunctionDecl *D, ASTContext *C) const; + FVConstraint * + getFuncConstraint(const std::string &FuncName, const std::string &FileName, + bool IsStatic) const; FVConstraint *getExtFuncDefnConstraint(std::string FuncName) const; FVConstraint *getStaticFuncConstraint(std::string FuncName, std::string FileName) const; diff --git a/clang/include/clang/3C/ProgramVar.h b/clang/include/clang/3C/ProgramVar.h index 5742b95c0d75..d6da3d94e9e0 100644 --- a/clang/include/clang/3C/ProgramVar.h +++ b/clang/include/clang/3C/ProgramVar.h @@ -362,9 +362,6 @@ class FunctionScope : public ProgramVarScope { if (auto *FS = clang::dyn_cast(&O)) { return (FS->FName == FName && FS->IsStatic == IsStatic); } - if (auto *FPS = clang::dyn_cast(&O)) { - return (FPS->FName == FName && FPS->IsStatic == IsStatic); - } return false; } diff --git a/clang/include/clang/3C/RewriteUtils.h b/clang/include/clang/3C/RewriteUtils.h index 8930327700f2..5bb60aff7787 100644 --- a/clang/include/clang/3C/RewriteUtils.h +++ b/clang/include/clang/3C/RewriteUtils.h @@ -38,12 +38,22 @@ class DeclReplacement { virtual ~DeclReplacement() {} + const std::vector &getSupplementaryDecls() const { + return SupplementaryDecls; + } + protected: - explicit DeclReplacement(std::string R, DRKind K) : Replacement(R), Kind(K) {} + explicit DeclReplacement(std::string R, std::vector SDecls, + DRKind K) : Replacement(R), + SupplementaryDecls(SDecls), Kind(K) {} // The string to replace the declaration with. std::string Replacement; + // A declaration might need to be replaced with more than a single new + // declaration. These extra declarations can be stored in this vector to be + // emitted after the original declaration. + std::vector SupplementaryDecls; private: const DRKind Kind; }; @@ -51,8 +61,9 @@ class DeclReplacement { template class DeclReplacementTempl : public DeclReplacement { public: - explicit DeclReplacementTempl(DeclT *D, std::string R) - : DeclReplacement(R, K), Decl(D) {} + explicit DeclReplacementTempl(DeclT *D, std::string R, + std::vector SDecls) + : DeclReplacement(R, SDecls, K), Decl(D) {} DeclT *getDecl() const override { return Decl; } @@ -69,9 +80,10 @@ class FunctionDeclReplacement : public DeclReplacementTempl { public: - explicit FunctionDeclReplacement(FunctionDecl *D, std::string R, bool Return, + explicit FunctionDeclReplacement(FunctionDecl *D, std::string R, + std::vector SDecls, bool Return, bool Params, bool Generic = false) - : DeclReplacementTempl(D, R), RewriteGeneric(Generic), + : DeclReplacementTempl(D, R, SDecls), RewriteGeneric(Generic), RewriteReturn(Return), RewriteParams(Params) { assert("Doesn't make sense to rewrite nothing!" && (RewriteGeneric || RewriteReturn || RewriteParams)); @@ -94,13 +106,49 @@ class FunctionDeclReplacement typedef std::map RSet; +// Represent a rewritten declaration split into three components. For a +// parameter or local variable declaration, concatenating Type and IType will +// give the full declaration. For a function return, Type should appear before +// the identifier and parameter list and itype should appear after. +struct RewrittenDecl { + explicit RewrittenDecl() : Type(), IType(), SupplementaryDecl() {} + explicit RewrittenDecl(std::string Type, std::string IType, + std::string SupplementaryDecl) + : Type(Type), IType(IType), SupplementaryDecl(SupplementaryDecl) {} + + // For function returns, the component of the declaration that appears before + // the identifier. For parameter and local variables, a prefix of the full + // declaration up to at least the identifier, but possibly omitting any itype + // or array bounds, which may be stored in the Itype field below. The + // identifier in this string is not always the same as the original identifier. + // If 3C generates a fresh lower bound (stored in the SupplementrayDecl + // string), then the identifier is changed to a temporary name. + std::string Type; + + // For function returns, the component of the rewritten declaration that + // appears after the parameter list. For parameter and local variables, some + // suffix of the full declaration, often any itype or bounds declaration, + // but also possibly empty. + std::string IType; + + // An additional declaration required as a result of the rewriting done to the + // original declaration. The additional declaration may refer to the original, + // so it must be emitted after the original declaration. + // This is currently only used to automatically fatten pointers to use fresh + // lower bound pointers. e.g., + // _Array_ptr a : bounds(__3c_lower_bound_a, __3c_lower_bound_a + n) + // If the declaration does not need a fresh lower bound, then this string is + // empty. + std::string SupplementaryDecl; +}; + // Generate a string for the declaration based on the given PVConstraint. // Includes the storage qualifier, type, name, and bounds string (as // applicable), or generates an itype declaration if required due to // ItypesForExtern. Does not include a trailing semicolon or an initializer, so // it can be used in combination with getDeclSourceRangeWithAnnotations with // IncludeInitializer = false to preserve an existing initializer. -std::string mkStringForPVDecl(MultiDeclMemberDecl *MMD, PVConstraint *PVC, +RewrittenDecl mkStringForPVDecl(MultiDeclMemberDecl *MMD, PVConstraint *PVC, ProgramInfo &Info); // Generate a string like mkStringForPVDecl, but for a declaration whose type is @@ -124,7 +172,8 @@ class ArrayBoundsRewriter { ArrayBoundsRewriter(ProgramInfo &I) : Info(I) {} // Get the string representation of the bounds for the given variable. std::string getBoundsString(const PVConstraint *PV, Decl *D, - bool Isitype = false); + bool Isitype = false, + bool OmitLowerBound = false); // Check if the constraint variable has newly created bounds string. bool hasNewBoundsString(const PVConstraint *PV, Decl *D, diff --git a/clang/include/clang/3C/Utils.h b/clang/include/clang/3C/Utils.h index 8480a8b543c1..5cd8159fd82a 100644 --- a/clang/include/clang/3C/Utils.h +++ b/clang/include/clang/3C/Utils.h @@ -69,8 +69,8 @@ template class BiMap { ValToK.clear(); } - const std::map &left() { return KtoVal; } - const std::map &right() { return ValToK; } + const std::map &left() const { return KtoVal; } + const std::map &right() const { return ValToK; } private: std::map KtoVal; @@ -128,6 +128,11 @@ bool isPointerType(clang::ValueDecl *VD); // Is this a pointer or array type? bool isPtrOrArrayType(const clang::QualType &QT); +// Is this an array type? Note that this includes pointer types decayed from +// array types (i.e., arrays in function parameters) but does not include +// Checked C checked array pointers (unless they decayed from a checked array). +bool isArrayType(const clang::QualType &QT); + // Is this a type that can go inside an _Nt_array_ptr? bool isNullableType(const clang::QualType &QT); @@ -230,6 +235,12 @@ int64_t getStmtIdWorkaround(const clang::Stmt *St, clang::SourceLocation getCheckedCAnnotationsEnd(const clang::Decl *D); + +clang::SourceLocation +getLocationAfterToken(clang::SourceLocation SL, const clang::SourceManager &SM, + const clang::LangOptions &LO); + + // Get the source range for a declaration including Checked C annotations. // Optionally, any initializer can be excluded from the range in order to avoid // interfering with other rewrites inside an existing initializer diff --git a/clang/lib/3C/3C.cpp b/clang/lib/3C/3C.cpp index 267082ae0db8..c86211471619 100644 --- a/clang/lib/3C/3C.cpp +++ b/clang/lib/3C/3C.cpp @@ -596,6 +596,11 @@ bool _3CInterface::solveConstraints() { GlobalProgramInfo.getABoundsInfo().dumpAVarGraph( "arr_bounds_initial.dot"); + // Infer lower bounds for pointers that are not valid lower bounds. + // The result of this inference is required for length inference, so + // this call must be before the subsequent call to performFlowAnalysis. + GlobalProgramInfo.getABoundsInfo().inferLowerBounds(&GlobalProgramInfo); + // Propagate initial data-flow information for Array pointers from // bounds declarations. GlobalProgramInfo.getABoundsInfo().performFlowAnalysis(&GlobalProgramInfo); diff --git a/clang/lib/3C/ABounds.cpp b/clang/lib/3C/ABounds.cpp index 293be644490b..b2fc9f9c5609 100644 --- a/clang/lib/3C/ABounds.cpp +++ b/clang/lib/3C/ABounds.cpp @@ -13,14 +13,6 @@ #include "clang/3C/ABounds.h" #include "clang/3C/AVarBoundsInfo.h" -std::set ABounds::KeysUsedInBounds; - -void ABounds::addBoundsUsedKey(BoundsKey BK) { KeysUsedInBounds.insert(BK); } - -bool ABounds::isKeyUsedInBounds(BoundsKey ToCheck) { - return KeysUsedInBounds.find(ToCheck) != KeysUsedInBounds.end(); -} - ABounds *ABounds::getBoundsInfo(AVarBoundsInfo *ABInfo, BoundsExpr *BExpr, const ASTContext &C) { ABounds *Ret = nullptr; @@ -41,18 +33,6 @@ ABounds *ABounds::getBoundsInfo(AVarBoundsInfo *ABInfo, BoundsExpr *BExpr, } } } - // Disabling Range bounds as they are not currently supported! - /* - RangeBoundsExpr *RBE = dyn_cast(BExpr->IgnoreParenCasts()); - if (BExpr->isRange() && RBE) { - Expr *LHS = RBE->getLowerExpr()->IgnoreParenCasts(); - Expr *RHS = RBE->getUpperExpr()->IgnoreParenImpCasts(); - BoundsKey LV, RV; - if (ABInfo->tryGetVariable(LHS, C, LV) && - ABInfo->tryGetVariable(RHS, C, RV)) { - Ret = new RangeBound(LV, RV); - } - }*/ return Ret; } @@ -80,56 +60,75 @@ std::string ABounds::getBoundsKeyStr(BoundsKey BK, AVarBoundsInfo *ABI, return BKStr; } -std::string CountBound::mkString(AVarBoundsInfo *ABI, clang::Decl *D) { - return "count(" + ABounds::getBoundsKeyStr(CountVar, ABI, D) + ")"; +std::string +ABounds::mkString(AVarBoundsInfo *ABI, clang::Decl *D, BoundsKey BK) { + if (LowerBoundKey != 0 && LowerBoundKey != BK) + return mkStringWithLowerBound(ABI, D); + return mkStringWithoutLowerBound(ABI, D); +} + +std::string +CountBound::mkStringWithLowerBound(AVarBoundsInfo *ABI, clang::Decl *D) { + std::string LowerBound = ABounds::getBoundsKeyStr(LowerBoundKey, ABI, D); + // Assume that LowerBound is the same pointer type as this pointer this bound + // acts on, so pointer arithmetic works as expected. + return "bounds(" + LowerBound + ", " + LowerBound + " + " + + ABounds::getBoundsKeyStr(LengthKey, ABI, D) + ")"; +} + +std::string +CountBound::mkStringWithoutLowerBound(AVarBoundsInfo *ABI, clang::Decl *D) { + return "count(" + ABounds::getBoundsKeyStr(LengthKey, ABI, D) + ")"; } bool CountBound::areSame(ABounds *O, AVarBoundsInfo *ABI) { if (O != nullptr) { if (CountBound *OT = dyn_cast(O)) - return ABI->areSameProgramVar(this->CountVar, OT->CountVar); + return ABI->areSameProgramVar(this->LengthKey, OT->LengthKey); } return false; } -BoundsKey CountBound::getBKey() { return this->CountVar; } - ABounds *CountBound::makeCopy(BoundsKey NK) { return new CountBound(NK); } -std::string CountPlusOneBound::mkString(AVarBoundsInfo *ABI, clang::Decl *D) { - std::string CVar = ABounds::getBoundsKeyStr(CountVar, ABI, D); - return "count(" + CVar + " + 1)"; +std::string CountPlusOneBound::mkStringWithLowerBound(AVarBoundsInfo *ABI, + clang::Decl *D) { + + std::string LowerBound = ABounds::getBoundsKeyStr(LowerBoundKey, ABI, D); + return "bounds(" + LowerBound + ", " + LowerBound + " + " + + ABounds::getBoundsKeyStr(LengthKey, ABI, D) + " + 1)"; +} + +std::string CountPlusOneBound::mkStringWithoutLowerBound(AVarBoundsInfo *ABI, + clang::Decl *D) { + return "count(" + ABounds::getBoundsKeyStr(LengthKey, ABI, D) + " + 1)"; } bool CountPlusOneBound::areSame(ABounds *O, AVarBoundsInfo *ABI) { if (CountPlusOneBound *OT = dyn_cast_or_null(O)) - return ABI->areSameProgramVar(this->CountVar, OT->CountVar); + return ABI->areSameProgramVar(this->LengthKey, OT->LengthKey); return false; } -std::string ByteBound::mkString(AVarBoundsInfo *ABI, clang::Decl *D) { - return "byte_count(" + ABounds::getBoundsKeyStr(ByteVar, ABI, D) + ")"; +std::string ByteBound::mkStringWithLowerBound(AVarBoundsInfo *ABI, + clang::Decl *D) { + std::string LowerBound = ABounds::getBoundsKeyStr(LowerBoundKey, ABI, D); + // LowerBound will be a pointer to some type that is not necessarily char, so + // pointer arithmetic will not give the behavior needed for a byte count. + // First cast the pointer to a char pointer, and then add byte count. + return "bounds(((_Array_ptr)" + LowerBound + "), ((_Array_ptr)" + + LowerBound + ") + " + ABounds::getBoundsKeyStr(LengthKey, ABI, D) + ")"; +} + +std::string ByteBound::mkStringWithoutLowerBound(AVarBoundsInfo *ABI, + clang::Decl *D) { + return "byte_count(" + ABounds::getBoundsKeyStr(LengthKey, ABI, D) + ")"; } bool ByteBound::areSame(ABounds *O, AVarBoundsInfo *ABI) { if (ByteBound *BB = dyn_cast_or_null(O)) - return ABI->areSameProgramVar(this->ByteVar, BB->ByteVar); + return ABI->areSameProgramVar(this->LengthKey, BB->LengthKey); return false; } -BoundsKey ByteBound::getBKey() { return this->ByteVar; } - ABounds *ByteBound::makeCopy(BoundsKey NK) { return new ByteBound(NK); } - -std::string RangeBound::mkString(AVarBoundsInfo *ABI, clang::Decl *D) { - std::string LBStr = ABounds::getBoundsKeyStr(LB, ABI, D); - std::string UBStr = ABounds::getBoundsKeyStr(UB, ABI, D); - return "bounds(" + LBStr + ", " + UBStr + ")"; -} - -bool RangeBound::areSame(ABounds *O, AVarBoundsInfo *ABI) { - if (RangeBound *RB = dyn_cast_or_null(O)) - return ABI->areSameProgramVar(this->LB, RB->LB) && - ABI->areSameProgramVar(this->UB, RB->UB); - return false; -} diff --git a/clang/lib/3C/AVarBoundsInfo.cpp b/clang/lib/3C/AVarBoundsInfo.cpp index 82b3ea846ebd..ebe09bd264cd 100644 --- a/clang/lib/3C/AVarBoundsInfo.cpp +++ b/clang/lib/3C/AVarBoundsInfo.cpp @@ -15,6 +15,7 @@ #include "clang/3C/ProgramInfo.h" #include "clang/3C/3CGlobalOptions.h" #include +#include std::vector AVarBoundsInfo::PrioList{Declared, Allocator, FlowInferred, Heuristics}; @@ -92,16 +93,10 @@ bool isInSrcArray(const ConstraintVariable *CK, const Constraints &CS) { // This class picks variables that are in the same scope as the provided scope. class ScopeVisitor { public: - ScopeVisitor(const ProgramVarScope *S, - const std::map &VM, - const std::set &P) - : Scope(S), InScopeKeys(), VisibleKeys(), PVarInfo(VM), - PointerBoundsKey(P) {} + ScopeVisitor(const ProgramVarScope *S, AVarBoundsInfo *BI) + : Scope(S), InScopeKeys(), VisibleKeys(), BI(BI) {} void visitBoundsKey(BoundsKey V) { - // If the variable is non-pointer? - if (PVarInfo.find(V) != PVarInfo.end() && - PointerBoundsKey.find(V) == PointerBoundsKey.end()) { - ProgramVar *S = PVarInfo.at(V); + if (ProgramVar *S = BI->getProgramVar(V)) { // If the variable is constant or in the same scope? if (S->isNumConstant() || (*Scope == *(S->getScope()))) { InScopeKeys.insert(V); @@ -127,15 +122,7 @@ class ScopeVisitor { // bounds keys from scopes where this scope is an inner scope. std::set VisibleKeys; - // A constant reference to PVarInfo frm the AVarBoundsInfo instance. This set - // maps each bounds key to variable. BoundsKeys are just a uint_32, so a - // corresponding ProgramVar is required find the scope of a key. - const std::map &PVarInfo; - - // A constant reference to the field PointerBoundsKey from the AVarBoundsInfo - // instance. This set contains the bounds keys that correspond to pointers. - // Used to verify that a visited bounds key is not a pointer. - const std::set &PointerBoundsKey; + const AVarBoundsInfo *BI; }; void AvarBoundsInference::mergeReachableProgramVars( @@ -226,11 +213,11 @@ void AvarBoundsInference::convergeInferredBounds() { if (BI->getBounds(PtrBoundsKey) == nullptr) { // Maps ABounds::BoundsKind to the set of possible bounds of this kind for // the current PtrBoundsKey. - auto BKindMap = CInfABnds.second; + auto &BKindMap = CInfABnds.second; for (auto &TySet : BKindMap) mergeReachableProgramVars(PtrBoundsKey, TySet.second); - ABounds *NewBound = getPreferredBound(BKindMap); + ABounds *NewBound = getPreferredBound(PtrBoundsKey); // If we found any bounds? if (NewBound != nullptr) { // Record that we inferred bounds using data-flow. @@ -248,7 +235,17 @@ void AvarBoundsInference::convergeInferredBounds() { // count-plus-one bounds. This function assumes that the BoundsKey sets in the // map contain either zero or one BoundsKey. This is be achieved by first // passing the sets to `mergeReachableProgramVars`. -ABounds *AvarBoundsInference::getPreferredBound(const BndsKindMap &BKindMap) { +ABounds *AvarBoundsInference::getPreferredBound(BoundsKey BK) { + BoundsKey BaseVar = 0; + bool NeedsBasePointer = + BI->InvalidLowerBounds.find(BK) != BI->InvalidLowerBounds.end(); + if (NeedsBasePointer && BI->LowerBounds.find(BK) != BI->LowerBounds.end()) + BaseVar = BI->LowerBounds[BK]; + + assert("Lower bound required but not available." && + (!NeedsBasePointer || BaseVar != 0)); + + const auto &BKindMap = CurrIterInferBounds[BK]; // Utility to check if the map contains a non-empty set of bounds for a // particular kind. This makes the following if statements much cleaner. auto HasBoundKind = [&BKindMap](ABounds::BoundsKind Kind) { @@ -257,14 +254,15 @@ ABounds *AvarBoundsInference::getPreferredBound(const BndsKindMap &BKindMap) { // Order of preference: Count, Byte, Count-plus-one if (HasBoundKind(ABounds::CountBoundKind)) - return new CountBound(getOnly(BKindMap.at(ABounds::CountBoundKind))); + return new CountBound(getOnly(BKindMap.at(ABounds::CountBoundKind)), + BaseVar); if (HasBoundKind(ABounds::ByteBoundKind)) - return new ByteBound(getOnly(BKindMap.at(ABounds::ByteBoundKind))); + return new ByteBound(getOnly(BKindMap.at(ABounds::ByteBoundKind)), BaseVar); if (HasBoundKind(ABounds::CountPlusOneBoundKind)) return new CountPlusOneBound( - getOnly(BKindMap.at(ABounds::CountPlusOneBoundKind))); + getOnly(BKindMap.at(ABounds::CountPlusOneBoundKind)), BaseVar); return nullptr; } @@ -305,7 +303,7 @@ bool AvarBoundsInference::getReachableBoundKeys(const ProgramVarScope *DstScope, } // Find all in scope variables reachable from the FromVarK bounds variable. - ScopeVisitor TV(DstScope, BI->PVarInfo, BI->PointerBoundsKey); + ScopeVisitor TV(DstScope, BI); BKGraph.visitBreadthFirst(FromVarK, [&TV](BoundsKey BK) { TV.visitBoundsKey(BK); }); // Prioritize in scope keys. @@ -346,16 +344,11 @@ bool AvarBoundsInference::getReachableBoundKeys(const ProgramVarScope *DstScope, void AvarBoundsInference::getRelevantBounds(BoundsKey BK, BndsKindMap &ResBounds) { - // Try to get the bounds of all RBKeys. - // If this pointer is used in pointer arithmetic then there - // are no relevant bounds for this pointer. - if (!BI->hasPointerArithmetic(BK)) { - if (CurrIterInferBounds.find(BK) != CurrIterInferBounds.end()) { - // get the bounds inferred from the current iteration - ResBounds = CurrIterInferBounds[BK]; - } else if (ABounds *PrevBounds = BI->getBounds(BK)) { - ResBounds[PrevBounds->getKind()].insert(PrevBounds->getBKey()); - } + if (CurrIterInferBounds.find(BK) != CurrIterInferBounds.end()) { + // get the bounds inferred from the current iteration + ResBounds = CurrIterInferBounds[BK]; + } else if (ABounds *PrevBounds = BI->getBounds(BK)) { + ResBounds[PrevBounds->getKind()].insert(PrevBounds->getLengthKey()); } } @@ -369,7 +362,7 @@ bool AvarBoundsInference::areDeclaredBounds( if (DeclB && DeclB->getKind() == Bnds.first) { IsDeclaredB = true; for (auto TmpNBK : Bnds.second) { - if (!this->BI->areSameProgramVar(TmpNBK, DeclB->getBKey())) { + if (!this->BI->areSameProgramVar(TmpNBK, DeclB->getLengthKey())) { IsDeclaredB = false; break; } @@ -395,7 +388,7 @@ bool AvarBoundsInference::predictBounds(BoundsKey K, if (!NeighboursBnds.empty()) { for (auto &NKBChoice : NeighboursBnds) { ABounds::BoundsKind NeighbourKind = NKBChoice.first; - std::set NeighbourSet = NKBChoice.second; + const std::set &NeighbourSet = NKBChoice.second; std::set InfBK; for (BoundsKey NeighborBK : NeighbourSet) @@ -446,11 +439,11 @@ bool AvarBoundsInference::predictBounds(BoundsKey K, for (auto &IN : InferredNBnds) { for (auto &INB : IN.second) { ABounds::BoundsKind NeighbourKind = INB.first; - std::set NeighbourSet = INB.second; + const std::set &NeighbourSet = INB.second; if (InferredKBnds.find(NeighbourKind) == InferredKBnds.end()) { InferredKBnds[NeighbourKind] = NeighbourSet; } else { - std::set KBoundsOfKind = InferredKBnds[NeighbourKind]; + const std::set &KBoundsOfKind = InferredKBnds[NeighbourKind]; // Keep the bounds in the intersection between the current bounds and // the bounds from the neighbor. std::set SharedBounds; @@ -509,7 +502,12 @@ bool AvarBoundsInference::inferBounds(BoundsKey K, const AVarGraph &BKGraph, bool FromPB) { bool IsChanged = false; - if (BI->InvalidBounds.find(K) == BI->InvalidBounds.end()) { + // If a lower bound could not be inferred for a BoundsKey, then we refuse to + // infer an upper bound for it as well. This prevents inferring incorrect + // bounds when a bound would propagate through a pointer without a lower + // bound. + if (BI->hasLowerBound(K) && + BI->InvalidBounds.find(K) == BI->InvalidBounds.end()) { // Infer from potential bounds? if (FromPB) { IsChanged = inferFromPotentialBounds(K, BKGraph); @@ -524,6 +522,227 @@ bool AvarBoundsInference::inferBounds(BoundsKey K, const AVarGraph &BKGraph, return IsChanged; } + +void AVarBoundsInfo::computeInvalidLowerBounds(ProgramInfo *PI) { + // This will compute a breadth first search starting from the constant + // InvalidLowerBoundKey. Any reachable keys are also invalid lower bounds. + // This is essentially the same algorithm as is used for solving the checked + // type constraint graph. + assert(InvalidLowerBounds.empty()); + std::queue WorkList; + WorkList.push(InvalidLowerBoundKey); + + // To check if a bounds key is already invalidated we check if it is either in + // the set of invalidated keys, or is the constant InvalidLowerBoundKey. + auto IsInvalidated = [this](BoundsKey BK) { + return BK == InvalidLowerBoundKey || + InvalidLowerBounds.find(BK) != InvalidLowerBounds.end(); + }; + + while (!WorkList.empty()) { + BoundsKey Curr = WorkList.front(); + WorkList.pop(); + assert(IsInvalidated(Curr)); + + std::set Neighbors; + LowerBoundGraph.getSuccessors(Curr, Neighbors); + for (BoundsKey NK : Neighbors) { + // This is an awful hack to work around a problem during conversion phase + // two. A parameter would be given count bounds, with a local being + // created to hold the range bounds. A conversion is done with + // -itypes-for-extern` and the headers are copied over. The version of + // the header in the local directory now has count bounds on an itype. If + // we trust those bounds, then the next conversion does not emit range + // bounds. + PointerVariableConstraint *PVC = getConstraintVariable(PI, NK); + // Strictly speaking, this can occur outside of -itypes-for-extern, but + // it is unlikely, and I've decided that the risk of unintentionally + // changing other behavior is greater than the risk that this special + // case will be needed in some other circumstance. + bool IsItypeParam = _3COpts.ItypesForExtern && PVC && PVC->srcHasItype(); + + // The neighbors of an invalid lower bound are also invalid, with the + // exception that if there is a bound in the source code, then we assume + // the bound is correct, and so the pointer is a valid lower bound for + // itself. + bool HasDeclaredBounds = + getBounds(NK, BoundsPriority::Declared) != nullptr; + if ((IsItypeParam || !HasDeclaredBounds) && !IsInvalidated(NK)) { + InvalidLowerBounds.insert(NK); + WorkList.push(NK); + } + } + } +} + +void +AVarBoundsInfo::inferLowerBounds(ProgramInfo *PI) { + computeInvalidLowerBounds(PI); + + // This maps array pointers to a single consistent lower bound pointer, or + // possible the constant InvalidLowerBoundKey if no lower bound could be found + // or generated. Note that there can only be a single valid lower bound. + // Future work can extend this map to track sets if possible lower bounds. + std::map InfLBs; + + // Lower bound inference will proceed as a traversal of the LowerBoundGraph. + // The traversal starts at the direct predecessors of the pointers that need + // an inferred lower bound. + std::queue WorkList; + for (BoundsKey BK: InvalidLowerBounds) { + std::set Pred; + LowerBoundGraph.getPredecessors(BK, Pred); + for (BoundsKey Seed: Pred) { + if (Seed != InvalidLowerBoundKey && + InvalidLowerBounds.find(Seed) == InvalidLowerBounds.end()) { + // This pointer is a valid lower bound for itself, so add it to the + // worklist and initialize it in the map of inferred lower bounds. + InfLBs[Seed] = Seed; + WorkList.push(Seed); + } + } + } + + // It's possible for there to be invalid lower bounds that are not reachable + // from any valid lower bounds, so we also initialize the worklist with all + // invalid lower bounds. These come after the valid lower bounds so that is + // less likely a fresh lower bound will be generated but later thrown out, as + // that process is inefficient at least in the current implementation. + for (BoundsKey InvLB : InvalidLowerBounds) + WorkList.push(InvLB); + + // This set tracks the pointers for which we will need to generate a fresh + // lower bound pointer. These pointers do not have a single consistent lower + // bound in the source code, but 3C is able to insert a duplicate declaration + // to act as the lower bound. + std::set NeedFreshLB; + + // This set track the pointers that have multiple inconsistent lower bounds. + // This is used to differentiate pointers that need a fresh lower bound + // because no lower bounds could be found from pointers that need a fresh + // lower bound because there were multiple inconsistent lower bounds. Note + // that is not a subset of NeedFreshLB because a pointer may have conflicting + // bounds but be ineligible for a fresh lower bound. + std::set HasConflictingBounds; + + while (!WorkList.empty()) { + BoundsKey BK = WorkList.front(); + WorkList.pop(); + + if (isEligibleForFreshLowerBound(BK) && + (InfLBs.find(BK) == InfLBs.end() || + InfLBs[BK] == InvalidLowerBoundKey)) { + // We've reached an array pointer in the work list that either has not + // been assigned a lower bound, or has multiple conflicting lower bounds. + // We will generate a fresh lower bound. + assert( + "Generating fresh bound for pointer that can be its own lower bound." && + InvalidLowerBounds.find(BK) != InvalidLowerBounds.end()); + InfLBs[BK] = getFreshLowerBound(BK); + NeedFreshLB.insert(BK); + } + + std::set Succ; + LowerBoundGraph.getSuccessors(BK, Succ); + for (BoundsKey S : Succ) { + + // Do not process any array pointers that are valid lower bounds. They + // should just serve as their own lower bound. + if (InvalidLowerBounds.find(S) == InvalidLowerBounds.end()) + continue; + + if (InfLBs.find(S) == InfLBs.end()) { + // No prior lower bound known for `S`. Initialize it to use the same + // lower bound as `BK`, if this is possible given their scopes. + if (isInAccessibleScope(S, InfLBs[BK])) { + InfLBs[S] = InfLBs[BK]; + } else { + InfLBs[S] = InvalidLowerBoundKey; + } + WorkList.push(S); + } else if (InfLBs[BK] != InfLBs[S] && + HasConflictingBounds.find(S) == HasConflictingBounds.end()) { + // The lower bound of `BK` is not the same as the current inferred lower + // bounds of `S`. This is a problem, so we need to mark `S` as having + // conflicting lower bounds. We only do this invalidation step once. If + // the BoundsKey is already known to have conflicting bounds, then do + // not reset it again. Doing so can cause an infinite loop when there is + // a cycle of BoundsKeys needing a lower bound, where each BoundsKey in + // the cycle is eligible for a fresh lower bound. + HasConflictingBounds.insert(S); + + if (NeedFreshLB.find(S) != NeedFreshLB.end()) { + // This case handles when we a fresh lower bounds was created for `S` + // before any conflict was detected. It is possible that the conflict + // we detect here only exists between the fresh lower bound and the + // lower bound of `BK`. In this case, we can drop the fresh lower + // bounds to use the inferred lower bound. In order to fully drop the + // fresh bound, we must also drop it from all BoundsKey reachable from + // `S`, as these may have already had a lower bound inferred based on + // the fresh lower bound. + NeedFreshLB.erase(S); + BoundsKey SLB = InfLBs[S]; + // TODO: Erasing the bounds by a breadth first search from S is + // inefficient, probably resulting in quadratic worst case + // running time, but this hasn't shown up a real performance + // issue yet. + LowerBoundGraph.visitBreadthFirst(S, [this, SLB, &InfLBs, &WorkList]( + BoundsKey BK) { + if (InfLBs.find(BK) != InfLBs.end() && InfLBs[BK] == SLB) { + InfLBs.erase(BK); + std::set Pred; + LowerBoundGraph.getPredecessors(BK, Pred); + for (BoundsKey P : Pred) { + if (P != InvalidLowerBoundKey && + InfLBs.find(P) != InfLBs.end()) + WorkList.push(P); + } + } + }); + } else if (InfLBs[S] != InvalidLowerBoundKey) { + // If no fresh lower bound was generated, then things are much + // simpler. Just invalidate the lower bound of `S` and enqueue it. + InfLBs[S] = InvalidLowerBoundKey; + WorkList.push(S); + } + } + // Otherwise, the a lower bound exists for `S`, and it's the same lower + // bounds as `BK`. Nothing changes, so don't enqueue `S`. + } + } + + NeedFreshLowerBounds = NeedFreshLB; + LowerBounds = InfLBs; + + // This is an awful hack to work around a problem during conversion phase + // two. + if (_3COpts.ItypesForExtern) { + for (auto InferredLBPair : LowerBounds) { + if (BInfo[InferredLBPair.first][Declared]) { + BInfo[InferredLBPair.first][Declared]->setLowerBoundKey( + InferredLBPair.second); + } + } + } +} + +BoundsKey AVarBoundsInfo::getFreshLowerBound(BoundsKey Arr) { + ProgramVar *ArrVar = getProgramVar(Arr); + BoundsKey FreshLB = getRandomBKey(); + ProgramVar *FreshLBVar = + ProgramVar::createNewProgramVar(FreshLB, + "__3c_lower_bound_" + ArrVar->getVarName(), + ArrVar->getScope()); + insertProgramVar(FreshLB, FreshLBVar); + return FreshLB; +} + +bool AVarBoundsInfo::hasLowerBound(BoundsKey K) { + return InvalidLowerBounds.find(K) == InvalidLowerBounds.end() || + (LowerBounds.find(K) != LowerBounds.end() && + LowerBounds[K] != InvalidLowerBoundKey); +} + bool AvarBoundsInference::inferFromPotentialBounds(BoundsKey BK, const AVarGraph &BKGraph) { // If we have any inferred bounds for K then ignore potential bounds. @@ -704,6 +923,9 @@ bool AVarBoundsInfo::tryGetVariable(clang::Expr *E, const ASTContext &C, // Merging bounds B with the present bounds of key L at the same priority P // Returns true if we update the bounds for L (with B) bool AVarBoundsInfo::mergeBounds(BoundsKey L, BoundsPriority P, ABounds *B) { + if (B->getLowerBoundKey() == InvalidLowerBoundKey && + LowerBounds.find(L) != LowerBounds.end()) + B->setLowerBoundKey(LowerBounds[L]); bool RetVal = false; if (BInfo.find(L) != BInfo.end() && BInfo[L].find(P) != BInfo[L].end()) { // If previous computed bounds are not same? Then release the old bounds. @@ -810,8 +1032,18 @@ BoundsKey AVarBoundsInfo::getVariable(clang::VarDecl *VD) { auto *PVar = ProgramVar::createNewProgramVar(NK, VD->getNameAsString(), PVS); insertProgramVar(NK, PVar); - if (isPtrOrArrayType(VD->getType())) + if (isPtrOrArrayType(VD->getType())) { PointerBoundsKey.insert(NK); + // Global variables cannot be given range bounds because it is not + // possible to initialize a duplicated pointer variable with the same + // value as the original. + // TODO: Followup issue: Implementing the rewriting here would be easy, + // but it would also require change the compiler to recognize + // dynamic bounds casts are constant expressions, which doesn't + // sound too hard. + if (!VD->isLocalVarDeclOrParm()) + markIneligibleForFreshLowerBound(NK); + } } return getVarKey(PSL); } @@ -829,16 +1061,43 @@ BoundsKey AVarBoundsInfo::getVariable(clang::ParmVarDecl *PVD) { const FunctionParamScope *FPS = FunctionParamScope::getFunctionParamScope( FD->getNameAsString(), FD->isStatic()); std::string ParamName = PVD->getNameAsString(); - // If this is a parameter without name!? - // Just get the name from argument number. - if (ParamName.empty()) - ParamName = "NONAMEPARAM_" + std::to_string(ParamIdx); + + if (ParamName.empty()) { + // The parameter declaration doesn't have a name. Try to get the + // corresponding function definition, and then the parameter declaration + // in that function. Use the name of that parameter. + // TODO: I think the declaration merging code written by kyle does a good + // job of handling missing/inconsistent parameter names. Can I just + // use that? + if (auto *FnDef = FD->getDefinition()) { + if (FnDef->getNumParams() >= ParamIdx) { + if (auto *DefPVD = FnDef->getParamDecl(ParamIdx)) { + ParamName = DefPVD->getNameAsString(); + } + } + } + // If we still couldn't find a name, then we make one up using the + // parameter index. + // TODO: Is this better than leaving the name string empty? There are + // situations where an identifier can be omitted without error, but + // using an undeclared identifier is an error. + if (ParamName.empty()) + ParamName = "NONAMEPARAM_" + std::to_string(ParamIdx); + } auto *PVar = ProgramVar::createNewProgramVar(NK, ParamName, FPS); insertProgramVar(NK, PVar); insertParamKey(ParamKey, NK); - if (isPtrOrArrayType(PVD->getType())) + if (isPtrOrArrayType(PVD->getType())) { PointerBoundsKey.insert(NK); + // We do not give range bounds to parameters with an array type. Doing + // this causes the local variable duplicate definition to have an array + // type, but pointer arithmetic on constant size arrays is not allowed. + // TODO: Follow up issue: Can we add some special logic in rewriting to + // emit the duplicate definition with an _Array_pointer type? + if (isArrayType(PVD->getType())) + markIneligibleForFreshLowerBound(NK); + } } return ParamDeclVarMap.left().at(ParamKey); } @@ -858,8 +1117,12 @@ BoundsKey AVarBoundsInfo::getVariable(clang::FunctionDecl *FD) { ProgramVar::createNewProgramVar(NK, FD->getNameAsString(), FPS); insertProgramVar(NK, PVar); FuncDeclVarMap.insert(FuncKey, NK); - if (isPtrOrArrayType(FD->getReturnType())) + if (isPtrOrArrayType(FD->getReturnType())) { PointerBoundsKey.insert(NK); + // Fresh lower bounds are not inserted for function returns. I don't see a + // way around this limitation. + markIneligibleForFreshLowerBound(NK); + } } return FuncDeclVarMap.left().at(FuncKey); } @@ -874,8 +1137,16 @@ BoundsKey AVarBoundsInfo::getVariable(clang::FieldDecl *FD) { const StructScope *SS = StructScope::getStructScope(StName); auto *PVar = ProgramVar::createNewProgramVar(NK, FD->getNameAsString(), SS); insertProgramVar(NK, PVar); - if (isPtrOrArrayType(FD->getType())) + if (isPtrOrArrayType(FD->getType())) { PointerBoundsKey.insert(NK); + // Fields are not rewritten with range bounds because we would need to + // duplicate the field and update all structure initializations to + // properly set the new field. + // TODO: Followup issue: Add the duplicate declaration as a new field in + // the struct and then also update all struct initializer to include + // the new field. + markIneligibleForFreshLowerBound(NK); + } } return getVarKey(PSL); } @@ -886,19 +1157,6 @@ BoundsKey AVarBoundsInfo::getRandomBKey() { return Ret; } -bool AVarBoundsInfo::addAssignment(clang::Decl *L, clang::Decl *R) { - BoundsKey BL, BR; - if (tryGetVariable(L, BL) && tryGetVariable(R, BR)) { - return addAssignment(BL, BR); - } - return false; -} - -bool AVarBoundsInfo::addAssignment(clang::DeclRefExpr *L, - clang::DeclRefExpr *R) { - return addAssignment(L->getDecl(), R->getDecl()); -} - bool AVarBoundsInfo::handleAssignment(clang::Expr *L, const CVarSet &LCVars, const std::set &CSLKeys, clang::Expr *R, const CVarSet &RCVars, @@ -953,10 +1211,13 @@ bool AVarBoundsInfo::addAssignment(BoundsKey L, BoundsKey R) { // So, if we create a edge from return to itself then we create a cyclic // dependency and never will be able to find the bounds for the return // value. - if (L != R) + if (L != R) { ProgVarGraph.addUniqueEdge(R, L); + LowerBoundGraph.addUniqueEdge(R, L); + } } else { ProgVarGraph.addUniqueEdge(R, L); + LowerBoundGraph.addUniqueEdge(R, L); ProgramVar *PV = getProgramVar(R); if (!(PV && PV->isNumConstant())) ProgVarGraph.addUniqueEdge(L, R); @@ -964,66 +1225,11 @@ bool AVarBoundsInfo::addAssignment(BoundsKey L, BoundsKey R) { return true; } -// Visitor to collect all the variables and structure member access that are -// used during the life-time of the visitor. -class CollectDeclsVisitor : public RecursiveASTVisitor { -public: - explicit CollectDeclsVisitor(ASTContext *Ctx) - : ObservedDecls(), StructAccess(), C(Ctx) {} - - virtual ~CollectDeclsVisitor() {} - - bool VisitDeclRefExpr(DeclRefExpr *DRE) { - if (auto *VD = dyn_cast_or_null(DRE->getDecl())) - ObservedDecls.insert(VD); - return true; - } - - // For a->b; We need to get `a->b` - bool VisitMemberExpr(MemberExpr *ME) { - std::string MAccess = getSourceText(ME->getSourceRange(), *C); - if (!MAccess.empty()) { - StructAccess.insert(MAccess); - } - return false; - } - - const std::set &getObservedDecls() { return ObservedDecls; } - const std::set &getStructAccess() { return StructAccess; } - -private: - // Contains all VarDecls seen by this visitor - std::set ObservedDecls; - - // Contains the source representation of all record access (MemberExpression) - // seen by this visitor. - std::set StructAccess; - - ASTContext *C; -}; - -bool AVarBoundsInfo::handlePointerAssignment(clang::Stmt *St, clang::Expr *L, - clang::Expr *R, ASTContext *C, +bool AVarBoundsInfo::handlePointerAssignment(clang::Expr *L, clang::Expr *R, + ASTContext *C, ConstraintResolver *CR) { - CollectDeclsVisitor LVarVis(C); - LVarVis.TraverseStmt(L->getExprStmt()); - - CollectDeclsVisitor RVarVis(C); - RVarVis.TraverseStmt(R->getExprStmt()); - - std::set CommonVars; - std::set CommonStVars; - findIntersection(LVarVis.getObservedDecls(), RVarVis.getObservedDecls(), - CommonVars); - findIntersection(LVarVis.getStructAccess(), RVarVis.getStructAccess(), - CommonStVars); - - if (!CommonVars.empty() || CommonStVars.empty()) { - for (auto *LHSCVar : CR->getExprConstraintVarsSet(L)) { - if (LHSCVar->hasBoundsKey()) - ArrPointerBoundsKey.insert(LHSCVar->getBoundsKey()); - } - } + if (!isLowerBoundAssignment(L, R)) + recordArithmeticOperation(L, CR); return true; } @@ -1039,21 +1245,62 @@ void AVarBoundsInfo::recordArithmeticOperation(clang::Expr *E, ConstraintResolver *CR) { CVarSet CSet = CR->getExprConstraintVarsSet(E); for (auto *CV : CSet) { - if (CV->hasBoundsKey()) - ArrPointersWithArithmetic.insert(CV->getBoundsKey()); + if (CV->hasBoundsKey()) { + BoundsKey BK = CV->getBoundsKey(); + ArrPointersWithArithmetic.insert(BK); + LowerBoundGraph.addUniqueEdge(InvalidLowerBoundKey, BK); + } } } -bool AVarBoundsInfo::hasPointerArithmetic(BoundsKey BK) { - return ArrPointersWithArithmetic.find(BK) != ArrPointersWithArithmetic.end(); +bool AVarBoundsInfo::needsFreshLowerBound(BoundsKey BK) { + return NeedFreshLowerBounds.find(BK) != NeedFreshLowerBounds.end(); } -ProgramVar *AVarBoundsInfo::getProgramVar(BoundsKey VK) { - ProgramVar *Ret = nullptr; - if (PVarInfo.find(VK) != PVarInfo.end()) { - Ret = PVarInfo[VK]; - } - return Ret; +bool AVarBoundsInfo::isEligibleForFreshLowerBound(BoundsKey BK) { + return IneligibleForFreshLowerBound.find(BK) == + IneligibleForFreshLowerBound.end(); +} + +void AVarBoundsInfo::markIneligibleForFreshLowerBound(BoundsKey BK) { + IneligibleForFreshLowerBound.insert(BK); +} + +bool AVarBoundsInfo::needsFreshLowerBound(ConstraintVariable *CV) { + if (!CV->hasBoundsKey()) + return false; + BoundsKey BK = CV->getBoundsKey(); + // A pointer should get range bounds if it is computed by pointer arithmetic + // and would otherwise need bounds. Some pointers (global variables and struct + // fields) can't be rewritten to use range bounds (by 3C; Checked C does + // permit it), so we return false on these. + return needsFreshLowerBound(BK) && isEligibleForFreshLowerBound(BK) && + getBounds(BK) != nullptr; +} + +ProgramVar *AVarBoundsInfo::getProgramVar(BoundsKey VK) const { + if (PVarInfo.find(VK) != PVarInfo.end()) + return PVarInfo.at(VK); + return nullptr; +} + +const ProgramVarScope *AVarBoundsInfo::getProgramVarScope(BoundsKey BK) const{ + if (ProgramVar *Var = getProgramVar(BK)) + return Var->getScope(); + return nullptr; +} + +bool AVarBoundsInfo::isInAccessibleScope(BoundsKey From, BoundsKey To) { + const ProgramVarScope *FromScope = getProgramVarScope(From); + const ProgramVarScope *ToScope = getProgramVarScope(To); + return FromScope != nullptr && ToScope != nullptr && + (*FromScope == *ToScope || FromScope->isInInnerScope(*ToScope)); +} + +bool AVarBoundsInfo::scopeCanHaveLowerBound(BoundsKey BK) { + const ProgramVarScope *BKScope = getProgramVarScope(BK); + return BKScope != nullptr && !isa(BKScope) && + !isa(BKScope); } bool AVarBoundsInfo::hasVarKey(PersistentSourceLoc &PSL) { @@ -1142,9 +1389,11 @@ void AVarBoundsInfo::computeArrPointers(const ProgramInfo *PI) { NtArrPointerBoundsKey.clear(); ArrPointerBoundsKey.clear(); - // Called in following loop to add a BoundsKey to the appropriate sets based - // on the pointer type of a corresponding ConstraintVariable. - auto AddToArrSets = [this, PI](BoundsKey BK, const ConstraintVariable *CV) { + for (auto BK : PointerBoundsKey) { + const PointerVariableConstraint *CV = getConstraintVariable(PI, BK); + if (CV == nullptr) + continue; + if (hasArray(CV, PI->getConstraints())) ArrPointerBoundsKey.insert(BK); @@ -1161,50 +1410,6 @@ void AVarBoundsInfo::computeArrPointers(const ProgramInfo *PI) { if (CV->getName() == RETVAR && getBounds(BK) == nullptr) PointersWithImpossibleBounds.insert(BK); } - }; - - // Find a FVConstraint in the ProgramInfo function definition maps given a - // function name and filename. - auto LookupFVCons = [PI](const std::string &FuncName, - const std::string &FileName, bool IsStatic) { - if (IsStatic || !PI->getExtFuncDefnConstraint(FuncName)) - return PI->getStaticFuncConstraint(FuncName, FileName); - return PI->getExtFuncDefnConstraint(FuncName); - }; - - auto &VariableMap = DeclVarMap.right(); - auto &ParamMap = ParamDeclVarMap.right(); - auto &ReturnMap = FuncDeclVarMap.right(); - for (auto Bkey : PointerBoundsKey) { - if (VariableMap.find(Bkey) != VariableMap.end()) { - // Regular variables. - const PersistentSourceLoc &PSL = VariableMap.at(Bkey); - const ConstraintVariable *BkeyCV = PI->getVarMap().at(PSL); - AddToArrSets(Bkey, BkeyCV); - - } else if (ParamMap.find(Bkey) != ParamMap.end()) { - // Function parameters - auto &ParmTup = ParamMap.at(Bkey); - std::string FuncName = std::get<0>(ParmTup); - std::string FileName = std::get<1>(ParmTup); - bool IsStatic = std::get<2>(ParmTup); - unsigned ParmNum = std::get<3>(ParmTup); - - FVConstraint *FV = LookupFVCons(FuncName, FileName, IsStatic); - PVConstraint *ParamPVC = FV->getExternalParam(ParmNum); - AddToArrSets(Bkey, ParamPVC); - - } else if (ReturnMap.find(Bkey) != ReturnMap.end()) { - // Function returns. - auto &FuncRet = ReturnMap.at(Bkey); - std::string FuncName = std::get<0>(FuncRet); - std::string FileName = std::get<1>(FuncRet); - bool IsStatic = std::get<2>(FuncRet); - - FVConstraint *FV = LookupFVCons(FuncName, FileName, IsStatic); - PVConstraint *RetPVC = FV->getExternalReturn(); - AddToArrSets(Bkey, RetPVC); - } } // Get all context-sensitive BoundsKey for each of the actual BKs @@ -1344,6 +1549,7 @@ void AVarBoundsInfo::performFlowAnalysis(ProgramInfo *PI) { PStats.endArrayBoundsInferenceTime(); } + bool AVarBoundsInfo::keepHighestPriorityBounds() { bool HasChanged = false; for (auto BK : ArrPointerBoundsKey) { @@ -1382,6 +1588,7 @@ void AVarBoundsInfo::dumpAVarGraph(const std::string &DFPath) { DumpGraph(ProgVarGraph, "ProgVar"); DumpGraph(CtxSensProgVarGraph, "CtxSen"); DumpGraph(RevCtxSensProgVarGraph, "RevCtxSen"); + DumpGraph(LowerBoundGraph, "Invalid"); } bool AVarBoundsInfo::isFunctionReturn(BoundsKey BK) { @@ -1500,4 +1707,40 @@ void AVarBoundsInfo::addConstantArrayBounds(ProgramInfo &I) { } } } -} \ No newline at end of file +} + +PVConstraint *AVarBoundsInfo::getConstraintVariable(const ProgramInfo *PI, + BoundsKey BK) const { + // Regular variables. + const auto &VariableMap = DeclVarMap.right(); + if (VariableMap.find(BK) != VariableMap.end()) { + const PersistentSourceLoc &PSL = VariableMap.at(BK); + return dyn_cast(PI->getVarMap().at(PSL)); + } + + // Function parameters + const auto &ParamMap = ParamDeclVarMap.right(); + if (ParamMap.find(BK) != ParamMap.end()) { + auto &ParmTup = ParamMap.at(BK); + std::string FuncName = std::get<0>(ParmTup); + std::string FileName = std::get<1>(ParmTup); + bool IsStatic = std::get<2>(ParmTup); + unsigned ParmNum = std::get<3>(ParmTup); + + FVConstraint *FV = PI->getFuncConstraint(FuncName, FileName, IsStatic); + return FV->getExternalParam(ParmNum); + } + + // Function returns. + const auto &ReturnMap = FuncDeclVarMap.right(); + if (ReturnMap.find(BK) != ReturnMap.end()) { + auto &FuncRet = ReturnMap.at(BK); + std::string FuncName = std::get<0>(FuncRet); + std::string FileName = std::get<1>(FuncRet); + bool IsStatic = std::get<2>(FuncRet); + + FVConstraint *FV = PI->getFuncConstraint(FuncName, FileName, IsStatic); + return FV->getExternalReturn(); + } + return nullptr; +} diff --git a/clang/lib/3C/AVarGraph.cpp b/clang/lib/3C/AVarGraph.cpp index c20ccf6eafac..3527d26c4381 100644 --- a/clang/lib/3C/AVarGraph.cpp +++ b/clang/lib/3C/AVarGraph.cpp @@ -62,7 +62,9 @@ llvm::DOTGraphTraits::getNodeLabel(const DataNode *Node, BoundsKey BK = Node->getData(); ProgramVar *Tmp = ABInfo->getProgramVar(BK); std::string LblStr = "Temp"; - if (Tmp != nullptr) + if (BK == 0) + LblStr = "Invalid"; + else if (Tmp != nullptr) LblStr = Tmp->verboseStr(); bool IsArrPtr = ABInfo->ArrPointerBoundsKey.find(BK) != ABInfo->ArrPointerBoundsKey.end(); diff --git a/clang/lib/3C/ArrayBoundsInferenceConsumer.cpp b/clang/lib/3C/ArrayBoundsInferenceConsumer.cpp index 5f25bc9af9d9..640a6fd24336 100644 --- a/clang/lib/3C/ArrayBoundsInferenceConsumer.cpp +++ b/clang/lib/3C/ArrayBoundsInferenceConsumer.cpp @@ -144,7 +144,7 @@ static bool needArrayBounds(Decl *D, ProgramInfo &Info, ASTContext *C) { // Map that contains association of allocator functions and indexes of // parameters that correspond to the size of the object being assigned. static std::map> AllocatorSizeAssoc = { - {"malloc", {0}}, {"calloc", {0, 1}}}; + {"malloc", {0}}, {"calloc", {0, 1}} , {"realloc" , {1}}}; // Get the name of the function called by this call expression. static std::string getCalledFunctionName(const CallExpr *CE) { diff --git a/clang/lib/3C/CMakeLists.txt b/clang/lib/3C/CMakeLists.txt index 7a57f8e93ff7..11a38afa6782 100644 --- a/clang/lib/3C/CMakeLists.txt +++ b/clang/lib/3C/CMakeLists.txt @@ -43,6 +43,7 @@ add_clang_library(clang3C CtxSensAVarBounds.cpp DeclRewriter.cpp IntermediateToolHook.cpp + LowerBoundAssignment.cpp MappingVisitor.cpp MultiDecls.cpp PersistentSourceLoc.cpp diff --git a/clang/lib/3C/ConstraintBuilder.cpp b/clang/lib/3C/ConstraintBuilder.cpp index 902acb400c8a..cd043c32d3ce 100644 --- a/clang/lib/3C/ConstraintBuilder.cpp +++ b/clang/lib/3C/ConstraintBuilder.cpp @@ -10,6 +10,7 @@ //===----------------------------------------------------------------------===// #include "clang/3C/ConstraintBuilder.h" +#include "clang/3C/LowerBoundAssignment.h" #include "clang/3C/3CGlobalOptions.h" #include "clang/3C/3CStats.h" #include "clang/3C/ArrayBoundsInferenceConsumer.h" @@ -471,10 +472,12 @@ void VariableAdderConsumer::HandleTranslationUnit(ASTContext &C) { } VariableAdderVisitor VAV = VariableAdderVisitor(&C, Info); + LowerBoundAssignmentFinder LBF = LowerBoundAssignmentFinder(&C, Info); TranslationUnitDecl *TUD = C.getTranslationUnitDecl(); // Collect Variables. for (const auto &D : TUD->decls()) { VAV.TraverseDecl(D); + LBF.TraverseDecl(D); } if (_3COpts.Verbose) diff --git a/clang/lib/3C/ConstraintResolver.cpp b/clang/lib/3C/ConstraintResolver.cpp index 77c264dbfade..e6e7d859d9d5 100644 --- a/clang/lib/3C/ConstraintResolver.cpp +++ b/clang/lib/3C/ConstraintResolver.cpp @@ -464,14 +464,12 @@ CSetBkeyPair ConstraintResolver::getExprConstraintVars(Expr *E) { } else if (DeclaratorDecl *FD = dyn_cast(D)) { /* Allocator call */ if (isFunctionAllocator(std::string(FD->getName()))) { - bool DidInsert = false; IsAllocator = true; + ConstraintVariable *CV = nullptr; if (TypeVars.find(0) != TypeVars.end() && TypeVars[0].isConsistent()) { - ConstraintVariable *CV = TypeVars[0].getConstraint( - Info.getConstraints().getVariables()); - ReturnCVs.insert(CV); - DidInsert = true; + CV = TypeVars[0].getConstraint( + Info.getConstraints().getVariables()); } else if (CE->getNumArgs() > 0) { QualType ArgTy; std::string FuncName = FD->getNameAsString(); @@ -484,8 +482,7 @@ CSetBkeyPair ConstraintResolver::getExprConstraintVars(Expr *E) { *Context, nullptr, 0); PVC->constrainOuterTo(CS, A, ReasonLoc(ALLOCATOR_REASON, ExprPSL), true); - ReturnCVs.insert(PVC); - DidInsert = true; + CV = PVC; if (FuncName.compare("realloc") == 0) { // We will constrain the first arg to the return of // realloc, below @@ -495,11 +492,27 @@ CSetBkeyPair ConstraintResolver::getExprConstraintVars(Expr *E) { } } } - if (!DidInsert) { + if (CV == nullptr) { std::string Rsn = "Unsafe call to allocator function."; PersistentSourceLoc PL = PersistentSourceLoc::mkPSL(CE, *Context); ReturnCVs.insert(PVConstraint::getWildPVConstraint( Info.getConstraints(), ReasonLoc(Rsn, PL))); + } else { + // The ConstraintVariable generated for allocator functions don't + // have associated BoundsKeys. This is problem for lower bound + // inference, because, without a BoundsKey, the algorithm cannot + // detect that the allocator represents a possible lower bound. If + // there is another possible lower bound, the conflict is not + // detected. To avoid this, we fill the bounds key with the bounds + // key for the allocators declaration. + // TODO: This is another place where fully de-specializing + // allocators will simplify code. + CVarOption FuncCV = Info.getVariable(FD, Context); + assert(FuncCV.hasValue() && "Function without constraint variable."); + FVConstraint *FVC = cast(&FuncCV.getValue()); + CV->setBoundsKey(FVC->getExternalReturn()->getBoundsKey()); + + ReturnCVs.insert(CV); } /* Normal function call */ @@ -709,7 +722,7 @@ void ConstraintResolver::constrainLocalAssign(Stmt *TSt, Expr *LHS, Expr *RHS, // Handle pointer arithmetic. auto &ABI = Info.getABoundsInfo(); - ABI.handlePointerAssignment(TSt, LHS, RHS, Context, this); + ABI.handlePointerAssignment(LHS, RHS, Context, this); // Only if all types are enabled and these are not pointers, then track // the assignment. diff --git a/clang/lib/3C/CtxSensAVarBounds.cpp b/clang/lib/3C/CtxSensAVarBounds.cpp index 1fe6d9ff11c7..f3916876fb64 100644 --- a/clang/lib/3C/CtxSensAVarBounds.cpp +++ b/clang/lib/3C/CtxSensAVarBounds.cpp @@ -78,6 +78,12 @@ void CtxSensitiveBoundsKeyHandler::insertCtxSensBoundsKey( ABI->insertProgramVar(NK, NKVar); ABI->RevCtxSensProgVarGraph.addUniqueEdge(OldPV->getKey(), NKVar->getKey()); ABI->CtxSensProgVarGraph.addUniqueEdge(NKVar->getKey(), OldPV->getKey()); + + // For context sensitive structure keys, I want the structure lower bound to + // be invalidated if any use is invalidated, but function parameter lower + // bounds should not be invalidated by invalidated arguments. + if (isa(NPS)) + ABI->LowerBoundGraph.addUniqueEdge(NKVar->getKey(), OldPV->getKey()); } void CtxSensitiveBoundsKeyHandler::createCtxSensBoundsKey( @@ -92,13 +98,14 @@ void CtxSensitiveBoundsKeyHandler::createCtxSensBoundsKey( BoundsPriority TP = Invalid; ABounds *CKBounds = ABI->getBounds(OK, Invalid, &TP); if (CKBounds != nullptr) { - BoundsKey NBK = CKBounds->getBKey(); - if (CBMap.find(NBK) == CBMap.end()) { + BoundsKey LenBK = CKBounds->getLengthKey(); + if (CBMap.find(LenBK) == CBMap.end()) { BoundsKey TmpBK = ++(ABI->BCount); - CBMap[NBK] = TmpBK; - insertCtxSensBoundsKey(CKVar, TmpBK, NPS); + CBMap[LenBK] = TmpBK; + ProgramVar *LenVar = ABI->getProgramVar(LenBK); + insertCtxSensBoundsKey(LenVar, TmpBK, NPS); } - CKBounds = CKBounds->makeCopy(CBMap[NBK]); + CKBounds = CKBounds->makeCopy(CBMap[LenBK]); ABI->replaceBounds(NK, TP, CKBounds); } } diff --git a/clang/lib/3C/DeclRewriter.cpp b/clang/lib/3C/DeclRewriter.cpp index 07257ca1dd14..7b19b25cd6e6 100644 --- a/clang/lib/3C/DeclRewriter.cpp +++ b/clang/lib/3C/DeclRewriter.cpp @@ -27,14 +27,58 @@ using namespace llvm; using namespace clang; +static bool checkNeedsFreshLowerBound(PVConstraint *Defn, std::string UseName, + ProgramInfo &Info, + std::string &DeclName) { + bool NeedsFreshLowerBound = Info.getABoundsInfo().needsFreshLowerBound(Defn); + + if (NeedsFreshLowerBound) { + BoundsKey FreshLB = Info.getABoundsInfo() + .getBounds(Defn->getBoundsKey()) + ->getLowerBoundKey(); + DeclName = Info.getABoundsInfo().getProgramVar(FreshLB)->getVarName(); + } else { + DeclName = UseName; + } + + return NeedsFreshLowerBound; +} + +static std::string buildSupplementaryDecl(PVConstraint *Defn, + DeclaratorDecl *Decl, + ArrayBoundsRewriter &ABR, + ProgramInfo &Info, bool SDeclChecked, + std::string DeclName) { + std::string SDecl = Defn->mkString( + Info.getConstraints(), MKSTRING_OPTS(ForItypeBase = !SDeclChecked)); + if (SDeclChecked) + SDecl += ABR.getBoundsString(Defn, Decl); + SDecl += " = " + DeclName + ";"; + return SDecl; +} + // Generate a new declaration for the PVConstraint using an itype where the // unchecked portion of the type is the original type, and the checked portion -// is the taken from the constraint graph solution. The unchecked portion is -// assigned to string reference Type and the checked (itype) portion is assigned -// to the string reference Itype. -void DeclRewriter::buildItypeDecl(PVConstraint *Defn, DeclaratorDecl *Decl, - std::string &Type, std::string &IType, - ProgramInfo &Info, ArrayBoundsRewriter &ABR) { +// is the taken from the constraint graph solution. +// +// If SDeclChecked = true, then any generated supplementary declaration uses the +// checked type; that's generally what you want if an itype is being used only +// because of -itypes-for-extern. If SDeclChecked = false, then the unchecked +// type is used; that's what you want when the supplementary declaration is +// standing in for a function parameter that got an itype because it is used +// unsafely inside the function. TODO: Instead of using an ad-hoc boolean +// parameter for this, maybe we could just pass in the internal PVConstraint and +// look at that +// (https://github.com/correctcomputation/checkedc-clang/issues/704). +RewrittenDecl +DeclRewriter::buildItypeDecl(PVConstraint *Defn, DeclaratorDecl *Decl, + std::string UseName, ProgramInfo &Info, + ArrayBoundsRewriter &ABR, bool GenerateSDecls, + bool SDeclChecked) { + std::string DeclName; + bool NeedsFreshLowerBound = + checkNeedsFreshLowerBound(Defn, UseName, Info, DeclName); + const EnvironmentMap &Env = Info.getConstraints().getVariables(); // True when the type of this variable is defined by a typedef, and the // constraint variable representing the typedef solved to an unchecked type. @@ -80,10 +124,12 @@ void DeclRewriter::buildItypeDecl(PVConstraint *Defn, DeclaratorDecl *Decl, // - Possible future change: if the internal PVConstraint is partially checked // and we want to use it // (https://github.com/correctcomputation/checkedc-clang/issues/704). + std::string Type; if (IsCheckedTypedef || Defn->getFV() || BaseTypeRenamed) { Type = Defn->mkString(Info.getConstraints(), MKSTRING_OPTS(UnmaskTypedef = IsCheckedTypedef, - ForItypeBase = true)); + ForItypeBase = true, + UseName = DeclName)); } else { // In the remaining cases, the unchecked portion of the itype is just the // original type of the pointer. The first branch tries to generate the type @@ -91,17 +137,41 @@ void DeclRewriter::buildItypeDecl(PVConstraint *Defn, DeclaratorDecl *Decl, // because it avoids changing parameter names, particularly in cases where // multiple functions sharing the same name are defined in different // translation units. - if (isa_and_nonnull(Decl) && !Decl->getName().empty()) - Type = qtyToStr(Decl->getType(), Decl->getNameAsString()); + if (isa_and_nonnull(Decl) && !DeclName.empty()) + Type = qtyToStr(Decl->getType(), DeclName); else Type = Defn->getOriginalTypeWithName(); } - IType = " : itype("; - IType += Defn->mkString(Info.getConstraints(), - MKSTRING_OPTS(EmitName = false, ForItype = true, - UnmaskTypedef = IsUncheckedTypedef)); - IType += ")" + ABR.getBoundsString(Defn, Decl, true); + std::string IType = " : itype(" + + Defn->mkString(Info.getConstraints(), + MKSTRING_OPTS(EmitName = false, ForItype = true, + UnmaskTypedef = IsUncheckedTypedef)) + ")"; + IType += ABR.getBoundsString(Defn, Decl, true, NeedsFreshLowerBound); + + std::string SDecl; + if (GenerateSDecls && NeedsFreshLowerBound) + SDecl = + buildSupplementaryDecl(Defn, Decl, ABR, Info, SDeclChecked, DeclName); + return RewrittenDecl(Type, IType, SDecl); +} + +RewrittenDecl +DeclRewriter::buildCheckedDecl(PVConstraint *Defn, DeclaratorDecl *Decl, + std::string UseName, ProgramInfo &Info, + ArrayBoundsRewriter &ABR, bool GenerateSDecls) { + std::string DeclName; + bool NeedsFreshLowerBound = + checkNeedsFreshLowerBound(Defn, UseName, Info, DeclName); + + std::string Type = + Defn->mkString(Info.getConstraints(), MKSTRING_OPTS(UseName = DeclName)); + std::string IType = + ABR.getBoundsString(Defn, Decl, false, NeedsFreshLowerBound); + std::string SDecl; + if (GenerateSDecls && NeedsFreshLowerBound) + SDecl = buildSupplementaryDecl(Defn, Decl, ABR, Info, true, DeclName); + return RewrittenDecl(Type, IType, SDecl); } // This function is the public entry point for declaration rewriting. @@ -145,7 +215,7 @@ void DeclRewriter::rewriteDecls(ASTContext &Context, ProgramInfo &Info, Var.mkString(Info.getConstraints(), MKSTRING_OPTS(UnmaskTypedef = true)); RewriteThese.insert( - std::make_pair(TD, new MultiDeclMemberReplacement(TD, NewTy))); + std::make_pair(TD, new MultiDeclMemberReplacement(TD, NewTy, {}))); } } } @@ -188,9 +258,14 @@ void DeclRewriter::rewriteDecls(ASTContext &Context, ProgramInfo &Info, "isPartOfFunctionPrototype returns false?"); MultiDeclMemberDecl *MMD = getAsMultiDeclMember(D); assert(MMD && "Unrecognized declaration type."); - std::string ReplacementText = mkStringForPVDecl(MMD, PV, Info); + + RewrittenDecl RD = mkStringForPVDecl(MMD, PV, Info); + std::string ReplacementText = RD.Type + RD.IType; + std::vector SDecl; + if (!RD.SupplementaryDecl.empty()) + SDecl.push_back(RD.SupplementaryDecl); RewriteThese.insert(std::make_pair( - MMD, new MultiDeclMemberReplacement(MMD, ReplacementText))); + MMD, new MultiDeclMemberReplacement(MMD, ReplacementText, SDecl))); } } } @@ -440,19 +515,53 @@ void DeclRewriter::rewriteMultiDecl(MultiDeclInfo &MDI, RSet &ToRewrite) { } } - // Variables in a multi-decl are delimited by commas. The rewritten decls - // are separate statements separated by a semicolon and a newline. + // Processing related to the comma or semicolon ("terminator") that follows + // the multi-decl member. Members are separated by commas, and the last + // member is terminated by a semicolon. The rewritten decls are each + // terminated by a semicolon and are separated by newlines. bool IsLast = (MIt + 1 == MDI.Members.end()); + bool HaveSupplementaryDecls = + (Replacement && !Replacement->getSupplementaryDecls().empty()); + // Unlike in ReplaceSR, we want to start searching for the terminator after + // the entire multi-decl member, including any existing initializer. + SourceRange FullSR = + getDeclSourceRangeWithAnnotations(DL, /*IncludeInitializer=*/true); + // Search for the terminator. + // + // FIXME: If the terminator is hidden inside a macro, + // getNextCommaOrSemicolon will continue scanning and may return a comma or + // semicolon later in the file (which has bizarre consequences if we try to + // use it to rewrite this multi-decl) or fail an assertion if it doesn't + // find one. As a stopgap for the existing regression test in + // macro_rewrite_error.c that has a semicolon inside a macro, we only search + // for the terminator if we actually need it. + SourceLocation Terminator; + if (!IsLast || HaveSupplementaryDecls) { + Terminator = getNextCommaOrSemicolon(FullSR.getEnd()); + } if (!IsLast) { - // This differs from ReplaceSR in that we want to advance past the entire - // multi-decl member, _including_ any existing initializer. - SourceRange SkipSR = - getDeclSourceRangeWithAnnotations(DL, /*IncludeInitializer=*/true); - SourceRange Comma = getNextComma(SkipSR.getEnd()); - rewriteSourceRange(R, Comma, ";\n"); - // Offset by one to skip past what we've just added so it isn't - // overwritten. - PrevEnd = Comma.getEnd().getLocWithOffset(1); + // We expect the terminator to be a comma. Change it to a semicolon. + rewriteSourceRange(R, SourceRange(Terminator, Terminator), ";"); + } + if (HaveSupplementaryDecls) { + emitSupplementaryDeclarations(Replacement->getSupplementaryDecls(), + Terminator); + } + if (!IsLast) { + // Insert a newline between this multi-decl member and the next. The + // Rewriter preserves the order of insertions at the same location, so if + // there are supplementary declarations, this newline will go between them + // and the next member, which is what we want because + // emitSupplementaryDeclarations by itself doesn't add a newline after the + // supplementary declarations. + SourceLocation AfterTerminator = + getLocationAfterToken(Terminator, A.getSourceManager(), + A.getLangOpts()); + R.InsertText(AfterTerminator, "\n"); + // When rewriting the next member, start after the terminator. The + // Rewriter is smart enough not to mess with anything we already inserted + // at that location. + PrevEnd = AfterTerminator; } } @@ -492,21 +601,47 @@ void DeclRewriter::doDeclRewrite(SourceRange &SR, DeclReplacement *N) { void DeclRewriter::rewriteFunctionDecl(FunctionDeclReplacement *N) { rewriteSourceRange(R, N->getSourceRange(A.getSourceManager()), N->getReplacement()); + if (N->getDecl()->isThisDeclarationADefinition()) { + Stmt *S = N->getDecl()->getBody(); + assert("Supplementary declarations should only exist on rewritings for " + "function definitions." && S != nullptr); + // Insert supplementary declarations after the opening curly brace of the + // function body. + emitSupplementaryDeclarations(N->getSupplementaryDecls(), + S->getBeginLoc()); + } +} + +void DeclRewriter::emitSupplementaryDeclarations( + const std::vector &SDecls, SourceLocation Loc) { + // There are no supplementary declarations to emit. The AllDecls String + // will remain empty, so insertText should no-op, but it's still an error to + // insert an empty string at an invalid source location, so short circuit here + // to be safe. + if (SDecls.empty()) + return; + + std::string AllDecls; + for (std::string D : SDecls) + AllDecls += "\n" + D; + + R.InsertText(getLocationAfterToken(Loc, R.getSourceMgr(), R.getLangOpts()), + AllDecls); } -// Uses clangs lexer to find the location of the next comma after +// Uses clangs lexer to find the location of the next comma or semicolon after // the given source location. This is used to find the end of each declaration // within a multi-declaration. -SourceRange DeclRewriter::getNextComma(SourceLocation L) { +SourceLocation DeclRewriter::getNextCommaOrSemicolon(SourceLocation L) { SourceManager &SM = A.getSourceManager(); auto Tok = Lexer::findNextToken(L, SM, A.getLangOpts()); while (Tok.hasValue() && !Tok->is(clang::tok::eof)) { - if (Tok->is(clang::tok::comma)) - return SourceRange(Tok->getLocation(), Tok->getLocation()); + if (Tok->is(clang::tok::comma) || Tok->is(clang::tok::semi)) + return Tok->getLocation(); Tok = Lexer::findNextToken(Tok->getEndLoc(), A.getSourceManager(), A.getLangOpts()); } - llvm_unreachable("Unable to find comma at source location."); + llvm_unreachable("Unable to find comma or semicolon at source location."); } // This function checks how to re-write a function declaration. @@ -571,6 +706,12 @@ bool FunctionDeclBuilder::VisitFunctionDecl(FunctionDecl *FD) { && !FD->isGenericFunction() && !FD->isItypeGenericFunction()) RewriteGeneric = true; + // This will keep track of the supplementary declarations that are required by + // function parameters. The new declarations will be emitted inside the + // function body in the order of function parameters that generated them. + std::vector SDecls; + bool GenerateSDecls = FD->isThisDeclarationADefinition(); + // Get rewritten parameter variable declarations. Try to use // the source for as much as possible. std::vector ParmStrs; @@ -589,23 +730,27 @@ bool FunctionDeclBuilder::VisitFunctionDecl(FunctionDecl *FD) { for (unsigned I = 0; I < FD->getNumParams(); ++I) { ParmVarDecl *PVDecl = FD->getParamDecl(I); const FVComponentVariable *CV = FDConstraint->getCombineParam(I); - std::string Type, IType; - this->buildDeclVar(CV, PVDecl, Type, IType, - PVDecl->getQualifiedNameAsString(), RewriteGeneric, - RewriteParams, RewriteReturn, FD->isStatic()); - ParmStrs.push_back(Type + IType); - ProtoHasItype |= !IType.empty(); + RewrittenDecl RD = + this->buildDeclVar(CV, PVDecl, PVDecl->getQualifiedNameAsString(), + RewriteGeneric, RewriteParams, RewriteReturn, + FD->isStatic(), GenerateSDecls); + ParmStrs.push_back(RD.Type + RD.IType); + if (!RD.SupplementaryDecl.empty()) + SDecls.push_back(RD.SupplementaryDecl); + ProtoHasItype |= !RD.IType.empty(); } } else if (FDConstraint->numParams() != 0) { // lacking params but the constraint has them: mirror the constraint for (unsigned I = 0; I < FDConstraint->numParams(); ++I) { ParmVarDecl *PVDecl = nullptr; const FVComponentVariable *CV = FDConstraint->getCombineParam(I); - std::string Type, IType; - this->buildDeclVar(CV, PVDecl, Type, IType, "", RewriteGeneric, - RewriteParams, RewriteReturn, FD->isStatic()); - ParmStrs.push_back(Type + IType); - ProtoHasItype |= !IType.empty(); + RewrittenDecl RD = + this->buildDeclVar(CV, PVDecl, "", RewriteGeneric, RewriteParams, + RewriteReturn, FD->isStatic(), GenerateSDecls); + ParmStrs.push_back(RD.Type + RD.IType); + if (!RD.SupplementaryDecl.empty()) + SDecls.push_back(RD.SupplementaryDecl); + ProtoHasItype |= !RD.IType.empty(); // FIXME: when the above FIXME is changed this condition will always // be true. This is correct, always rewrite if there were no params // in source but they exist in the constraint variable. @@ -620,14 +765,16 @@ bool FunctionDeclBuilder::VisitFunctionDecl(FunctionDecl *FD) { } // Get rewritten return variable. - std::string ReturnVar = "", ItypeStr = ""; // For now we still need to check if this needs rewriting, see FIXME below // if (!DeclIsTypedef) - this->buildDeclVar(FDConstraint->getCombineReturn(), FD, ReturnVar, ItypeStr, - "", RewriteGeneric, RewriteParams, - RewriteReturn, FD->isStatic()); + RewrittenDecl RewrittenReturn = + this->buildDeclVar(FDConstraint->getCombineReturn(), FD, "", RewriteGeneric, + RewriteParams, RewriteReturn, FD->isStatic(), + GenerateSDecls); + assert("Supplementary declarations should not be generated for return." && + RewrittenReturn.SupplementaryDecl.empty()); - ProtoHasItype |= !ItypeStr.empty(); + ProtoHasItype |= !RewrittenReturn.IType.empty(); // Generic forany and return are in the same rewrite location, so // we must rewrite the return if rewriting generic @@ -702,7 +849,7 @@ bool FunctionDeclBuilder::VisitFunctionDecl(FunctionDecl *FD) { NewSig += ") "; } if (RewriteReturn) - NewSig += getStorageQualifierString(FD) + ReturnVar; + NewSig += getStorageQualifierString(FD) + RewrittenReturn.Type; if (RewriteReturn && RewriteParams) NewSig += FDConstraint->getName(); @@ -720,66 +867,64 @@ bool FunctionDeclBuilder::VisitFunctionDecl(FunctionDecl *FD) { NewSig += ", ..."; NewSig += ")"; } - if (!ItypeStr.empty()) - NewSig = NewSig + ItypeStr; + NewSig = NewSig + RewrittenReturn.IType; // Add new declarations to RewriteThese if it has changed if (RewriteReturn || RewriteParams) { - RewriteThese.insert(std::make_pair( - FD, - new FunctionDeclReplacement(FD, NewSig, RewriteReturn, - RewriteParams, RewriteGeneric))); + auto *FDR = new FunctionDeclReplacement(FD, NewSig, SDecls, RewriteReturn, + RewriteParams, RewriteGeneric); + RewriteThese.insert(std::make_pair(FD, FDR)); } return true; } -void FunctionDeclBuilder::buildCheckedDecl( - PVConstraint *Defn, DeclaratorDecl *Decl, std::string &Type, - std::string &IType, std::string UseName, bool &RewriteParm, - bool &RewriteRet) { - Type = - Defn->mkString(Info.getConstraints(), MKSTRING_OPTS(UseName = UseName)); - //IType = getExistingIType(Defn); - IType = ABRewriter.getBoundsString(Defn, Decl, !IType.empty()); - RewriteParm |= getExistingIType(Defn).empty() != IType.empty() || +RewrittenDecl +FunctionDeclBuilder::buildCheckedDecl(PVConstraint *Defn, DeclaratorDecl *Decl, + std::string UseName, bool &RewriteParm, + bool &RewriteRet, bool GenerateSDecls) { + RewrittenDecl RD = DeclRewriter::buildCheckedDecl(Defn, Decl, UseName, Info, + ABRewriter, GenerateSDecls); + RewriteParm |= getExistingIType(Defn).empty() != RD.IType.empty() || isa_and_nonnull(Decl); RewriteRet |= isa_and_nonnull(Decl); + return RD; } - -void FunctionDeclBuilder::buildItypeDecl(PVConstraint *Defn, - DeclaratorDecl *Decl, - std::string &Type, std::string &IType, - bool &RewriteParm, bool &RewriteRet) { +RewrittenDecl +FunctionDeclBuilder::buildItypeDecl(PVConstraint *Defn, DeclaratorDecl *Decl, + std::string UseName, bool &RewriteParm, + bool &RewriteRet, bool GenerateSDecls, + bool SDeclChecked) { Info.getPerfStats().incrementNumITypes(); - DeclRewriter::buildItypeDecl(Defn, Decl, Type, IType, Info, ABRewriter); + RewrittenDecl RD = DeclRewriter::buildItypeDecl( + Defn, Decl, UseName, Info, ABRewriter, GenerateSDecls, SDeclChecked); RewriteParm = true; RewriteRet |= isa_and_nonnull(Decl); + return RD; } // Note: For a parameter, Type + IType will give the full declaration (including // the name) but the breakdown between Type and IType is not guaranteed. For a // return, Type will be what goes before the name and IType will be what goes // after the parentheses. -void FunctionDeclBuilder::buildDeclVar(const FVComponentVariable *CV, - DeclaratorDecl *Decl, std::string &Type, - std::string &IType, std::string UseName, - bool &RewriteGen, bool &RewriteParm, - bool &RewriteRet, bool StaticFunc) { +RewrittenDecl +FunctionDeclBuilder::buildDeclVar(const FVComponentVariable *CV, + DeclaratorDecl *Decl, std::string UseName, + bool &RewriteGen, bool &RewriteParm, + bool &RewriteRet, bool StaticFunc, + bool GenerateSDecls) { bool CheckedSolution = CV->hasCheckedSolution(Info.getConstraints()); bool ItypeSolution = CV->hasItypeSolution(Info.getConstraints()); if (ItypeSolution || (CheckedSolution && _3COpts.ItypesForExtern && !StaticFunc)) { - buildItypeDecl(CV->getExternal(), Decl, Type, IType, RewriteParm, - RewriteRet); - return; + return buildItypeDecl(CV->getExternal(), Decl, UseName, RewriteParm, + RewriteRet, GenerateSDecls, CheckedSolution); } if (CheckedSolution) { - buildCheckedDecl(CV->getExternal(), Decl, Type, IType, UseName, RewriteParm, - RewriteRet); - return; + return buildCheckedDecl(CV->getExternal(), Decl, UseName, RewriteParm, + RewriteRet, GenerateSDecls); } // Don't add generics if one of the potential generic params is wild, @@ -799,27 +944,25 @@ void FunctionDeclBuilder::buildDeclVar(const FVComponentVariable *CV, // Variables that do not need to be rewritten fall through to here. // Try to use the source. + std::string Type, IType; ParmVarDecl *PVD = dyn_cast_or_null(Decl); if (PVD && !PVD->getName().empty()) { SourceRange Range = PVD->getSourceRange(); if (PVD->hasBoundsExpr()) Range.setEnd(PVD->getBoundsExpr()->getEndLoc()); - if (Range.isValid() && !inParamMultiDecl(PVD)) { + if (Range.isValid() && !inParamMultiDecl(PVD)) Type = getSourceText(Range, *Context); - if (!Type.empty()) { - IType = getExistingIType(CV->getExternal()) + BoundsStr; - return; - } - } // Otherwise, reconstruct the name and type, and reuse the code below for // the itype and bounds. // TODO: Do we care about `register` or anything else this doesn't handle? - Type = qtyToStr(PVD->getOriginalType(), PVD->getNameAsString()); + if (Type.empty()) + Type = qtyToStr(PVD->getOriginalType(), PVD->getNameAsString()); } else { Type = CV->mkTypeStr(Info.getConstraints(), true, CV->getExternal()->getName()); } IType = getExistingIType(CV->getExternal()) + BoundsStr; + return RewrittenDecl(Type, IType, ""); } std::string FunctionDeclBuilder::getExistingIType(ConstraintVariable *DeclC) { diff --git a/clang/lib/3C/LowerBoundAssignment.cpp b/clang/lib/3C/LowerBoundAssignment.cpp new file mode 100644 index 000000000000..e21b60d2a4d3 --- /dev/null +++ b/clang/lib/3C/LowerBoundAssignment.cpp @@ -0,0 +1,151 @@ +//=--LowerBoundAssignment.cpp-------------------------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Implementation of methods in LowerBoundAssignment.h +//===----------------------------------------------------------------------===// + +#include "clang/3C/ProgramInfo.h" +#include "clang/AST/ASTContext.h" + +#include "clang/Rewrite/Core/Rewriter.h" +#include "clang/3C/LowerBoundAssignment.h" +#include "clang/3C/RewriteUtils.h" + +using namespace llvm; +using namespace clang; + +// Visitor to collect all the variables and structure member access that are +// used during the life-time of the visitor. +class CollectDeclsVisitor : public RecursiveASTVisitor { +public: + explicit CollectDeclsVisitor() : ObservedDecls(), ObservedStructAccesses() {} + + virtual ~CollectDeclsVisitor() {} + + bool VisitDeclRefExpr(DeclRefExpr *DRE) { + if (auto *VD = dyn_cast_or_null(DRE->getDecl())) + ObservedDecls.insert(VD); + return true; + } + + // For `a->b` we need to get `a->b` rather than just `b`. This way assignment + // from a field in one instance of a structure to the same field in another + // instance is not treated as pointer arithmetic. + bool VisitMemberExpr(MemberExpr *ME) { + // TODO: Is this cast legit? `getMemberDecl()` returns a `ValueDecl`, but I + // think it can only be a `FieldDecl` for structs in C. + auto *FD = cast(ME->getMemberDecl()); + + CollectDeclsVisitor MEVis; + MEVis.TraverseStmt(ME->getBase()); + // Field access through variable. + for (auto *D : MEVis.getObservedDecls()) { + std::vector SingletonAccessList({FD}); + ObservedStructAccesses.insert(std::make_pair(D, SingletonAccessList)); + } + // Field access through other structure fields. + for (StructAccess SA : MEVis.getObservedStructAccesses()) { + SA.second.push_back(FD); + ObservedStructAccesses.insert(SA); + } + return false; + } + + bool VisitCallExpr(CallExpr *CE) { + // Stop the visitor when we hit a CallExpr. This stops us from treating a + // function call like `a = foo(a);` the same as `a = a + 1`. + return false; + } + + const std::set &getObservedDecls() { return ObservedDecls; } + + // A structure field access is represented + typedef std::pair> StructAccess; + const std::set &getObservedStructAccesses() { + return ObservedStructAccesses; + } + +private: + // Contains all VarDecls seen by this visitor + std::set ObservedDecls; + + // Contains the source representation of all record access (MemberExpression) + // seen by this visitor. + std::set ObservedStructAccesses; +}; + +bool isLowerBoundAssignment(Expr *LHS, Expr *RHS) { + CollectDeclsVisitor LVarVis; + LVarVis.TraverseStmt(LHS); + + CollectDeclsVisitor RVarVis; + RVarVis.TraverseStmt(RHS); + + std::set CommonVars; + std::set CommonStVars; + findIntersection(LVarVis.getObservedDecls(), RVarVis.getObservedDecls(), + CommonVars); + findIntersection(LVarVis.getObservedStructAccesses(), + RVarVis.getObservedStructAccesses(), CommonStVars); + + // If CommonVars is empty, then the same pointer does not appears on the LHS + // and RHS of the assignment. We say that the assignment is a lower bound + // update. + return CommonVars.empty() && CommonStVars.empty(); +} + +bool +LowerBoundAssignmentVisitor::VisitBinaryOperator(BinaryOperator *O) { + if (O->getOpcode() == clang::BO_Assign && + isLowerBoundAssignment(O->getLHS(), O->getRHS())) + visitLowerBoundAssignment(O->getLHS(), O->getRHS()); + return true; +} + +void LowerBoundAssignmentUpdater::visitLowerBoundAssignment(Expr *LHS, + Expr *RHS) { + CVarSet LHSCVs = CR.getExprConstraintVarsSet(LHS); + // It is possible for multiple ConstraintVariables to exist on the LHS + // of an assignment expression; e.g., `*(0 ? a : b) = 0`. If this + // happens, and one of those variables needed range bounds, then the + // following rewriting is not correct. I believe that it can only happen + // when the LHS is a pointer dereference or struct field access. + // Structure fields and inner pointer levels can never have range bounds + // so this case currently is not possible. + assert(LHSCVs.size() == 1 || llvm::count_if(LHSCVs, [this]( + ConstraintVariable *CV) { return ABInfo.needsFreshLowerBound(CV); }) == 0); + for (ConstraintVariable *CV: LHSCVs) { + if (ABInfo.needsFreshLowerBound(CV)) { + BoundsKey LBKey = ABInfo.getBounds( + CV->getBoundsKey())->getLowerBoundKey(); + assert( + "Should not be rewriting assignments for pointer without lower bound!" && + LBKey != 0); + std::string LBName = ABInfo.getProgramVar(LBKey)->getVarName(); + rewriteSourceRange(R, LHS->getSourceRange(), LBName); + R.InsertTextAfter( + getLocationAfterToken(RHS->getEndLoc(), R.getSourceMgr(), + R.getLangOpts()), + ", " + CV->getName() + " = " + LBName); + } + } +} + +void LowerBoundAssignmentFinder::visitLowerBoundAssignment(Expr *LHS, + Expr *RHS) { + SourceLocation RHSEnd = + getLocationAfterToken(RHS->getEndLoc(), C->getSourceManager(), + C->getLangOpts()); + SourceLocation LHSLoc = LHS->getExprLoc(); + if (!(LHSLoc.isValid() && Rewriter::isRewritable(LHSLoc)) || + !(RHSEnd.isValid() && Rewriter::isRewritable(RHSEnd))) { + CVarSet LHSCVs = CR.getExprConstraintVarsSet(LHS); + for (auto *CV: LHSCVs) + if (CV->hasBoundsKey()) + ABInfo.markIneligibleForFreshLowerBound(CV->getBoundsKey()); + } +} \ No newline at end of file diff --git a/clang/lib/3C/ProgramInfo.cpp b/clang/lib/3C/ProgramInfo.cpp index ce93d0bf0ff6..883817899c3f 100644 --- a/clang/lib/3C/ProgramInfo.cpp +++ b/clang/lib/3C/ProgramInfo.cpp @@ -854,6 +854,14 @@ FVConstraint *ProgramInfo::getFuncConstraint(FunctionDecl *D, return getStaticFuncConstraint(FuncName, FileName); } +FVConstraint *ProgramInfo::getFuncConstraint(const std::string &FuncName, + const std::string &FileName, + bool IsStatic) const { + if (IsStatic || !getExtFuncDefnConstraint(FuncName)) + return getStaticFuncConstraint(FuncName, FileName); + return getExtFuncDefnConstraint(FuncName); +} + FVConstraint *ProgramInfo::getFuncFVConstraint(FunctionDecl *FD, ASTContext *C) { std::string FuncName = FD->getNameAsString(); diff --git a/clang/lib/3C/RewriteUtils.cpp b/clang/lib/3C/RewriteUtils.cpp index 83fd8815dd08..cce423f8ad24 100644 --- a/clang/lib/3C/RewriteUtils.cpp +++ b/clang/lib/3C/RewriteUtils.cpp @@ -14,6 +14,7 @@ #include "clang/3C/CastPlacement.h" #include "clang/3C/CheckedRegions.h" #include "clang/3C/DeclRewriter.h" +#include "clang/3C/LowerBoundAssignment.h" #include "clang/3C/TypeVariableAnalysis.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/Tooling/Transformer/SourceCode.h" @@ -21,12 +22,13 @@ using namespace llvm; using namespace clang; -std::string mkStringForPVDecl(MultiDeclMemberDecl *MMD, PVConstraint *PVC, - ProgramInfo &Info) { +RewrittenDecl mkStringForPVDecl(MultiDeclMemberDecl *MMD, PVConstraint *PVC, + ProgramInfo &Info) { // Currently, it's cheap to keep recreating the ArrayBoundsRewriter. If that // ceases to be true, we should pass it along as another argument. ArrayBoundsRewriter ABRewriter{Info}; - std::string NewDecl = getStorageQualifierString(MMD); + + RewrittenDecl RD; bool IsExternGlobalVar = isa(MMD) && cast(MMD)->getFormalLinkage() == Linkage::ExternalLinkage; @@ -42,17 +44,17 @@ std::string mkStringForPVDecl(MultiDeclMemberDecl *MMD, PVConstraint *PVC, // type could have been used. This does provide most of the rewriting // infrastructure that would be required to support these itypes if // constraint generation is updated to handle structure/global itypes. - std::string Type, IType; - // VarDecl and FieldDecl subclass DeclaratorDecl, so the cast will - // always succeed. - DeclRewriter::buildItypeDecl(PVC, cast(MMD), Type, IType, - Info, ABRewriter); - NewDecl += Type + IType; + RD = DeclRewriter::buildItypeDecl(PVC, cast(MMD), + PVC->getName(), Info, ABRewriter, true, + true); } else { - NewDecl += PVC->mkString(Info.getConstraints()) + - ABRewriter.getBoundsString(PVC, MMD); + RD = DeclRewriter::buildCheckedDecl(PVC, cast(MMD), + PVC->getName(), Info, ABRewriter, + true); } - return NewDecl; + RD.Type = getStorageQualifierString(MMD) + RD.Type; + + return RD; } std::string mkStringForDeclWithUnchangedType(MultiDeclMemberDecl *MMD, @@ -95,7 +97,9 @@ std::string mkStringForDeclWithUnchangedType(MultiDeclMemberDecl *MMD, // probably the behavior we want with -itypes-for-extern. If we don't care // about this case, we could alternatively inline the few lines of // mkStringForPVDecl that would still be relevant. - return mkStringForPVDecl(MMD, PVC, Info); + RewrittenDecl RD = mkStringForPVDecl(MMD, PVC, Info); + assert(RD.SupplementaryDecl.empty()); + return RD.Type + RD.IType; } // If the type is not a pointer or array, then it should just equal the base @@ -125,13 +129,37 @@ void rewriteSourceRange(Rewriter &R, const SourceRange &Range, ErrFail); } +// Wrapper for Rewriter::ReplaceText(CharSourceRange, StringRef) that works +// around a bug that occurs when text has previously been inserted at the start +// location of the specified range. +// +// When Rewriter::ReplaceText(CharSourceRange, StringRef) computes the range of +// the rewrite buffer to replace, it sets the start location to be after the +// previous insertion (RewriteBuffer::ReplaceText calls getMappedOffset with +// AfterInserts = true) but sets the length to include the length of the +// previous insertion (it calls getRangeSize with the default RewriteOptions +// with IncludeInsertsAtBeginOfRange = true). This causes the range to extend +// beyond the intended end location by an amount equal to the length of the +// previous insertion. We avoid the problem by calling getRangeSize ourselves +// with IncludeInsertsAtBeginOfRange = false. +// +// TODO: File an upstream Clang bug report if appropriate. As of this writing +// (2021-11-24), we found some discussion of the problem +// (https://reviews.llvm.org/D107503) but not a real entry in the bug tracker. +static bool replaceTextWorkaround(Rewriter &R, const CharSourceRange &Range, + const std::string &NewText) { + Rewriter::RewriteOptions Opts; + Opts.IncludeInsertsAtBeginOfRange = false; + return R.ReplaceText(Range.getBegin(), R.getRangeSize(Range, Opts), NewText); +} + void rewriteSourceRange(Rewriter &R, const CharSourceRange &Range, const std::string &NewText, bool ErrFail) { // Attempt to rewrite the source range. First use the source range directly // from the parameter. bool RewriteSuccess = false; if (canRewrite(R, Range)) - RewriteSuccess = !R.ReplaceText(Range, NewText); + RewriteSuccess = !replaceTextWorkaround(R, Range, NewText); // If initial rewriting attempt failed (either because canRewrite returned // false or because ReplaceText failed (returning true), try rewriting again @@ -140,7 +168,7 @@ void rewriteSourceRange(Rewriter &R, const CharSourceRange &Range, CharSourceRange Expand = clang::Lexer::makeFileCharRange( Range, R.getSourceMgr(), R.getLangOpts()); if (canRewrite(R, Expand)) - RewriteSuccess = !R.ReplaceText(Expand, NewText); + RewriteSuccess = !replaceTextWorkaround(R, Expand, NewText); } // Emit an error if we were unable to rewrite the source range. This is more @@ -578,7 +606,8 @@ SourceLocation FunctionDeclReplacement::getDeclEnd(SourceManager &SM) const { } std::string ArrayBoundsRewriter::getBoundsString(const PVConstraint *PV, - Decl *D, bool Isitype) { + Decl *D, bool Isitype, + bool OmitLowerBound) { auto &ABInfo = Info.getABoundsInfo(); // Try to find a bounds key for the constraint variable. If we can't, @@ -596,9 +625,13 @@ std::string ArrayBoundsRewriter::getBoundsString(const PVConstraint *PV, if (ValidBKey && !PV->hasSomeSizedArr()) { ABounds *ArrB = ABInfo.getBounds(DK); - // Only we we have bounds and no pointer arithmetic on the variable. - if (ArrB != nullptr && !ABInfo.hasPointerArithmetic(DK)) { - BString = ArrB->mkString(&ABInfo, D); + // If we have pointer arithmetic and cannot add range bounds, then do not + // emit any bounds string. + if (ArrB != nullptr && ABInfo.hasLowerBound(DK)) { + if (OmitLowerBound) + BString = ArrB->mkStringWithoutLowerBound(&ABInfo, D); + else + BString = ArrB->mkString(&ABInfo, D, DK); if (!BString.empty()) BString = Pfix + BString; } @@ -691,6 +724,7 @@ void RewriteConsumer::HandleTranslationUnit(ASTContext &Context) { CastPlacementVisitor ECPV(&Context, Info, R, CLV.getExprsWithCast()); TypeExprRewriter TER(&Context, Info, R); TypeArgumentAdder TPA(&Context, Info, R); + LowerBoundAssignmentUpdater AU(&Context, Info, R); TranslationUnitDecl *TUD = Context.getTranslationUnitDecl(); for (const auto &D : TUD->decls()) { if (_3COpts.AddCheckedRegions) { @@ -709,6 +743,7 @@ void RewriteConsumer::HandleTranslationUnit(ASTContext &Context) { CLV.TraverseDecl(D); ECPV.TraverseDecl(D); TPA.TraverseDecl(D); + AU.TraverseDecl(D); } // Output files. diff --git a/clang/lib/3C/StructInit.cpp b/clang/lib/3C/StructInit.cpp index fcc53f641f23..cdc7690a5b6c 100644 --- a/clang/lib/3C/StructInit.cpp +++ b/clang/lib/3C/StructInit.cpp @@ -65,7 +65,7 @@ bool StructVariableInitializer::VisitVarDecl(VarDecl *VD) { // Create replacement declaration text with an initializer. std::string ToReplace = mkStringForDeclWithUnchangedType(VD, I) + " = {}"; RewriteThese.insert( - std::make_pair(VD, new MultiDeclMemberReplacement(VD, ToReplace))); + std::make_pair(VD, new MultiDeclMemberReplacement(VD, ToReplace, {}))); } return true; } diff --git a/clang/lib/3C/Utils.cpp b/clang/lib/3C/Utils.cpp index 7da01da04cb6..bb3b2507f822 100644 --- a/clang/lib/3C/Utils.cpp +++ b/clang/lib/3C/Utils.cpp @@ -15,6 +15,7 @@ #include "clang/Sema/Sema.h" #include "llvm/Support/Path.h" #include +#include using namespace llvm; using namespace clang; @@ -209,6 +210,20 @@ bool isPtrOrArrayType(const clang::QualType &QT) { return QT->isPointerType() || QT->isArrayType(); } +bool isArrayType(const QualType &QT) { + // This is an array type. Just return true. + if (QT->isArrayType()) + return true; + + // It might instead be an array which has decayed to a pointer. We still want + // to treat this as an array. + const DecayedType *T = QT->getAs(); + if (T && T->getOriginalType()->isArrayType()) + return true; + + return false; +} + bool isNullableType(const clang::QualType &QT) { if (QT.getTypePtrOrNull()) return QT->isPointerType() || QT->isArrayType() || QT->isIntegerType(); @@ -606,6 +621,11 @@ SourceLocation getCheckedCAnnotationsEnd(const Decl *D) { return End; } +SourceLocation getLocationAfterToken(SourceLocation SL, const SourceManager &SM, + const LangOptions &LO) { + return Lexer::getLocForEndOfToken(SL, 0, SM, LO); +} + SourceRange getDeclSourceRangeWithAnnotations(const clang::Decl *D, bool IncludeInitializer) { SourceManager &SM = D->getASTContext().getSourceManager(); diff --git a/clang/test/3C/basic.c b/clang/test/3C/basic.c index 7e82b61a8008..01e821b717d2 100644 --- a/clang/test/3C/basic.c +++ b/clang/test/3C/basic.c @@ -131,7 +131,7 @@ void basic_realloc(int count) { free(ptr); } //CHECK_NOALL: int *ptr = (int *)malloc(n1 * sizeof(int)); -//CHECK_ALL: _Array_ptr ptr : count(n1) = (_Array_ptr)malloc(n1 * sizeof(int)); +//CHECK_ALL: _Array_ptr ptr = (_Array_ptr)malloc(n1 * sizeof(int)); struct student { char name[30]; diff --git a/clang/test/3C/generated_tests/arrboth.c b/clang/test/3C/generated_tests/arrboth.c index 5ce4d8119577..b975e44dc238 100644 --- a/clang/test/3C/generated_tests/arrboth.c +++ b/clang/test/3C/generated_tests/arrboth.c @@ -107,11 +107,12 @@ int *sus(int *x, int *y) { //CHECK: x = (int *)5; int *z = calloc(5, sizeof(int)); //CHECK_NOALL: int *z = calloc(5, sizeof(int)); - //CHECK_ALL: _Array_ptr z = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; int i, fac; int *p; //CHECK_NOALL: int *p; - //CHECK_ALL: _Array_ptr p = ((void *)0); + //CHECK_ALL: _Array_ptr p : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = ((void *)0); for (i = 0, p = z, fac = 1; i < 5; ++i, p++, fac *= i) { //CHECK_NOALL: for (i = 0, p = z, fac = 1; i < 5; ++i, p++, fac *= i) { //CHECK_ALL: for (i = 0, p = z, fac = 1; i < 5; ++i, p++, fac *= i) _Checked { diff --git a/clang/test/3C/generated_tests/arrbothmulti2.c b/clang/test/3C/generated_tests/arrbothmulti2.c index 7e1f3f5bf93d..e032c28e377d 100644 --- a/clang/test/3C/generated_tests/arrbothmulti2.c +++ b/clang/test/3C/generated_tests/arrbothmulti2.c @@ -114,11 +114,12 @@ int *sus(int *x, int *y) { //CHECK: x = (int *)5; int *z = calloc(5, sizeof(int)); //CHECK_NOALL: int *z = calloc(5, sizeof(int)); - //CHECK_ALL: _Array_ptr z = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; int i, fac; int *p; //CHECK_NOALL: int *p; - //CHECK_ALL: _Array_ptr p = ((void *)0); + //CHECK_ALL: _Array_ptr p : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = ((void *)0); for (i = 0, p = z, fac = 1; i < 5; ++i, p++, fac *= i) { //CHECK_NOALL: for (i = 0, p = z, fac = 1; i < 5; ++i, p++, fac *= i) { //CHECK_ALL: for (i = 0, p = z, fac = 1; i < 5; ++i, p++, fac *= i) _Checked { diff --git a/clang/test/3C/generated_tests/arrcallee.c b/clang/test/3C/generated_tests/arrcallee.c index 4ff1ac043a6b..4f27cea0e703 100644 --- a/clang/test/3C/generated_tests/arrcallee.c +++ b/clang/test/3C/generated_tests/arrcallee.c @@ -107,11 +107,12 @@ int *sus(int *x, int *y) { //CHECK: x = (int *)5; int *z = calloc(5, sizeof(int)); //CHECK_NOALL: int *z = calloc(5, sizeof(int)); - //CHECK_ALL: _Array_ptr z = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; int i, fac; int *p; //CHECK_NOALL: int *p; - //CHECK_ALL: _Array_ptr p = ((void *)0); + //CHECK_ALL: _Array_ptr p : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = ((void *)0); for (i = 0, p = z, fac = 1; i < 5; ++i, p++, fac *= i) { //CHECK_NOALL: for (i = 0, p = z, fac = 1; i < 5; ++i, p++, fac *= i) { //CHECK_ALL: for (i = 0, p = z, fac = 1; i < 5; ++i, p++, fac *= i) _Checked { diff --git a/clang/test/3C/generated_tests/arrcalleemulti2.c b/clang/test/3C/generated_tests/arrcalleemulti2.c index 0780cdb00c1c..472effa99082 100644 --- a/clang/test/3C/generated_tests/arrcalleemulti2.c +++ b/clang/test/3C/generated_tests/arrcalleemulti2.c @@ -114,11 +114,12 @@ int *sus(int *x, int *y) { //CHECK: x = (int *)5; int *z = calloc(5, sizeof(int)); //CHECK_NOALL: int *z = calloc(5, sizeof(int)); - //CHECK_ALL: _Array_ptr z = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; int i, fac; int *p; //CHECK_NOALL: int *p; - //CHECK_ALL: _Array_ptr p = ((void *)0); + //CHECK_ALL: _Array_ptr p : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = ((void *)0); for (i = 0, p = z, fac = 1; i < 5; ++i, p++, fac *= i) { //CHECK_NOALL: for (i = 0, p = z, fac = 1; i < 5; ++i, p++, fac *= i) { //CHECK_ALL: for (i = 0, p = z, fac = 1; i < 5; ++i, p++, fac *= i) _Checked { diff --git a/clang/test/3C/generated_tests/arrcaller.c b/clang/test/3C/generated_tests/arrcaller.c index 2adb6c448150..ed1de3fccb9e 100644 --- a/clang/test/3C/generated_tests/arrcaller.c +++ b/clang/test/3C/generated_tests/arrcaller.c @@ -111,7 +111,7 @@ int *sus(int *x, int *y) { int i, fac; int *p; //CHECK_NOALL: int *p; - //CHECK_ALL: _Array_ptr p = ((void *)0); + //CHECK_ALL: _Array_ptr p : bounds(z, z + 5) = ((void *)0); for (i = 0, p = z, fac = 1; i < 5; ++i, p++, fac *= i) { //CHECK_NOALL: for (i = 0, p = z, fac = 1; i < 5; ++i, p++, fac *= i) { //CHECK_ALL: for (i = 0, p = z, fac = 1; i < 5; ++i, p++, fac *= i) _Checked { @@ -142,7 +142,8 @@ int *bar() { //CHECK: _Ptr y = malloc(sizeof(int)); int *z = sus(x, y); //CHECK_NOALL: int *z = sus(x, y); - //CHECK_ALL: _Array_ptr z = sus(x, y); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = sus(x, y); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; z += 2; return z; } diff --git a/clang/test/3C/generated_tests/arrcallermulti1.c b/clang/test/3C/generated_tests/arrcallermulti1.c index c2f5bc204629..c2d132324cbf 100644 --- a/clang/test/3C/generated_tests/arrcallermulti1.c +++ b/clang/test/3C/generated_tests/arrcallermulti1.c @@ -133,7 +133,8 @@ int *bar() { //CHECK: _Ptr y = malloc(sizeof(int)); int *z = sus(x, y); //CHECK_NOALL: int *z = sus(x, y); - //CHECK_ALL: _Array_ptr z = sus(x, y); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = sus(x, y); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; z += 2; return z; } diff --git a/clang/test/3C/generated_tests/arrcallermulti2.c b/clang/test/3C/generated_tests/arrcallermulti2.c index 0d5870e7e507..35b3a330a851 100644 --- a/clang/test/3C/generated_tests/arrcallermulti2.c +++ b/clang/test/3C/generated_tests/arrcallermulti2.c @@ -118,7 +118,7 @@ int *sus(int *x, int *y) { int i, fac; int *p; //CHECK_NOALL: int *p; - //CHECK_ALL: _Array_ptr p = ((void *)0); + //CHECK_ALL: _Array_ptr p : bounds(z, z + 5) = ((void *)0); for (i = 0, p = z, fac = 1; i < 5; ++i, p++, fac *= i) { //CHECK_NOALL: for (i = 0, p = z, fac = 1; i < 5; ++i, p++, fac *= i) { //CHECK_ALL: for (i = 0, p = z, fac = 1; i < 5; ++i, p++, fac *= i) _Checked { diff --git a/clang/test/3C/generated_tests/arrofstructboth.c b/clang/test/3C/generated_tests/arrofstructboth.c index 6f93aad9db47..eb07991eef34 100644 --- a/clang/test/3C/generated_tests/arrofstructboth.c +++ b/clang/test/3C/generated_tests/arrofstructboth.c @@ -108,7 +108,8 @@ struct general **sus(struct general *x, struct general *y) { //CHECK: x = (struct general *)5; struct general **z = calloc(5, sizeof(struct general *)); //CHECK_NOALL: struct general **z = calloc(5, sizeof(struct general *)); - //CHECK_ALL: _Array_ptr<_Ptr> z = calloc<_Ptr>(5, sizeof(struct general *)); + //CHECK_ALL: _Array_ptr<_Ptr> __3c_lower_bound_z : count(5) = calloc<_Ptr>(5, sizeof(struct general *)); + //CHECK_ALL: _Array_ptr<_Ptr> z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; struct general *curr = y; //CHECK_NOALL: struct general *curr = y; //CHECK_ALL: _Ptr curr = y; diff --git a/clang/test/3C/generated_tests/arrofstructbothmulti2.c b/clang/test/3C/generated_tests/arrofstructbothmulti2.c index a7ea8805af6e..4511b2a23929 100644 --- a/clang/test/3C/generated_tests/arrofstructbothmulti2.c +++ b/clang/test/3C/generated_tests/arrofstructbothmulti2.c @@ -115,7 +115,8 @@ struct general **sus(struct general *x, struct general *y) { //CHECK: x = (struct general *)5; struct general **z = calloc(5, sizeof(struct general *)); //CHECK_NOALL: struct general **z = calloc(5, sizeof(struct general *)); - //CHECK_ALL: _Array_ptr<_Ptr> z = calloc<_Ptr>(5, sizeof(struct general *)); + //CHECK_ALL: _Array_ptr<_Ptr> __3c_lower_bound_z : count(5) = calloc<_Ptr>(5, sizeof(struct general *)); + //CHECK_ALL: _Array_ptr<_Ptr> z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; struct general *curr = y; //CHECK_NOALL: struct general *curr = y; //CHECK_ALL: _Ptr curr = y; diff --git a/clang/test/3C/generated_tests/arrofstructcallee.c b/clang/test/3C/generated_tests/arrofstructcallee.c index 017fdbc4b3c3..bd4862c3adaf 100644 --- a/clang/test/3C/generated_tests/arrofstructcallee.c +++ b/clang/test/3C/generated_tests/arrofstructcallee.c @@ -108,7 +108,8 @@ struct general **sus(struct general *x, struct general *y) { //CHECK: x = (struct general *)5; struct general **z = calloc(5, sizeof(struct general *)); //CHECK_NOALL: struct general **z = calloc(5, sizeof(struct general *)); - //CHECK_ALL: _Array_ptr<_Ptr> z = calloc<_Ptr>(5, sizeof(struct general *)); + //CHECK_ALL: _Array_ptr<_Ptr> __3c_lower_bound_z : count(5) = calloc<_Ptr>(5, sizeof(struct general *)); + //CHECK_ALL: _Array_ptr<_Ptr> z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; struct general *curr = y; //CHECK_NOALL: struct general *curr = y; //CHECK_ALL: _Ptr curr = y; diff --git a/clang/test/3C/generated_tests/arrofstructcalleemulti2.c b/clang/test/3C/generated_tests/arrofstructcalleemulti2.c index 1fae0a2b9a68..5545acd45e64 100644 --- a/clang/test/3C/generated_tests/arrofstructcalleemulti2.c +++ b/clang/test/3C/generated_tests/arrofstructcalleemulti2.c @@ -115,7 +115,8 @@ struct general **sus(struct general *x, struct general *y) { //CHECK: x = (struct general *)5; struct general **z = calloc(5, sizeof(struct general *)); //CHECK_NOALL: struct general **z = calloc(5, sizeof(struct general *)); - //CHECK_ALL: _Array_ptr<_Ptr> z = calloc<_Ptr>(5, sizeof(struct general *)); + //CHECK_ALL: _Array_ptr<_Ptr> __3c_lower_bound_z : count(5) = calloc<_Ptr>(5, sizeof(struct general *)); + //CHECK_ALL: _Array_ptr<_Ptr> z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; struct general *curr = y; //CHECK_NOALL: struct general *curr = y; //CHECK_ALL: _Ptr curr = y; diff --git a/clang/test/3C/generated_tests/arrofstructcaller.c b/clang/test/3C/generated_tests/arrofstructcaller.c index 96fe2390f2bc..d87a8136125d 100644 --- a/clang/test/3C/generated_tests/arrofstructcaller.c +++ b/clang/test/3C/generated_tests/arrofstructcaller.c @@ -167,7 +167,8 @@ struct general **bar() { } struct general **z = sus(x, y); //CHECK_NOALL: struct general **z = sus(x, y); - //CHECK_ALL: _Array_ptr<_Ptr> z = sus(x, y); + //CHECK_ALL: _Array_ptr<_Ptr> __3c_lower_bound_z : count(5) = sus(x, y); + //CHECK_ALL: _Array_ptr<_Ptr> z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; z += 2; return z; } diff --git a/clang/test/3C/generated_tests/arrofstructcallermulti1.c b/clang/test/3C/generated_tests/arrofstructcallermulti1.c index afb24d071058..84af896ac164 100644 --- a/clang/test/3C/generated_tests/arrofstructcallermulti1.c +++ b/clang/test/3C/generated_tests/arrofstructcallermulti1.c @@ -151,7 +151,8 @@ struct general **bar() { } struct general **z = sus(x, y); //CHECK_NOALL: struct general **z = sus(x, y); - //CHECK_ALL: _Array_ptr<_Ptr> z = sus(x, y); + //CHECK_ALL: _Array_ptr<_Ptr> __3c_lower_bound_z : count(5) = sus(x, y); + //CHECK_ALL: _Array_ptr<_Ptr> z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; z += 2; return z; } diff --git a/clang/test/3C/generated_tests/arrofstructprotoboth.c b/clang/test/3C/generated_tests/arrofstructprotoboth.c index 922344e4a381..fdf49b838b36 100644 --- a/clang/test/3C/generated_tests/arrofstructprotoboth.c +++ b/clang/test/3C/generated_tests/arrofstructprotoboth.c @@ -165,7 +165,8 @@ struct general **sus(struct general *x, struct general *y) { //CHECK: x = (struct general *)5; struct general **z = calloc(5, sizeof(struct general *)); //CHECK_NOALL: struct general **z = calloc(5, sizeof(struct general *)); - //CHECK_ALL: _Array_ptr<_Ptr> z = calloc<_Ptr>(5, sizeof(struct general *)); + //CHECK_ALL: _Array_ptr<_Ptr> __3c_lower_bound_z : count(5) = calloc<_Ptr>(5, sizeof(struct general *)); + //CHECK_ALL: _Array_ptr<_Ptr> z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; struct general *curr = y; //CHECK_NOALL: struct general *curr = y; //CHECK_ALL: _Ptr curr = y; diff --git a/clang/test/3C/generated_tests/arrofstructprotocallee.c b/clang/test/3C/generated_tests/arrofstructprotocallee.c index be3522fbe01a..b62d0e704bcc 100644 --- a/clang/test/3C/generated_tests/arrofstructprotocallee.c +++ b/clang/test/3C/generated_tests/arrofstructprotocallee.c @@ -164,7 +164,8 @@ struct general **sus(struct general *x, struct general *y) { //CHECK: x = (struct general *)5; struct general **z = calloc(5, sizeof(struct general *)); //CHECK_NOALL: struct general **z = calloc(5, sizeof(struct general *)); - //CHECK_ALL: _Array_ptr<_Ptr> z = calloc<_Ptr>(5, sizeof(struct general *)); + //CHECK_ALL: _Array_ptr<_Ptr> __3c_lower_bound_z : count(5) = calloc<_Ptr>(5, sizeof(struct general *)); + //CHECK_ALL: _Array_ptr<_Ptr> z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; struct general *curr = y; //CHECK_NOALL: struct general *curr = y; //CHECK_ALL: _Ptr curr = y; diff --git a/clang/test/3C/generated_tests/arrofstructprotocaller.c b/clang/test/3C/generated_tests/arrofstructprotocaller.c index e8b2f0f01ae4..c21ad70fc0dc 100644 --- a/clang/test/3C/generated_tests/arrofstructprotocaller.c +++ b/clang/test/3C/generated_tests/arrofstructprotocaller.c @@ -153,7 +153,8 @@ struct general **bar() { } struct general **z = sus(x, y); //CHECK_NOALL: struct general **z = sus(x, y); - //CHECK_ALL: _Array_ptr<_Ptr> z = sus(x, y); + //CHECK_ALL: _Array_ptr<_Ptr> __3c_lower_bound_z : count(5) = sus(x, y); + //CHECK_ALL: _Array_ptr<_Ptr> z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; z += 2; return z; } diff --git a/clang/test/3C/generated_tests/arrprotoboth.c b/clang/test/3C/generated_tests/arrprotoboth.c index 888f692948e4..b3f1cb81bbf5 100644 --- a/clang/test/3C/generated_tests/arrprotoboth.c +++ b/clang/test/3C/generated_tests/arrprotoboth.c @@ -142,11 +142,12 @@ int *sus(int *x, int *y) { //CHECK: x = (int *)5; int *z = calloc(5, sizeof(int)); //CHECK_NOALL: int *z = calloc(5, sizeof(int)); - //CHECK_ALL: _Array_ptr z = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; int i, fac; int *p; //CHECK_NOALL: int *p; - //CHECK_ALL: _Array_ptr p = ((void *)0); + //CHECK_ALL: _Array_ptr p : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = ((void *)0); for (i = 0, p = z, fac = 1; i < 5; ++i, p++, fac *= i) { //CHECK_NOALL: for (i = 0, p = z, fac = 1; i < 5; ++i, p++, fac *= i) { //CHECK_ALL: for (i = 0, p = z, fac = 1; i < 5; ++i, p++, fac *= i) _Checked { diff --git a/clang/test/3C/generated_tests/arrprotocallee.c b/clang/test/3C/generated_tests/arrprotocallee.c index daab972232dc..6e05f211361d 100644 --- a/clang/test/3C/generated_tests/arrprotocallee.c +++ b/clang/test/3C/generated_tests/arrprotocallee.c @@ -141,11 +141,12 @@ int *sus(int *x, int *y) { //CHECK: x = (int *)5; int *z = calloc(5, sizeof(int)); //CHECK_NOALL: int *z = calloc(5, sizeof(int)); - //CHECK_ALL: _Array_ptr z = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; int i, fac; int *p; //CHECK_NOALL: int *p; - //CHECK_ALL: _Array_ptr p = ((void *)0); + //CHECK_ALL: _Array_ptr p : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = ((void *)0); for (i = 0, p = z, fac = 1; i < 5; ++i, p++, fac *= i) { //CHECK_NOALL: for (i = 0, p = z, fac = 1; i < 5; ++i, p++, fac *= i) { //CHECK_ALL: for (i = 0, p = z, fac = 1; i < 5; ++i, p++, fac *= i) _Checked { diff --git a/clang/test/3C/generated_tests/arrprotocaller.c b/clang/test/3C/generated_tests/arrprotocaller.c index 1bb5da125dca..d43c1e75e737 100644 --- a/clang/test/3C/generated_tests/arrprotocaller.c +++ b/clang/test/3C/generated_tests/arrprotocaller.c @@ -130,7 +130,8 @@ int *bar() { //CHECK: _Ptr y = malloc(sizeof(int)); int *z = sus(x, y); //CHECK_NOALL: int *z = sus(x, y); - //CHECK_ALL: _Array_ptr z = sus(x, y); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = sus(x, y); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; z += 2; return z; } @@ -146,7 +147,7 @@ int *sus(int *x, int *y) { int i, fac; int *p; //CHECK_NOALL: int *p; - //CHECK_ALL: _Array_ptr p = ((void *)0); + //CHECK_ALL: _Array_ptr p : bounds(z, z + 5) = ((void *)0); for (i = 0, p = z, fac = 1; i < 5; ++i, p++, fac *= i) { //CHECK_NOALL: for (i = 0, p = z, fac = 1; i < 5; ++i, p++, fac *= i) { //CHECK_ALL: for (i = 0, p = z, fac = 1; i < 5; ++i, p++, fac *= i) _Checked { diff --git a/clang/test/3C/generated_tests/arrprotosafe.c b/clang/test/3C/generated_tests/arrprotosafe.c index f23f81b52e6b..9eb627be85bd 100644 --- a/clang/test/3C/generated_tests/arrprotosafe.c +++ b/clang/test/3C/generated_tests/arrprotosafe.c @@ -144,7 +144,7 @@ int *sus(int *x, int *y) { int i, fac; int *p; //CHECK_NOALL: int *p; - //CHECK_ALL: _Array_ptr p = ((void *)0); + //CHECK_ALL: _Array_ptr p : bounds(z, z + 5) = ((void *)0); for (i = 0, p = z, fac = 1; i < 5; ++i, p++, fac *= i) { //CHECK_NOALL: for (i = 0, p = z, fac = 1; i < 5; ++i, p++, fac *= i) { //CHECK_ALL: for (i = 0, p = z, fac = 1; i < 5; ++i, p++, fac *= i) _Checked { diff --git a/clang/test/3C/generated_tests/arrsafe.c b/clang/test/3C/generated_tests/arrsafe.c index 6f32b13da155..0ebcdf9fc554 100644 --- a/clang/test/3C/generated_tests/arrsafe.c +++ b/clang/test/3C/generated_tests/arrsafe.c @@ -110,7 +110,7 @@ int *sus(int *x, int *y) { int i, fac; int *p; //CHECK_NOALL: int *p; - //CHECK_ALL: _Array_ptr p = ((void *)0); + //CHECK_ALL: _Array_ptr p : bounds(z, z + 5) = ((void *)0); for (i = 0, p = z, fac = 1; i < 5; ++i, p++, fac *= i) { //CHECK_NOALL: for (i = 0, p = z, fac = 1; i < 5; ++i, p++, fac *= i) { //CHECK_ALL: for (i = 0, p = z, fac = 1; i < 5; ++i, p++, fac *= i) _Checked { diff --git a/clang/test/3C/generated_tests/arrsafemulti2.c b/clang/test/3C/generated_tests/arrsafemulti2.c index ad9b1fbcff7c..c31ade211125 100644 --- a/clang/test/3C/generated_tests/arrsafemulti2.c +++ b/clang/test/3C/generated_tests/arrsafemulti2.c @@ -117,7 +117,7 @@ int *sus(int *x, int *y) { int i, fac; int *p; //CHECK_NOALL: int *p; - //CHECK_ALL: _Array_ptr p = ((void *)0); + //CHECK_ALL: _Array_ptr p : bounds(z, z + 5) = ((void *)0); for (i = 0, p = z, fac = 1; i < 5; ++i, p++, fac *= i) { //CHECK_NOALL: for (i = 0, p = z, fac = 1; i < 5; ++i, p++, fac *= i) { //CHECK_ALL: for (i = 0, p = z, fac = 1; i < 5; ++i, p++, fac *= i) _Checked { diff --git a/clang/test/3C/generated_tests/arrstructboth.c b/clang/test/3C/generated_tests/arrstructboth.c index 8f8d5712f83e..63831aeb3f1d 100644 --- a/clang/test/3C/generated_tests/arrstructboth.c +++ b/clang/test/3C/generated_tests/arrstructboth.c @@ -107,7 +107,8 @@ int *sus(struct general *x, struct general *y) { //CHECK: x = (struct general *)5; int *z = calloc(5, sizeof(int)); //CHECK_NOALL: int *z = calloc(5, sizeof(int)); - //CHECK_ALL: _Array_ptr z = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; struct general *p = y; //CHECK: _Ptr p = y; int i; diff --git a/clang/test/3C/generated_tests/arrstructbothmulti2.c b/clang/test/3C/generated_tests/arrstructbothmulti2.c index c27f62b9041a..50d5172eedc9 100644 --- a/clang/test/3C/generated_tests/arrstructbothmulti2.c +++ b/clang/test/3C/generated_tests/arrstructbothmulti2.c @@ -114,7 +114,8 @@ int *sus(struct general *x, struct general *y) { //CHECK: x = (struct general *)5; int *z = calloc(5, sizeof(int)); //CHECK_NOALL: int *z = calloc(5, sizeof(int)); - //CHECK_ALL: _Array_ptr z = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; struct general *p = y; //CHECK: _Ptr p = y; int i; diff --git a/clang/test/3C/generated_tests/arrstructcallee.c b/clang/test/3C/generated_tests/arrstructcallee.c index 43dabba34c05..a0c18b168681 100644 --- a/clang/test/3C/generated_tests/arrstructcallee.c +++ b/clang/test/3C/generated_tests/arrstructcallee.c @@ -107,7 +107,8 @@ int *sus(struct general *x, struct general *y) { //CHECK: x = (struct general *)5; int *z = calloc(5, sizeof(int)); //CHECK_NOALL: int *z = calloc(5, sizeof(int)); - //CHECK_ALL: _Array_ptr z = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; struct general *p = y; //CHECK: _Ptr p = y; int i; diff --git a/clang/test/3C/generated_tests/arrstructcalleemulti2.c b/clang/test/3C/generated_tests/arrstructcalleemulti2.c index 7bfa9f6024f3..bc34fdba0958 100644 --- a/clang/test/3C/generated_tests/arrstructcalleemulti2.c +++ b/clang/test/3C/generated_tests/arrstructcalleemulti2.c @@ -114,7 +114,8 @@ int *sus(struct general *x, struct general *y) { //CHECK: x = (struct general *)5; int *z = calloc(5, sizeof(int)); //CHECK_NOALL: int *z = calloc(5, sizeof(int)); - //CHECK_ALL: _Array_ptr z = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; struct general *p = y; //CHECK: _Ptr p = y; int i; diff --git a/clang/test/3C/generated_tests/arrstructcaller.c b/clang/test/3C/generated_tests/arrstructcaller.c index d96b34a027ac..812d95ceb782 100644 --- a/clang/test/3C/generated_tests/arrstructcaller.c +++ b/clang/test/3C/generated_tests/arrstructcaller.c @@ -160,7 +160,8 @@ int *bar() { } int *z = sus(x, y); //CHECK_NOALL: int *z = sus(x, y); - //CHECK_ALL: _Array_ptr z = sus(x, y); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = sus(x, y); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; z += 2; return z; } diff --git a/clang/test/3C/generated_tests/arrstructcallermulti1.c b/clang/test/3C/generated_tests/arrstructcallermulti1.c index d02c6bee0552..180ee54753ed 100644 --- a/clang/test/3C/generated_tests/arrstructcallermulti1.c +++ b/clang/test/3C/generated_tests/arrstructcallermulti1.c @@ -151,7 +151,8 @@ int *bar() { } int *z = sus(x, y); //CHECK_NOALL: int *z = sus(x, y); - //CHECK_ALL: _Array_ptr z = sus(x, y); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = sus(x, y); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; z += 2; return z; } diff --git a/clang/test/3C/generated_tests/arrstructprotoboth.c b/clang/test/3C/generated_tests/arrstructprotoboth.c index eddbf7d23bd3..b74573ab5b31 100644 --- a/clang/test/3C/generated_tests/arrstructprotoboth.c +++ b/clang/test/3C/generated_tests/arrstructprotoboth.c @@ -160,7 +160,8 @@ int *sus(struct general *x, struct general *y) { //CHECK: x = (struct general *)5; int *z = calloc(5, sizeof(int)); //CHECK_NOALL: int *z = calloc(5, sizeof(int)); - //CHECK_ALL: _Array_ptr z = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; struct general *p = y; //CHECK: _Ptr p = y; int i; diff --git a/clang/test/3C/generated_tests/arrstructprotocallee.c b/clang/test/3C/generated_tests/arrstructprotocallee.c index a125dfe5c41c..709a4825a7ff 100644 --- a/clang/test/3C/generated_tests/arrstructprotocallee.c +++ b/clang/test/3C/generated_tests/arrstructprotocallee.c @@ -159,7 +159,8 @@ int *sus(struct general *x, struct general *y) { //CHECK: x = (struct general *)5; int *z = calloc(5, sizeof(int)); //CHECK_NOALL: int *z = calloc(5, sizeof(int)); - //CHECK_ALL: _Array_ptr z = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; struct general *p = y; //CHECK: _Ptr p = y; int i; diff --git a/clang/test/3C/generated_tests/arrstructprotocaller.c b/clang/test/3C/generated_tests/arrstructprotocaller.c index 2666e0d85459..f0e03f3ee881 100644 --- a/clang/test/3C/generated_tests/arrstructprotocaller.c +++ b/clang/test/3C/generated_tests/arrstructprotocaller.c @@ -148,7 +148,8 @@ int *bar() { } int *z = sus(x, y); //CHECK_NOALL: int *z = sus(x, y); - //CHECK_ALL: _Array_ptr z = sus(x, y); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = sus(x, y); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; z += 2; return z; } diff --git a/clang/test/3C/generated_tests/fptrarrboth.c b/clang/test/3C/generated_tests/fptrarrboth.c index 1ac9298c64f4..65a2bd69734c 100644 --- a/clang/test/3C/generated_tests/fptrarrboth.c +++ b/clang/test/3C/generated_tests/fptrarrboth.c @@ -108,7 +108,8 @@ int **sus(int *x, int *y) { //CHECK: x = (int *)5; int **z = calloc(5, sizeof(int *)); //CHECK_NOALL: int **z = calloc(5, sizeof(int *)); - //CHECK_ALL: _Array_ptr<_Ptr> z = calloc<_Ptr>(5, sizeof(int *)); + //CHECK_ALL: _Array_ptr<_Ptr> __3c_lower_bound_z : count(5) = calloc<_Ptr>(5, sizeof(int *)); + //CHECK_ALL: _Array_ptr<_Ptr> z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; int *(*mul2ptr)(int *) = mul2; //CHECK: _Ptr<_Ptr (_Ptr)> mul2ptr = mul2; int i; diff --git a/clang/test/3C/generated_tests/fptrarrbothmulti2.c b/clang/test/3C/generated_tests/fptrarrbothmulti2.c index b13a262ce83b..b25892a3a555 100644 --- a/clang/test/3C/generated_tests/fptrarrbothmulti2.c +++ b/clang/test/3C/generated_tests/fptrarrbothmulti2.c @@ -115,7 +115,8 @@ int **sus(int *x, int *y) { //CHECK: x = (int *)5; int **z = calloc(5, sizeof(int *)); //CHECK_NOALL: int **z = calloc(5, sizeof(int *)); - //CHECK_ALL: _Array_ptr<_Ptr> z = calloc<_Ptr>(5, sizeof(int *)); + //CHECK_ALL: _Array_ptr<_Ptr> __3c_lower_bound_z : count(5) = calloc<_Ptr>(5, sizeof(int *)); + //CHECK_ALL: _Array_ptr<_Ptr> z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; int *(*mul2ptr)(int *) = mul2; //CHECK: _Ptr<_Ptr (_Ptr)> mul2ptr = mul2; int i; diff --git a/clang/test/3C/generated_tests/fptrarrcallee.c b/clang/test/3C/generated_tests/fptrarrcallee.c index 5e5af7a46d3d..d32910c15ff4 100644 --- a/clang/test/3C/generated_tests/fptrarrcallee.c +++ b/clang/test/3C/generated_tests/fptrarrcallee.c @@ -108,7 +108,8 @@ int **sus(int *x, int *y) { //CHECK: x = (int *)5; int **z = calloc(5, sizeof(int *)); //CHECK_NOALL: int **z = calloc(5, sizeof(int *)); - //CHECK_ALL: _Array_ptr<_Ptr> z = calloc<_Ptr>(5, sizeof(int *)); + //CHECK_ALL: _Array_ptr<_Ptr> __3c_lower_bound_z : count(5) = calloc<_Ptr>(5, sizeof(int *)); + //CHECK_ALL: _Array_ptr<_Ptr> z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; int *(*mul2ptr)(int *) = mul2; //CHECK: _Ptr<_Ptr (_Ptr)> mul2ptr = mul2; int i; diff --git a/clang/test/3C/generated_tests/fptrarrcalleemulti2.c b/clang/test/3C/generated_tests/fptrarrcalleemulti2.c index f32d91f2c797..afd62e0dea9e 100644 --- a/clang/test/3C/generated_tests/fptrarrcalleemulti2.c +++ b/clang/test/3C/generated_tests/fptrarrcalleemulti2.c @@ -115,7 +115,8 @@ int **sus(int *x, int *y) { //CHECK: x = (int *)5; int **z = calloc(5, sizeof(int *)); //CHECK_NOALL: int **z = calloc(5, sizeof(int *)); - //CHECK_ALL: _Array_ptr<_Ptr> z = calloc<_Ptr>(5, sizeof(int *)); + //CHECK_ALL: _Array_ptr<_Ptr> __3c_lower_bound_z : count(5) = calloc<_Ptr>(5, sizeof(int *)); + //CHECK_ALL: _Array_ptr<_Ptr> z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; int *(*mul2ptr)(int *) = mul2; //CHECK: _Ptr<_Ptr (_Ptr)> mul2ptr = mul2; int i; diff --git a/clang/test/3C/generated_tests/fptrarrcaller.c b/clang/test/3C/generated_tests/fptrarrcaller.c index a37ff7c719f2..5a5048d11ac3 100644 --- a/clang/test/3C/generated_tests/fptrarrcaller.c +++ b/clang/test/3C/generated_tests/fptrarrcaller.c @@ -162,7 +162,8 @@ int **bar() { } int **z = sus(x, y); //CHECK_NOALL: int **z = sus(x, y); - //CHECK_ALL: _Array_ptr<_Ptr> z = sus(x, y); + //CHECK_ALL: _Array_ptr<_Ptr> __3c_lower_bound_z : count(5) = sus(x, y); + //CHECK_ALL: _Array_ptr<_Ptr> z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; z += 2; return z; diff --git a/clang/test/3C/generated_tests/fptrarrcallermulti1.c b/clang/test/3C/generated_tests/fptrarrcallermulti1.c index 9d76f7ba09bf..6223e3da0e65 100644 --- a/clang/test/3C/generated_tests/fptrarrcallermulti1.c +++ b/clang/test/3C/generated_tests/fptrarrcallermulti1.c @@ -150,7 +150,8 @@ int **bar() { } int **z = sus(x, y); //CHECK_NOALL: int **z = sus(x, y); - //CHECK_ALL: _Array_ptr<_Ptr> z = sus(x, y); + //CHECK_ALL: _Array_ptr<_Ptr> __3c_lower_bound_z : count(5) = sus(x, y); + //CHECK_ALL: _Array_ptr<_Ptr> z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; z += 2; return z; diff --git a/clang/test/3C/generated_tests/fptrarrprotoboth.c b/clang/test/3C/generated_tests/fptrarrprotoboth.c index bcf48a6a9e91..da3b11f84fb3 100644 --- a/clang/test/3C/generated_tests/fptrarrprotoboth.c +++ b/clang/test/3C/generated_tests/fptrarrprotoboth.c @@ -161,7 +161,8 @@ int **sus(int *x, int *y) { //CHECK: x = (int *)5; int **z = calloc(5, sizeof(int *)); //CHECK_NOALL: int **z = calloc(5, sizeof(int *)); - //CHECK_ALL: _Array_ptr<_Ptr> z = calloc<_Ptr>(5, sizeof(int *)); + //CHECK_ALL: _Array_ptr<_Ptr> __3c_lower_bound_z : count(5) = calloc<_Ptr>(5, sizeof(int *)); + //CHECK_ALL: _Array_ptr<_Ptr> z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; int *(*mul2ptr)(int *) = mul2; //CHECK: _Ptr<_Ptr (_Ptr)> mul2ptr = mul2; int i; diff --git a/clang/test/3C/generated_tests/fptrarrprotocallee.c b/clang/test/3C/generated_tests/fptrarrprotocallee.c index 4de828f3dc14..f5c25d2dc569 100644 --- a/clang/test/3C/generated_tests/fptrarrprotocallee.c +++ b/clang/test/3C/generated_tests/fptrarrprotocallee.c @@ -160,7 +160,8 @@ int **sus(int *x, int *y) { //CHECK: x = (int *)5; int **z = calloc(5, sizeof(int *)); //CHECK_NOALL: int **z = calloc(5, sizeof(int *)); - //CHECK_ALL: _Array_ptr<_Ptr> z = calloc<_Ptr>(5, sizeof(int *)); + //CHECK_ALL: _Array_ptr<_Ptr> __3c_lower_bound_z : count(5) = calloc<_Ptr>(5, sizeof(int *)); + //CHECK_ALL: _Array_ptr<_Ptr> z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; int *(*mul2ptr)(int *) = mul2; //CHECK: _Ptr<_Ptr (_Ptr)> mul2ptr = mul2; int i; diff --git a/clang/test/3C/generated_tests/fptrarrprotocaller.c b/clang/test/3C/generated_tests/fptrarrprotocaller.c index a6e7705c974d..b18faa7a468d 100644 --- a/clang/test/3C/generated_tests/fptrarrprotocaller.c +++ b/clang/test/3C/generated_tests/fptrarrprotocaller.c @@ -147,7 +147,8 @@ int **bar() { } int **z = sus(x, y); //CHECK_NOALL: int **z = sus(x, y); - //CHECK_ALL: _Array_ptr<_Ptr> z = sus(x, y); + //CHECK_ALL: _Array_ptr<_Ptr> __3c_lower_bound_z : count(5) = sus(x, y); + //CHECK_ALL: _Array_ptr<_Ptr> z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; z += 2; return z; diff --git a/clang/test/3C/generated_tests/fptrsafeboth.c b/clang/test/3C/generated_tests/fptrsafeboth.c index 596d093e3235..9d21567937ba 100644 --- a/clang/test/3C/generated_tests/fptrsafeboth.c +++ b/clang/test/3C/generated_tests/fptrsafeboth.c @@ -108,7 +108,8 @@ int *sus(struct general *x, struct general *y) { //CHECK: x = (struct general *)5; int *z = calloc(5, sizeof(int)); //CHECK_NOALL: int *z = calloc(5, sizeof(int)); - //CHECK_ALL: _Array_ptr z = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; struct general *p = y; //CHECK: _Ptr p = y; int i; diff --git a/clang/test/3C/generated_tests/fptrsafebothmulti2.c b/clang/test/3C/generated_tests/fptrsafebothmulti2.c index 7b975cee340a..25c3906959b1 100644 --- a/clang/test/3C/generated_tests/fptrsafebothmulti2.c +++ b/clang/test/3C/generated_tests/fptrsafebothmulti2.c @@ -115,7 +115,8 @@ int *sus(struct general *x, struct general *y) { //CHECK: x = (struct general *)5; int *z = calloc(5, sizeof(int)); //CHECK_NOALL: int *z = calloc(5, sizeof(int)); - //CHECK_ALL: _Array_ptr z = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; struct general *p = y; //CHECK: _Ptr p = y; int i; diff --git a/clang/test/3C/generated_tests/fptrsafecallee.c b/clang/test/3C/generated_tests/fptrsafecallee.c index fc21ef661933..e657e5f3620f 100644 --- a/clang/test/3C/generated_tests/fptrsafecallee.c +++ b/clang/test/3C/generated_tests/fptrsafecallee.c @@ -108,7 +108,8 @@ int *sus(struct general *x, struct general *y) { //CHECK: x = (struct general *)5; int *z = calloc(5, sizeof(int)); //CHECK_NOALL: int *z = calloc(5, sizeof(int)); - //CHECK_ALL: _Array_ptr z = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; struct general *p = y; //CHECK: _Ptr p = y; int i; diff --git a/clang/test/3C/generated_tests/fptrsafecalleemulti2.c b/clang/test/3C/generated_tests/fptrsafecalleemulti2.c index 92a0d3a82aa2..1bf10ac6934d 100644 --- a/clang/test/3C/generated_tests/fptrsafecalleemulti2.c +++ b/clang/test/3C/generated_tests/fptrsafecalleemulti2.c @@ -115,7 +115,8 @@ int *sus(struct general *x, struct general *y) { //CHECK: x = (struct general *)5; int *z = calloc(5, sizeof(int)); //CHECK_NOALL: int *z = calloc(5, sizeof(int)); - //CHECK_ALL: _Array_ptr z = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; struct general *p = y; //CHECK: _Ptr p = y; int i; diff --git a/clang/test/3C/generated_tests/fptrsafeprotoboth.c b/clang/test/3C/generated_tests/fptrsafeprotoboth.c index 9647410f6dcb..59769f9788a0 100644 --- a/clang/test/3C/generated_tests/fptrsafeprotoboth.c +++ b/clang/test/3C/generated_tests/fptrsafeprotoboth.c @@ -169,7 +169,8 @@ int *sus(struct general *x, struct general *y) { //CHECK: x = (struct general *)5; int *z = calloc(5, sizeof(int)); //CHECK_NOALL: int *z = calloc(5, sizeof(int)); - //CHECK_ALL: _Array_ptr z = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; struct general *p = y; //CHECK: _Ptr p = y; int i; diff --git a/clang/test/3C/generated_tests/fptrsafeprotocallee.c b/clang/test/3C/generated_tests/fptrsafeprotocallee.c index 80d1aa3d0ba2..771e222f6426 100644 --- a/clang/test/3C/generated_tests/fptrsafeprotocallee.c +++ b/clang/test/3C/generated_tests/fptrsafeprotocallee.c @@ -168,7 +168,8 @@ int *sus(struct general *x, struct general *y) { //CHECK: x = (struct general *)5; int *z = calloc(5, sizeof(int)); //CHECK_NOALL: int *z = calloc(5, sizeof(int)); - //CHECK_ALL: _Array_ptr z = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; struct general *p = y; //CHECK: _Ptr p = y; int i; diff --git a/clang/test/3C/generated_tests/ptrTOptrboth.c b/clang/test/3C/generated_tests/ptrTOptrboth.c index 07745dc4f7c5..31a5bb631916 100644 --- a/clang/test/3C/generated_tests/ptrTOptrboth.c +++ b/clang/test/3C/generated_tests/ptrTOptrboth.c @@ -109,7 +109,8 @@ char ***sus(char ***x, char ***y) { *ch = 'A'; /*Capital A*/ char ***z = malloc(5 * sizeof(char **)); //CHECK_NOALL: char ***z = malloc(5 * sizeof(char **)); - //CHECK_ALL: _Array_ptr<_Array_ptr> z = malloc<_Array_ptr>(5 * sizeof(char **)); + //CHECK_ALL: _Array_ptr<_Array_ptr> __3c_lower_bound_z : count(5) = malloc<_Array_ptr>(5 * sizeof(char **)); + //CHECK_ALL: _Array_ptr<_Array_ptr> z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; for (int i = 0; i < 5; i++) { z[i] = malloc(5 * sizeof(char *)); //CHECK: z[i] = malloc(5 * sizeof(char *)); diff --git a/clang/test/3C/generated_tests/ptrTOptrbothmulti2.c b/clang/test/3C/generated_tests/ptrTOptrbothmulti2.c index d80ec2650645..a4c3e474f57b 100644 --- a/clang/test/3C/generated_tests/ptrTOptrbothmulti2.c +++ b/clang/test/3C/generated_tests/ptrTOptrbothmulti2.c @@ -116,7 +116,8 @@ char ***sus(char ***x, char ***y) { *ch = 'A'; /*Capital A*/ char ***z = malloc(5 * sizeof(char **)); //CHECK_NOALL: char ***z = malloc(5 * sizeof(char **)); - //CHECK_ALL: _Array_ptr<_Array_ptr> z = malloc<_Array_ptr>(5 * sizeof(char **)); + //CHECK_ALL: _Array_ptr<_Array_ptr> __3c_lower_bound_z : count(5) = malloc<_Array_ptr>(5 * sizeof(char **)); + //CHECK_ALL: _Array_ptr<_Array_ptr> z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; for (int i = 0; i < 5; i++) { z[i] = malloc(5 * sizeof(char *)); //CHECK: z[i] = malloc(5 * sizeof(char *)); diff --git a/clang/test/3C/generated_tests/ptrTOptrcallee.c b/clang/test/3C/generated_tests/ptrTOptrcallee.c index ea8ca5db3ead..330227fd2fcc 100644 --- a/clang/test/3C/generated_tests/ptrTOptrcallee.c +++ b/clang/test/3C/generated_tests/ptrTOptrcallee.c @@ -109,7 +109,8 @@ char ***sus(char ***x, char ***y) { *ch = 'A'; /*Capital A*/ char ***z = malloc(5 * sizeof(char **)); //CHECK_NOALL: char ***z = malloc(5 * sizeof(char **)); - //CHECK_ALL: _Array_ptr<_Array_ptr> z = malloc<_Array_ptr>(5 * sizeof(char **)); + //CHECK_ALL: _Array_ptr<_Array_ptr> __3c_lower_bound_z : count(5) = malloc<_Array_ptr>(5 * sizeof(char **)); + //CHECK_ALL: _Array_ptr<_Array_ptr> z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; for (int i = 0; i < 5; i++) { z[i] = malloc(5 * sizeof(char *)); //CHECK: z[i] = malloc(5 * sizeof(char *)); diff --git a/clang/test/3C/generated_tests/ptrTOptrcalleemulti2.c b/clang/test/3C/generated_tests/ptrTOptrcalleemulti2.c index 28bf64c42e7c..74a90017c49e 100644 --- a/clang/test/3C/generated_tests/ptrTOptrcalleemulti2.c +++ b/clang/test/3C/generated_tests/ptrTOptrcalleemulti2.c @@ -116,7 +116,8 @@ char ***sus(char ***x, char ***y) { *ch = 'A'; /*Capital A*/ char ***z = malloc(5 * sizeof(char **)); //CHECK_NOALL: char ***z = malloc(5 * sizeof(char **)); - //CHECK_ALL: _Array_ptr<_Array_ptr> z = malloc<_Array_ptr>(5 * sizeof(char **)); + //CHECK_ALL: _Array_ptr<_Array_ptr> __3c_lower_bound_z : count(5) = malloc<_Array_ptr>(5 * sizeof(char **)); + //CHECK_ALL: _Array_ptr<_Array_ptr> z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; for (int i = 0; i < 5; i++) { z[i] = malloc(5 * sizeof(char *)); //CHECK: z[i] = malloc(5 * sizeof(char *)); diff --git a/clang/test/3C/generated_tests/ptrTOptrcaller.c b/clang/test/3C/generated_tests/ptrTOptrcaller.c index 79d5d8aff072..d9a1189e4a53 100644 --- a/clang/test/3C/generated_tests/ptrTOptrcaller.c +++ b/clang/test/3C/generated_tests/ptrTOptrcaller.c @@ -146,7 +146,8 @@ char ***bar() { //CHECK: _Ptr<_Ptr<_Ptr>> y = malloc<_Ptr<_Ptr>>(sizeof(char **)); char ***z = sus(x, y); //CHECK_NOALL: char ***z = sus(x, y); - //CHECK_ALL: _Array_ptr<_Array_ptr> z = sus(x, y); + //CHECK_ALL: _Array_ptr<_Array_ptr> __3c_lower_bound_z : count(5) = sus(x, y); + //CHECK_ALL: _Array_ptr<_Array_ptr> z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; z += 2; return z; } diff --git a/clang/test/3C/generated_tests/ptrTOptrcallermulti1.c b/clang/test/3C/generated_tests/ptrTOptrcallermulti1.c index af71ef6e10c3..854cc02951e6 100644 --- a/clang/test/3C/generated_tests/ptrTOptrcallermulti1.c +++ b/clang/test/3C/generated_tests/ptrTOptrcallermulti1.c @@ -132,7 +132,8 @@ char ***bar() { //CHECK: _Ptr<_Ptr<_Ptr>> y = malloc<_Ptr<_Ptr>>(sizeof(char **)); char ***z = sus(x, y); //CHECK_NOALL: char ***z = sus(x, y); - //CHECK_ALL: _Array_ptr<_Array_ptr> z = sus(x, y); + //CHECK_ALL: _Array_ptr<_Array_ptr> __3c_lower_bound_z : count(5) = sus(x, y); + //CHECK_ALL: _Array_ptr<_Array_ptr> z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; z += 2; return z; } diff --git a/clang/test/3C/generated_tests/ptrTOptrprotoboth.c b/clang/test/3C/generated_tests/ptrTOptrprotoboth.c index b4e8154d1a0e..914fcc99c89a 100644 --- a/clang/test/3C/generated_tests/ptrTOptrprotoboth.c +++ b/clang/test/3C/generated_tests/ptrTOptrprotoboth.c @@ -144,7 +144,8 @@ char ***sus(char ***x, char ***y) { *ch = 'A'; /*Capital A*/ char ***z = malloc(5 * sizeof(char **)); //CHECK_NOALL: char ***z = malloc(5 * sizeof(char **)); - //CHECK_ALL: _Array_ptr<_Array_ptr> z = malloc<_Array_ptr>(5 * sizeof(char **)); + //CHECK_ALL: _Array_ptr<_Array_ptr> __3c_lower_bound_z : count(5) = malloc<_Array_ptr>(5 * sizeof(char **)); + //CHECK_ALL: _Array_ptr<_Array_ptr> z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; for (int i = 0; i < 5; i++) { z[i] = malloc(5 * sizeof(char *)); //CHECK: z[i] = malloc(5 * sizeof(char *)); diff --git a/clang/test/3C/generated_tests/ptrTOptrprotocallee.c b/clang/test/3C/generated_tests/ptrTOptrprotocallee.c index df95dd98e94d..0c26c7b1ec46 100644 --- a/clang/test/3C/generated_tests/ptrTOptrprotocallee.c +++ b/clang/test/3C/generated_tests/ptrTOptrprotocallee.c @@ -143,7 +143,8 @@ char ***sus(char ***x, char ***y) { *ch = 'A'; /*Capital A*/ char ***z = malloc(5 * sizeof(char **)); //CHECK_NOALL: char ***z = malloc(5 * sizeof(char **)); - //CHECK_ALL: _Array_ptr<_Array_ptr> z = malloc<_Array_ptr>(5 * sizeof(char **)); + //CHECK_ALL: _Array_ptr<_Array_ptr> __3c_lower_bound_z : count(5) = malloc<_Array_ptr>(5 * sizeof(char **)); + //CHECK_ALL: _Array_ptr<_Array_ptr> z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; for (int i = 0; i < 5; i++) { z[i] = malloc(5 * sizeof(char *)); //CHECK: z[i] = malloc(5 * sizeof(char *)); diff --git a/clang/test/3C/generated_tests/ptrTOptrprotocaller.c b/clang/test/3C/generated_tests/ptrTOptrprotocaller.c index 50e87d22e809..4e871ee23223 100644 --- a/clang/test/3C/generated_tests/ptrTOptrprotocaller.c +++ b/clang/test/3C/generated_tests/ptrTOptrprotocaller.c @@ -129,7 +129,8 @@ char ***bar() { //CHECK: _Ptr<_Ptr<_Ptr>> y = malloc<_Ptr<_Ptr>>(sizeof(char **)); char ***z = sus(x, y); //CHECK_NOALL: char ***z = sus(x, y); - //CHECK_ALL: _Array_ptr<_Array_ptr> z = sus(x, y); + //CHECK_ALL: _Array_ptr<_Array_ptr> __3c_lower_bound_z : count(5) = sus(x, y); + //CHECK_ALL: _Array_ptr<_Array_ptr> z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; z += 2; return z; } diff --git a/clang/test/3C/generated_tests/safefptrargboth.c b/clang/test/3C/generated_tests/safefptrargboth.c index 84fd39cee838..5c84c793b721 100644 --- a/clang/test/3C/generated_tests/safefptrargboth.c +++ b/clang/test/3C/generated_tests/safefptrargboth.c @@ -108,7 +108,8 @@ int *sus(int (*x)(int), int (*y)(int)) { //CHECK: x = (int (*)(int))5; int *z = calloc(5, sizeof(int)); //CHECK_NOALL: int *z = calloc(5, sizeof(int)); - //CHECK_ALL: _Array_ptr z = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; int i; for (i = 0; i < 5; i++) { //CHECK_NOALL: for (i = 0; i < 5; i++) { diff --git a/clang/test/3C/generated_tests/safefptrargbothmulti2.c b/clang/test/3C/generated_tests/safefptrargbothmulti2.c index 2bec673a4895..7d07840119a3 100644 --- a/clang/test/3C/generated_tests/safefptrargbothmulti2.c +++ b/clang/test/3C/generated_tests/safefptrargbothmulti2.c @@ -115,7 +115,8 @@ int *sus(int (*x)(int), int (*y)(int)) { //CHECK: x = (int (*)(int))5; int *z = calloc(5, sizeof(int)); //CHECK_NOALL: int *z = calloc(5, sizeof(int)); - //CHECK_ALL: _Array_ptr z = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; int i; for (i = 0; i < 5; i++) { //CHECK_NOALL: for (i = 0; i < 5; i++) { diff --git a/clang/test/3C/generated_tests/safefptrargcallee.c b/clang/test/3C/generated_tests/safefptrargcallee.c index 08daf9fd5089..b701fc4ff960 100644 --- a/clang/test/3C/generated_tests/safefptrargcallee.c +++ b/clang/test/3C/generated_tests/safefptrargcallee.c @@ -108,7 +108,8 @@ int *sus(int (*x)(int), int (*y)(int)) { //CHECK: x = (int (*)(int))5; int *z = calloc(5, sizeof(int)); //CHECK_NOALL: int *z = calloc(5, sizeof(int)); - //CHECK_ALL: _Array_ptr z = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; int i; for (i = 0; i < 5; i++) { //CHECK_NOALL: for (i = 0; i < 5; i++) { diff --git a/clang/test/3C/generated_tests/safefptrargcalleemulti2.c b/clang/test/3C/generated_tests/safefptrargcalleemulti2.c index 9ec798a8656c..4611401e5002 100644 --- a/clang/test/3C/generated_tests/safefptrargcalleemulti2.c +++ b/clang/test/3C/generated_tests/safefptrargcalleemulti2.c @@ -115,7 +115,8 @@ int *sus(int (*x)(int), int (*y)(int)) { //CHECK: x = (int (*)(int))5; int *z = calloc(5, sizeof(int)); //CHECK_NOALL: int *z = calloc(5, sizeof(int)); - //CHECK_ALL: _Array_ptr z = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; int i; for (i = 0; i < 5; i++) { //CHECK_NOALL: for (i = 0; i < 5; i++) { diff --git a/clang/test/3C/generated_tests/safefptrargcaller.c b/clang/test/3C/generated_tests/safefptrargcaller.c index 260fd8796c87..071e985f6ea8 100644 --- a/clang/test/3C/generated_tests/safefptrargcaller.c +++ b/clang/test/3C/generated_tests/safefptrargcaller.c @@ -144,7 +144,8 @@ int *bar() { //CHECK: _Ptr y = sub1; int *z = sus(x, y); //CHECK_NOALL: int *z = sus(x, y); - //CHECK_ALL: _Array_ptr z = sus(x, y); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = sus(x, y); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; z += 2; return z; diff --git a/clang/test/3C/generated_tests/safefptrargcallermulti1.c b/clang/test/3C/generated_tests/safefptrargcallermulti1.c index 67d0cf8e4d76..f4a3109837e9 100644 --- a/clang/test/3C/generated_tests/safefptrargcallermulti1.c +++ b/clang/test/3C/generated_tests/safefptrargcallermulti1.c @@ -136,7 +136,8 @@ int *bar() { //CHECK: _Ptr y = sub1; int *z = sus(x, y); //CHECK_NOALL: int *z = sus(x, y); - //CHECK_ALL: _Array_ptr z = sus(x, y); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = sus(x, y); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; z += 2; return z; diff --git a/clang/test/3C/generated_tests/safefptrargprotoboth.c b/clang/test/3C/generated_tests/safefptrargprotoboth.c index 3e0dcb08f226..a8f3d6fd10ff 100644 --- a/clang/test/3C/generated_tests/safefptrargprotoboth.c +++ b/clang/test/3C/generated_tests/safefptrargprotoboth.c @@ -147,7 +147,8 @@ int *sus(int (*x)(int), int (*y)(int)) { //CHECK: x = (int (*)(int))5; int *z = calloc(5, sizeof(int)); //CHECK_NOALL: int *z = calloc(5, sizeof(int)); - //CHECK_ALL: _Array_ptr z = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; int i; for (i = 0; i < 5; i++) { //CHECK_NOALL: for (i = 0; i < 5; i++) { diff --git a/clang/test/3C/generated_tests/safefptrargprotocallee.c b/clang/test/3C/generated_tests/safefptrargprotocallee.c index ef8463f894f0..e6a1784435fa 100644 --- a/clang/test/3C/generated_tests/safefptrargprotocallee.c +++ b/clang/test/3C/generated_tests/safefptrargprotocallee.c @@ -146,7 +146,8 @@ int *sus(int (*x)(int), int (*y)(int)) { //CHECK: x = (int (*)(int))5; int *z = calloc(5, sizeof(int)); //CHECK_NOALL: int *z = calloc(5, sizeof(int)); - //CHECK_ALL: _Array_ptr z = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; int i; for (i = 0; i < 5; i++) { //CHECK_NOALL: for (i = 0; i < 5; i++) { diff --git a/clang/test/3C/generated_tests/safefptrargprotocaller.c b/clang/test/3C/generated_tests/safefptrargprotocaller.c index dc53491ef36f..2068133f4836 100644 --- a/clang/test/3C/generated_tests/safefptrargprotocaller.c +++ b/clang/test/3C/generated_tests/safefptrargprotocaller.c @@ -133,7 +133,8 @@ int *bar() { //CHECK: _Ptr y = sub1; int *z = sus(x, y); //CHECK_NOALL: int *z = sus(x, y); - //CHECK_ALL: _Array_ptr z = sus(x, y); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = sus(x, y); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; z += 2; return z; diff --git a/clang/test/3C/generated_tests/unsafefptrargboth.c b/clang/test/3C/generated_tests/unsafefptrargboth.c index 7ab2509a1a74..138a9228fa26 100644 --- a/clang/test/3C/generated_tests/unsafefptrargboth.c +++ b/clang/test/3C/generated_tests/unsafefptrargboth.c @@ -108,7 +108,8 @@ int *sus(int (*x)(int), int (*y)(int)) { //CHECK: x = (int (*)(int))5; int *z = calloc(5, sizeof(int)); //CHECK_NOALL: int *z = calloc(5, sizeof(int)); - //CHECK_ALL: _Array_ptr z = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; int i; for (i = 0; i < 5; i++) { //CHECK_NOALL: for (i = 0; i < 5; i++) { diff --git a/clang/test/3C/generated_tests/unsafefptrargbothmulti2.c b/clang/test/3C/generated_tests/unsafefptrargbothmulti2.c index bb17da0d58e6..ae06fad85937 100644 --- a/clang/test/3C/generated_tests/unsafefptrargbothmulti2.c +++ b/clang/test/3C/generated_tests/unsafefptrargbothmulti2.c @@ -115,7 +115,8 @@ int *sus(int (*x)(int), int (*y)(int)) { //CHECK: x = (int (*)(int))5; int *z = calloc(5, sizeof(int)); //CHECK_NOALL: int *z = calloc(5, sizeof(int)); - //CHECK_ALL: _Array_ptr z = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; int i; for (i = 0; i < 5; i++) { //CHECK_NOALL: for (i = 0; i < 5; i++) { diff --git a/clang/test/3C/generated_tests/unsafefptrargcallee.c b/clang/test/3C/generated_tests/unsafefptrargcallee.c index 6db0149437a2..b35210963eaa 100644 --- a/clang/test/3C/generated_tests/unsafefptrargcallee.c +++ b/clang/test/3C/generated_tests/unsafefptrargcallee.c @@ -108,7 +108,8 @@ int *sus(int (*x)(int), int (*y)(int)) { //CHECK: x = (int (*)(int))5; int *z = calloc(5, sizeof(int)); //CHECK_NOALL: int *z = calloc(5, sizeof(int)); - //CHECK_ALL: _Array_ptr z = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; int i; for (i = 0; i < 5; i++) { //CHECK_NOALL: for (i = 0; i < 5; i++) { diff --git a/clang/test/3C/generated_tests/unsafefptrargcalleemulti2.c b/clang/test/3C/generated_tests/unsafefptrargcalleemulti2.c index c9192718b711..a3a5813f0401 100644 --- a/clang/test/3C/generated_tests/unsafefptrargcalleemulti2.c +++ b/clang/test/3C/generated_tests/unsafefptrargcalleemulti2.c @@ -115,7 +115,8 @@ int *sus(int (*x)(int), int (*y)(int)) { //CHECK: x = (int (*)(int))5; int *z = calloc(5, sizeof(int)); //CHECK_NOALL: int *z = calloc(5, sizeof(int)); - //CHECK_ALL: _Array_ptr z = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; int i; for (i = 0; i < 5; i++) { //CHECK_NOALL: for (i = 0; i < 5; i++) { diff --git a/clang/test/3C/generated_tests/unsafefptrargcaller.c b/clang/test/3C/generated_tests/unsafefptrargcaller.c index cccd1213b4e7..2f3b68362b0f 100644 --- a/clang/test/3C/generated_tests/unsafefptrargcaller.c +++ b/clang/test/3C/generated_tests/unsafefptrargcaller.c @@ -144,7 +144,8 @@ int *bar() { //CHECK: int (*y)(int) = mul2; int *z = sus(x, y); //CHECK_NOALL: int *z = sus(x, _Assume_bounds_cast<_Ptr>(y)); - //CHECK_ALL: _Array_ptr z = sus(x, _Assume_bounds_cast<_Ptr>(y)); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = sus(x, _Assume_bounds_cast<_Ptr>(y)); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; z += 2; return z; diff --git a/clang/test/3C/generated_tests/unsafefptrargcallermulti1.c b/clang/test/3C/generated_tests/unsafefptrargcallermulti1.c index 9b591e1f388f..d266f90892fd 100644 --- a/clang/test/3C/generated_tests/unsafefptrargcallermulti1.c +++ b/clang/test/3C/generated_tests/unsafefptrargcallermulti1.c @@ -136,7 +136,8 @@ int *bar() { //CHECK: int (*y)(int) = mul2; int *z = sus(x, y); //CHECK_NOALL: int *z = sus(x, _Assume_bounds_cast<_Ptr>(y)); - //CHECK_ALL: _Array_ptr z = sus(x, _Assume_bounds_cast<_Ptr>(y)); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = sus(x, _Assume_bounds_cast<_Ptr>(y)); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; z += 2; return z; diff --git a/clang/test/3C/generated_tests/unsafefptrargprotoboth.c b/clang/test/3C/generated_tests/unsafefptrargprotoboth.c index 920e11b810a6..4a3c60f6344d 100644 --- a/clang/test/3C/generated_tests/unsafefptrargprotoboth.c +++ b/clang/test/3C/generated_tests/unsafefptrargprotoboth.c @@ -147,7 +147,8 @@ int *sus(int (*x)(int), int (*y)(int)) { //CHECK: x = (int (*)(int))5; int *z = calloc(5, sizeof(int)); //CHECK_NOALL: int *z = calloc(5, sizeof(int)); - //CHECK_ALL: _Array_ptr z = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; int i; for (i = 0; i < 5; i++) { //CHECK_NOALL: for (i = 0; i < 5; i++) { diff --git a/clang/test/3C/generated_tests/unsafefptrargprotocallee.c b/clang/test/3C/generated_tests/unsafefptrargprotocallee.c index 725c43a19a53..7955c54100a9 100644 --- a/clang/test/3C/generated_tests/unsafefptrargprotocallee.c +++ b/clang/test/3C/generated_tests/unsafefptrargprotocallee.c @@ -146,7 +146,8 @@ int *sus(int (*x)(int), int (*y)(int)) { //CHECK: x = (int (*)(int))5; int *z = calloc(5, sizeof(int)); //CHECK_NOALL: int *z = calloc(5, sizeof(int)); - //CHECK_ALL: _Array_ptr z = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = calloc(5, sizeof(int)); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; int i; for (i = 0; i < 5; i++) { //CHECK_NOALL: for (i = 0; i < 5; i++) { diff --git a/clang/test/3C/generated_tests/unsafefptrargprotocaller.c b/clang/test/3C/generated_tests/unsafefptrargprotocaller.c index cb305f2898e6..a4a8620c656f 100644 --- a/clang/test/3C/generated_tests/unsafefptrargprotocaller.c +++ b/clang/test/3C/generated_tests/unsafefptrargprotocaller.c @@ -133,7 +133,8 @@ int *bar() { //CHECK: int (*y)(int) = mul2; int *z = sus(x, y); //CHECK_NOALL: int *z = sus(x, _Assume_bounds_cast<_Ptr>(y)); - //CHECK_ALL: _Array_ptr z = sus(x, _Assume_bounds_cast<_Ptr>(y)); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(5) = sus(x, _Assume_bounds_cast<_Ptr>(y)); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 5) = __3c_lower_bound_z; z += 2; return z; diff --git a/clang/test/3C/itypes_for_extern.c b/clang/test/3C/itypes_for_extern.c index 650b926b8c31..14e189eb95af 100644 --- a/clang/test/3C/itypes_for_extern.c +++ b/clang/test/3C/itypes_for_extern.c @@ -139,3 +139,28 @@ int **g : count(2) itype(_Array_ptr) = 0; //CHECK: int **e : itype(_Array_ptr<_Ptr>) count(2) = ((void *)0); //CHECK: int **f : itype(_Array_ptr<_Ptr>) count(2) = ((void *)0); //CHECK: int **g : itype(_Array_ptr<_Ptr>) count(2) = 0; + +// `a` gets a fresh lower bound because of the update `a = a + 2`. The fresh +// bound for itype parameters uses an unchecked type outside of +// -itypes-for-extern, but needs to still use a checked type with +// -itypes-for-extern to avoid type errors on assignment to checked pointers +// inside the function. +void test_fresh_lower_bound(int *a, int l) { +// CHECK_ALL: void test_fresh_lower_bound(int *__3c_lower_bound_a : itype(_Array_ptr) count(l), int l) _Checked { +// CHECK_ALL: _Array_ptr a : bounds(__3c_lower_bound_a, __3c_lower_bound_a + l) = __3c_lower_bound_a; + for(int i = 0; i < l; i++) + a[i]; + a = a + 2; + int *b = a; + // CHECK_ALL: _Ptr b = a; +} + +void test_fresh_lower_bound_itype(int *a, int l) { +// CHECK_ALL: void test_fresh_lower_bound_itype(int *__3c_lower_bound_a : itype(_Array_ptr) count(l), int l) { +// CHECK_ALL: int *a = __3c_lower_bound_a; + for(int i = 0; i < l; i++) + a[i]; + a = a + 2; + a = 1; + int *b = a; +} diff --git a/clang/test/3C/liberal_itypes_ptr.c b/clang/test/3C/liberal_itypes_ptr.c index 74eeabfcaad9..10aab8ebb7c0 100644 --- a/clang/test/3C/liberal_itypes_ptr.c +++ b/clang/test/3C/liberal_itypes_ptr.c @@ -117,7 +117,7 @@ void bounds_fn(void *b : byte_count(1)); void bounds_call(void *p) { // CHECK_NOALL: void bounds_call(void *p) { - // CHECK_ALL: _For_any(T) void bounds_call(_Array_ptr p) { + // CHECK_ALL: _Itype_for_any(T) void bounds_call(_Array_ptr p : byte_count(1)) { bounds_fn(p); // CHECK: bounds_fn(p); } diff --git a/clang/test/3C/range_bounds.c b/clang/test/3C/range_bounds.c new file mode 100644 index 000000000000..8c106cfd836a --- /dev/null +++ b/clang/test/3C/range_bounds.c @@ -0,0 +1,257 @@ +// RUN: rm -rf %t* +// RUN: 3c -base-dir=%S -addcr -alltypes %s -- | FileCheck -match-full-lines -check-prefixes="CHECK_ALL","CHECK" %s +// RUN: 3c -base-dir=%S -addcr -alltypes %s -- | %clang -c -Xclang -verify -Wno-unused-value -fcheckedc-extension -x c -o /dev/null - +// RUN: 3c -base-dir=%S -alltypes -output-dir=%t.checked %s -- +// RUN: 3c -base-dir=%t.checked -alltypes %t.checked/range_bounds.c -- | diff %t.checked/range_bounds.c - + +#include + +void test0(size_t l) { + // Would get bounds, but there's pointer arithmetic. Now we generate a fresh + // lower bound and use it in range bounds. + int *p = malloc(l * sizeof(int)); + // CHECK_ALL: _Array_ptr __3c_lower_bound_p : count(l) = malloc(l * sizeof(int)); + // CHECK_ALL: _Array_ptr p : bounds(__3c_lower_bound_p, __3c_lower_bound_p + l) = __3c_lower_bound_p; + p++; + + // No bounds are inferred, but pointer arithemtic is used; don't split + int *q = 0; + // CHECK_ALL: _Array_ptr q = 0; + q++; +} + +// Parameters must be inserted inside function body. This also also checks +// that a pre-declaration gets the correct bounds and does not generate a +// second alias. In this case, the predeclaration doesn't need to use the new +// variable name, but it doesn't hurt and is required in some more complex +// cases. +void test1(int *a, int l); +// CHECK_ALL: void test1(_Array_ptr __3c_lower_bound_a : count(l), int l); +void test1(int *a, int l) { + // CHECK_ALL: void test1(_Array_ptr __3c_lower_bound_a : count(l), int l) _Checked { + // CHECK_ALL: _Array_ptr a : bounds(__3c_lower_bound_a, __3c_lower_bound_a + l) = __3c_lower_bound_a; + + // Also check that other types of assignment are recognized. + a = a + 1; + + // The increment above means this loop reads out of bounds if `l` is the + // length of `a`. 3c won't consider this, but, now that we give `a` a bound, + // the access `a[l-1]` can be caught by Checked C, and the programmer can + // correct the loop limit or the declared bound as appropriate. + for(int i = 0; i < l; i++) + a[i]; +} + +// Also check for itypes. They're interesting because the alias isn't checked. +void test2(int *a, int l); +// CHECK_ALL: void test2(int *__3c_lower_bound_a : itype(_Array_ptr) count(l), int l); +void test2(int *a, int l) { + // CHECK_ALL: void test2(int *__3c_lower_bound_a : itype(_Array_ptr) count(l), int l) { + // CHECK_ALL: int *a = __3c_lower_bound_a; + for(int i = 0; i < l; i++) + a[i]; + + a = a + 2; + a = (int*) 1; +} + +// Something more complex with multiple parameters. +void test3(int *a, int *b, int *c, int *d) { + // CHECK_ALL: void test3(_Array_ptr __3c_lower_bound_a : count(10), int *b : itype(_Array_ptr) bounds(__3c_lower_bound_d, __3c_lower_bound_d + 10), _Array_ptr __3c_lower_bound_c : count(10), int *__3c_lower_bound_d : itype(_Array_ptr) count(10)) { + // CHECK_ALL: _Array_ptr a : bounds(__3c_lower_bound_a, __3c_lower_bound_a + 10) = __3c_lower_bound_a; + // CHECK_ALL: _Array_ptr c : bounds(__3c_lower_bound_c, __3c_lower_bound_c + 10) = __3c_lower_bound_c; + // CHECK_ALL: int *d = __3c_lower_bound_d; + a += 1, b += 2, c--, d -= 1; + b = d = (int*) 1; + + for (int i = 0; i < 10; i++) + a[i], b[i], c[i], d[i]; +} + +// Multi-declarations might need to add new declarations. The order of the new +// declarations is important because a later declaration in the same multi-decl +// might reference the variable being emitted. The new declaration of `c` must +// come before `d`. +void test4() { + int *a = malloc(10*sizeof(int)), b, *c = malloc(10*sizeof(int)), *d = c; + // CHECK_ALL: _Array_ptr __3c_lower_bound_a : count(10) = malloc(10*sizeof(int)); + // CHECK_ALL: _Array_ptr a : bounds(__3c_lower_bound_a, __3c_lower_bound_a + 10) = __3c_lower_bound_a; + // CHECK_ALL: int b; + // CHECK_ALL: _Array_ptr __3c_lower_bound_c : count(10) = malloc(10*sizeof(int)); + // CHECK_ALL: _Array_ptr c : bounds(__3c_lower_bound_c, __3c_lower_bound_c + 10) = __3c_lower_bound_c; + // CHECK_ALL: _Ptr d = c; + + b; + a++, c++; + + // This is another bit of tricky multi-decl rewriting. There are be spaces or + // comments between the end of one declaration and the beginning of the next. + // The fresh lower bound needs to be inserted after the comma delimiting the + // declarations. + int *x = malloc(5 * sizeof(int)) , *y = malloc(2 * sizeof(int)) /*foo*/, z; + // CHECK_ALL: _Array_ptr __3c_lower_bound_x : count(5) = malloc(5 * sizeof(int)) ; + // CHECK_ALL: _Array_ptr x : bounds(__3c_lower_bound_x, __3c_lower_bound_x + 5) = __3c_lower_bound_x; + // CHECK_ALL: _Array_ptr __3c_lower_bound_y : count(2) = malloc(2 * sizeof(int)) /*foo*/; + // CHECK_ALL: _Array_ptr y : bounds(__3c_lower_bound_y, __3c_lower_bound_y + 2) = __3c_lower_bound_y; + // CHECK_ALL: int z; + + x++; + y++; +} + +// Test that bounds don't propagate through pointers that are updated with +// pointer arithmetic. In this example, `b` can *not* have bounds `count(2)`, +// but it can get `bounds(__3c_lower_bound_a, __3c_lower_bound_a + 2)`. The same restriction +// also applies to bounds on the return, but, for the return, `a` can't be used +// as a lower bound, so no bound is given. +int *test5() { + // CHECK_ALL: _Array_ptr test5(void) { + int *a = malloc(2 * sizeof(int)); + // CHECK_ALL: _Array_ptr __3c_lower_bound_a : count(2) = malloc(2 * sizeof(int)); + // CHECK_ALL: _Array_ptr a : bounds(__3c_lower_bound_a, __3c_lower_bound_a + 2) = __3c_lower_bound_a; + a++; + int *b = a; + // CHECK_ALL: _Array_ptr b : bounds(__3c_lower_bound_a, __3c_lower_bound_a + 2) = a; + b[0]; + + return a; +} + +// Assignments to the variable should update the original and the copy, as long +// as the value being assigned doesn't depend on the pointer. +void test6() { + int *p = malloc(10 * sizeof(int)); + // CHECK_ALL: _Array_ptr __3c_lower_bound_p : count(10) = malloc(10 * sizeof(int)); + // CHECK_ALL: _Array_ptr p : bounds(__3c_lower_bound_p, __3c_lower_bound_p + 10) = __3c_lower_bound_p; + p++; + + // This assignment isn't touched because `p` is on the RHS. + p = p + 1; + // CHECK_ALL: p = p + 1; + + // Null out `p`, so we need to null the original and the duplicate. + p = 0; + // CHECK_ALL: __3c_lower_bound_p = 0, p = __3c_lower_bound_p; + + // A slightly more complex update to a different pointer value. + int *q = malloc(10 * sizeof(int)); + p = q; + //CHECK_ALL: _Array_ptr q : count(10) = malloc(10 * sizeof(int)); + //CHECK_ALL: __3c_lower_bound_p = q, p = __3c_lower_bound_p; + + // Don't treat a call to realloc as pointer arithmetic. Freeing `p` after + // `p++` is highly questionable, but that's not the point here. + p = realloc(p, 10 * sizeof(int)); + // CHECK_ALL: __3c_lower_bound_p = realloc(p, 10 * sizeof(int)), p = __3c_lower_bound_p; + + // Assignment rewriting should work in more complex expression and around + // other 3C rewriting without breaking anything. + int *v = 1 + (p = (int*) 0, p = p + 1) + 1; + // CHECK_ALL: _Ptr v = 1 + (__3c_lower_bound_p = (_Array_ptr) 0, p = __3c_lower_bound_p, p = p + 1) + 1; +} + + +// Check interaction with declaration merging. Identifiers are added on the +// first two declarations even though it's not required. +void test7(int *); +void test7(); +void test7(int *a); +// CHECK_ALL: void test7(_Array_ptr __3c_lower_bound_s : count(5)); +// CHECK_ALL: void test7(_Array_ptr __3c_lower_bound_s : count(5)); +// CHECK_ALL: void test7(_Array_ptr __3c_lower_bound_s : count(5)); +void test7(int *s) { +// CHECK_ALL: void test7(_Array_ptr __3c_lower_bound_s : count(5)) _Checked { +// CHECK_ALL: _Array_ptr s : bounds(__3c_lower_bound_s, __3c_lower_bound_s + 5) = __3c_lower_bound_s; + s++; + for (int i = 0; i < 5; i++) + s[i]; +} + +// A structure field is handled as it was before implementing lower bound +// inference. Future work could insert a new field, and update all struct +// initializer to include it. +struct s { + int *a; + // CHECK_ALL: _Array_ptr a; +}; +void test8() { + struct s t; + t.a++; + t.a[0]; + // expected-error@-1 {{expression has unknown bounds}} +} + +// Same as above. Future work might figure out how to generate fresh lower +// bounds for global variables. +int *glob; +// CHECK_ALL: _Array_ptr glob = ((void *)0); +void test9() { + glob++; + glob[0]; + // expected-error@-1 {{expression has unknown bounds}} +} + +// Creating a temporary local `int a _Checked[10]` would be incorrect here +// because the local variable array type does not decay to a pointer type. +// Future work can instead use checked array pointer type for the local. +void test10(int a[10]) { +// CHECK_ALL: void test10(int a _Checked[10]) _Checked { +// expected-note@-2 {{}} + + // A warning is expected because we keep the bound for the checked array. It + // could instead be rewritten to `_Array_ptr a` without a bound. + a++; + // expected-warning@-1 {{cannot prove declared bounds for 'a' are valid after increment}} + // expected-note@-2 {{}} +} + +// Another case where fresh lower bounds can't be generated: if we would have +// to update an assignment expression in a macro (which would be a rewriting +// error), then the pointer cannot get fresh lower bound. +#define set_a_to_null a = 0 +#define null_with_semi 0; +#define another_macro d = +void test11(size_t n){ + int *a = malloc(sizeof(int) * n); + //CHECK: _Array_ptr a = malloc(sizeof(int) * n); + a++; + set_a_to_null; + + // The RHS is a macro, but we should still be able to rewrite. + int *b = malloc(sizeof(int) * n); + // CHECK: _Array_ptr __3c_lower_bound_b : count(n) = malloc(sizeof(int) * n); + // CHECK: _Array_ptr b : bounds(__3c_lower_bound_b, __3c_lower_bound_b + n) = __3c_lower_bound_b; + b++; + b = NULL; + // CHECK: __3c_lower_bound_b = NULL, b = __3c_lower_bound_b; + + // Like the above case, but the macro includes the semicolon, so we can't + // rewrite. + int *c = malloc(sizeof(int) * n); + // CHECK: _Array_ptr c = malloc(sizeof(int) * n); + c++; + c = null_with_semi + + int *d = malloc(sizeof(int) * n); + // CHECK: _Array_ptr d = malloc(sizeof(int) * n); + d++; + another_macro 0; +} + +// Check byte_count rewriting. Interesting because range bound needs a cast to +// `_Array_ptr` for pointer arithmetic with offset to be correct. +void byte_count_fn(int *a : byte_count(n), unsigned int n); +void test12(int *b, unsigned int n) { +// CHECK: void test12(_Array_ptr __3c_lower_bound_b : byte_count(n), unsigned int n) _Checked { +// CHECK: _Array_ptr b : bounds(((_Array_ptr)__3c_lower_bound_b), ((_Array_ptr)__3c_lower_bound_b) + n) = __3c_lower_bound_b; + + byte_count_fn(b, n); + b++; + + // And also check count-plus-ones bounds. + int *c; + // _Array_ptr __3c_lower_bound_c : count(0 + 1) = ((void *)0); + // _Array_ptr c : bounds(__3c_lower_bound_c, __3c_lower_bound_c + 0 + 1) = __3c_lower_bound_c; + c[0]; + c++; +} diff --git a/clang/test/3C/range_bounds_flow.c b/clang/test/3C/range_bounds_flow.c new file mode 100644 index 000000000000..e21f5299da1e --- /dev/null +++ b/clang/test/3C/range_bounds_flow.c @@ -0,0 +1,203 @@ +// RUN: rm -rf %t* +// RUN: 3c -base-dir=%S -addcr -alltypes %s -- | FileCheck -match-full-lines -check-prefixes="CHECK_ALL","CHECK" %s +// RUN: 3c -base-dir=%S -addcr -alltypes %s -- | %clang -c -Xclang -verify -Wno-unused-value -fcheckedc-extension -x c -o /dev/null - +// RUN: 3c -base-dir=%S -alltypes -output-dir=%t.checked %s -- +// RUN: 3c -base-dir=%t.checked -alltypes %t.checked/range_bounds_flow.c -- | diff %t.checked/range_bounds_flow.c - + +// `a` is inferred as the lower bound for `b` and `c`. +void test1() { + int *a; + //CHECK_ALL: _Array_ptr a : count(0 + 1) = ((void *)0); + a[0]; + + int *b = a; + b++; + //CHECK_ALL: _Array_ptr b : bounds(a, a + 0 + 1) = a; + + int *c = b; + //CHECK_ALL: _Array_ptr c : bounds(a, a + 0 + 1) = b; + c[0]; +} + + +// Here we need to add a temporary lower bound instead. +void test2() { + int *a; + //CHECK_ALL: _Array_ptr __3c_lower_bound_a : count(0 + 1) = ((void *)0); + //CHECK_ALL: _Array_ptr a : bounds(__3c_lower_bound_a, __3c_lower_bound_a + 0 + 1) = __3c_lower_bound_a; + a[0]; + a++; + + int *b = a; + //CHECK_ALL: _Array_ptr b : bounds(__3c_lower_bound_a, __3c_lower_bound_a + 0 + 1) = a; + + int *c = b; + //CHECK_ALL: _Array_ptr c : bounds(__3c_lower_bound_a, __3c_lower_bound_a + 0 + 1) = b; + c[0]; +} + +int *test3(int *a, int l) { + int *b = a; + // CHECK_ALL: _Array_ptr test3(_Array_ptr a : count(l), int l) : bounds(a, a + l) _Checked { + // CHECK_ALL: _Array_ptr b : bounds(a, a + l) = a; + b++; + return b; +} + +int *test4(int *, int); +int *test4(int *x, int l); +int *test4(); +// CHECK_ALL: _Array_ptr test4(_Array_ptr __3c_lower_bound_a : count(l), int l) : bounds(__3c_lower_bound_a, __3c_lower_bound_a + l); +// CHECK_ALL: _Array_ptr test4(_Array_ptr __3c_lower_bound_a : count(l), int l) : bounds(__3c_lower_bound_a, __3c_lower_bound_a + l); +// CHECK_ALL: _Array_ptr test4(_Array_ptr __3c_lower_bound_a : count(l), int l) : bounds(__3c_lower_bound_a, __3c_lower_bound_a + l); + +int *test4(int *a, int l) { + // CHECK_ALL: _Array_ptr test4(_Array_ptr __3c_lower_bound_a : count(l), int l) : bounds(__3c_lower_bound_a, __3c_lower_bound_a + l) _Checked { + // CHECK_ALL: _Array_ptr a : bounds(__3c_lower_bound_a, __3c_lower_bound_a + l) = __3c_lower_bound_a; + a++; + return a; +} + +// There are multiple possible lower bounds for `c`, but they are consistent +// with each other. +void test5(int *a, int l) { + int *b = a; + int *c = b; + // CHECK_ALL: void test5(_Array_ptr a : count(l), int l) _Checked { + // CHECK_ALL: _Array_ptr b : count(l) = a; + // CHECK_ALL: _Array_ptr c : bounds(b, b + l) = b; + c++; +} + +// Lower bounds aren't consistent. We can't use `a` or `b`, so a fresh lower +// bound is created g. +void test6() { + int *a; + int *b; + // CHECK_ALL: _Array_ptr a : count(0 + 1) = ((void *)0); + // CHECK_ALL: _Array_ptr b : count(0 + 1) = ((void *)0); + + int *c; + c = a; + c = b; + // CHECK_ALL: _Array_ptr __3c_lower_bound_c : count(0 + 1) = ((void *)0); + // CHECK_ALL: _Array_ptr c : bounds(__3c_lower_bound_c, __3c_lower_bound_c + 0 + 1) = __3c_lower_bound_c; + // CHECK_ALL: __3c_lower_bound_c = a, c = __3c_lower_bound_c; + // CHECK_ALL: __3c_lower_bound_c = b, c = __3c_lower_bound_c; + + c++; + c[0]; +} + +// Lower bound is inferred from pointer with an declared count bound. +void test7(int *a : count(l), int dummy, int l) { + int *b = a; + // CHECK_ALL: _Array_ptr b : bounds(a, a + l) = a; + b++; +} + +// There is no valid lower bound available, but the lower bound for `a` can +// be the same as the lower bound for `b`. A fresh lower bound is created for +// `b`, and then used for `a` as well. +void test8(int *a, int *b) { +// CHECK_ALL: void test8(_Array_ptr a : bounds(__3c_lower_bound_b, __3c_lower_bound_b + 0 + 1), _Array_ptr __3c_lower_bound_b : count(0 + 1)) _Checked { +// CHECK_ALL: _Array_ptr b : bounds(__3c_lower_bound_b, __3c_lower_bound_b + 0 + 1) = __3c_lower_bound_b; + + a++; + b++; + a = b; + a[0]; +} + +// A cycle is formed by `a`,`b` and `c`. The lower bound `x` starts at `a`, +// propagates through `b` and `c`, and then flows into `a` again. This is +// consistent, so `x` is used as the lower bound. +void test9(int *x, int l) { +// CHECK_ALL: void test9(_Array_ptr x : count(l), int l) _Checked { + int *a = x, *b, *c; +// CHECK_ALL: _Array_ptr a : bounds(x, x + l) = x; +// CHECK_ALL: _Array_ptr b : bounds(x, x + l) = ((void *)0); +// CHECK_ALL: _Array_ptr c : bounds(x, x + l) = ((void *)0); + a++; + b = a; + c = b; + a = c; +} + +// Same as above, but now fresh lower bound needs to be created for `x`. +void test10(int *x, int l) { +// CHECK_ALL: void test10(_Array_ptr __3c_lower_bound_x : count(l), int l) _Checked { +// CHECK_ALL: _Array_ptr x : bounds(__3c_lower_bound_x, __3c_lower_bound_x + l) = __3c_lower_bound_x; + x++; + int *a = x, *b, *c; + // CHECK_ALL: _Array_ptr a : bounds(__3c_lower_bound_x, __3c_lower_bound_x + l) = x; + // CHECK_ALL: _Array_ptr b : bounds(__3c_lower_bound_x, __3c_lower_bound_x + l) = ((void *)0); + // CHECK_ALL: _Array_ptr c : bounds(__3c_lower_bound_x, __3c_lower_bound_x + l) = ((void *)0); + a++; + b = a; + c = b; + a = c; +} + +// Context sensitive edges should not cause `c` to be a lower bound for `b`. +void testx(int *a){ a[0]; } +void otherxx(){ + int *b; + int *c; + //CHECK_ALL: _Array_ptr __3c_lower_bound_b : count(0 + 1) = ((void *)0); + //CHECK_ALL: _Array_ptr b : bounds(__3c_lower_bound_b, __3c_lower_bound_b + 0 + 1) = __3c_lower_bound_b; + //CHECK_ALL: _Array_ptr c : count(0 + 1) = ((void *)0); + + testx(b); + testx(c); + b[0]; + c[0]; + b++; +} + +struct structy { int *b; }; +// CHECK_ALL: struct structy { _Array_ptr b; }; +void testy(struct structy d) { + // expected-error@+2 {{inferred bounds for '__3c_lower_bound_e' are unknown after initialization}} + // expected-note@+1 {{}} + int *e = d.b; + // CHECK_ALL: _Array_ptr __3c_lower_bound_e : count(0 + 1) = d.b; + // CHECK_ALL: _Array_ptr e : bounds(__3c_lower_bound_e, __3c_lower_bound_e + 0 + 1) = __3c_lower_bound_e; + + d.b = e; + e++; + + e[0]; +} + +void foo(int *x, unsigned long s) { +// CHECK_ALL: void foo(_Array_ptr x : count(s), unsigned long s) _Checked { + for (int i = 0; i < s; i++) + x[i]; +} + +void foo_caller(unsigned long l) { + int *a; + a++; + // CHECK_ALL:_Array_ptr __3c_lower_bound_a : count(l) = ((void *)0); + // CHECK_ALL:_Array_ptr a : bounds(__3c_lower_bound_a, __3c_lower_bound_a + l) = __3c_lower_bound_a; + + foo(a, l); + // expected-warning@-1 {{cannot prove argument meets declared bounds for 1st parameter}} + // expected-note@-2 {{}} + // expected-note@-3 {{}} +} + + +// Lower bound inference for `b` fails because `a` is out of scope. If `a` were +// in scope, it would be used as a lower bound. +void bar(int *b) { +// CHECK_ALL: void bar(_Array_ptr __3c_lower_bound_b : count(0 + 1)) _Checked { +// CHECK_ALL: _Array_ptr b : bounds(__3c_lower_bound_b, __3c_lower_bound_b + 0 + 1) = __3c_lower_bound_b; + int *a; + // CHECK_ALL: _Array_ptr a : count(0 + 1) = ((void *)0); + b = a; + // CHECK_ALL: __3c_lower_bound_b = a, b = __3c_lower_bound_b; + b++; + b[0]; +} diff --git a/clang/test/3C/realloc.c b/clang/test/3C/realloc.c index dd619a33df89..e64c639bc1ad 100644 --- a/clang/test/3C/realloc.c +++ b/clang/test/3C/realloc.c @@ -15,6 +15,6 @@ void foo(int *w) { y[1] = 3; int *z = realloc(y, 5 * sizeof(int)); //CHECK_NOALL: int *z = realloc(y, 5 * sizeof(int)); - //CHECK_ALL: _Array_ptr z : count(3 + 1) = realloc(y, 5 * sizeof(int)); + //CHECK_ALL: _Array_ptr z : count(5) = realloc(y, 5 * sizeof(int)); z[3] = 2; } diff --git a/clang/test/3C/realloc_complex.c b/clang/test/3C/realloc_complex.c index 45cebe7aaabd..20a1a3b6b0b1 100644 --- a/clang/test/3C/realloc_complex.c +++ b/clang/test/3C/realloc_complex.c @@ -42,10 +42,10 @@ void foo(int *count) { y[1] = 3; int *z = realloc(y, 5 * sizeof(int)); //CHECK_NOALL: int *z = realloc(y, 5 * sizeof(int)); - //CHECK_ALL: _Array_ptr z : count(3 + 1) = realloc(y, 5 * sizeof(int)); + //CHECK_ALL: _Array_ptr z : count(5) = realloc(y, 5 * sizeof(int)); int *m = realloc(w, 2 * sizeof(int)); //CHECK_NOALL: int *m = realloc(w, 2 * sizeof(int)); - //CHECK_ALL: _Array_ptr m : count(1 + 1) = realloc(w, 2 * sizeof(int)); + //CHECK_ALL: _Array_ptr m : count(2) = realloc(w, 2 * sizeof(int)); m[1] = 5; z[3] = 2; } diff --git a/clang/test/3C/return_not_least.c b/clang/test/3C/return_not_least.c index ec04ee83b6de..d2b88cd8fca8 100644 --- a/clang/test/3C/return_not_least.c +++ b/clang/test/3C/return_not_least.c @@ -47,7 +47,9 @@ int *bar() { //CHECK_ALL: _Array_ptr bar(void) { int *z = calloc(2, sizeof(int)); //CHECK_NOALL: int *z = calloc(2, sizeof(int)); - //CHECK_ALL: _Array_ptr z = calloc(2, sizeof(int)); + //CHECK_ALL: _Array_ptr __3c_lower_bound_z : count(2) = calloc(2, sizeof(int)); + //CHECK_ALL: _Array_ptr z : bounds(__3c_lower_bound_z, __3c_lower_bound_z + 2) = __3c_lower_bound_z; + z += 2; return z; } diff --git a/clang/test/3C/testgenerator.py b/clang/test/3C/testgenerator.py index c2cc6b4c7611..d66063a2191e 100755 --- a/clang/test/3C/testgenerator.py +++ b/clang/test/3C/testgenerator.py @@ -684,8 +684,11 @@ def process_file_smart(prefix, proto, suffix, name, cnameNOALL, cnameALL, name2, allfile.close() os.system("rm {} {}".format(cnameNOALL, cnameALL)) - # ensure all lines are the same length - assert len(lines) == len(noall) == len(yeall), "fix file " + name + # Prior to the introduction of lower bound inference an automatic lower + # bounds generation, the initial and converted code should have had the + # same number of lines. Generated lower bounds introduce new lines, + # so this is nolonger the case. + # assert len(lines) == len(noall) == len(yeall), "fix file " + name if proto == "multi": file2 = open(name2, "r") @@ -701,8 +704,8 @@ def process_file_smart(prefix, proto, suffix, name, cnameNOALL, cnameALL, name2, allfile2.close() os.system("rm {} {}".format(cname2NOALL, cname2ALL)) - # ensure all lines are the same length - assert len(lines2) == len(noall2) == len(yeall2), "fix file " + name + # See earlier comment on why this is disable. + # assert len(lines2) == len(noall2) == len(yeall2), "fix file " + name def runtime_cname(s): assert s.startswith("tmp.") @@ -720,10 +723,11 @@ def runtime_cname(s): ckeywords_re = re.compile("\\b(" + "|".join(ckeywords) + ")\\b") in_extern = False + ye_offset = 0 for i in range(0, len(lines)): line = lines[i] noline = noall[i] - yeline = yeall[i] + yeline = yeall[i + ye_offset] if "extern" in line: in_extern = True if (not in_extern and @@ -738,19 +742,28 @@ def runtime_cname(s): if noline == yeline: lines[i] += "\n" + indentation + "//CHECK: " + noline.lstrip() else: - lines[i] += ("\n" + indentation + "//CHECK_NOALL: " + - noline.lstrip()) - lines[i] += ("\n" + indentation + "//CHECK_ALL: " + - yeline.lstrip()) + lines[i] += ("\n" + indentation + "//CHECK_NOALL: " + noline.lstrip()) + lines[i] += ("\n" + indentation + "//CHECK_ALL: " + yeline.lstrip()) + + # This is a hack needed to properly updated tests where an array + # variable declaration has been duplicated to allow for generating + # fresh lower bound. + if i + ye_offset + 1 < len(yeall): + yeline_next = yeall[i + ye_offset + 1] + if "= __3c_" in yeline_next and "> __3c_" in yeline: + lines[i] += ("\n" + indentation + "//CHECK_ALL: " + yeline_next.lstrip()) + ye_offset += 1 + if ";" in line: in_extern = False if proto == "multi": in_extern = False + ye_offset = 0 for i in range(0, len(lines2)): line = lines2[i] noline = noall2[i] - yeline = yeall2[i] + yeline = yeall2[i + ye_offset] if "extern" in line: in_extern = True if (not in_extern and @@ -767,6 +780,14 @@ def runtime_cname(s): noline.lstrip()) lines2[i] += ("\n" + indentation + "//CHECK_ALL: " + yeline.lstrip()) + + # See above comment for why this hack is necessary. + if i + ye_offset + 1 < len(yeall2): + yeline_next = yeall2[i + ye_offset + 1] + if "= __3c_" in yeline_next and "> __3c_" in yeline: + lines2[i] += ("\n" + indentation + "//CHECK_ALL: " + yeline_next.lstrip()) + ye_offset += 1 + if ";" in line: in_extern = False From 00552bd1070de903dd7d05407744b48bbc00ae43 Mon Sep 17 00:00:00 2001 From: David Tarditi Date: Fri, 28 Jan 2022 15:08:43 -0800 Subject: [PATCH 38/38] Fix 3C test that fails only on Windows. (#1194) The test 3C/multiple_tu.c fails only on Windows. The problem is the test script. The script generates a command file in JSON format by piping a small generated Python program into the Python interpreter. The program contains strings for file names. On Windows, the directory separator is a backslash character (`\`). The backslash is interpreted as a string escape character. This results in JSON with incorrect paths. Use raw Python strings for the file names so that Python leaves them alone. --- clang/test/3C/multiple_tu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/test/3C/multiple_tu.c b/clang/test/3C/multiple_tu.c index f56cb54e6945..4b26232ceda9 100644 --- a/clang/test/3C/multiple_tu.c +++ b/clang/test/3C/multiple_tu.c @@ -1,6 +1,6 @@ // RUN: rm -rf %t // RUN: mkdir %t && cd %t -// RUN: python -c 'import sys, json; json.dump([{"arguments": ["clang", "-c", "%s"], "directory": "%S", "file": "%s"}]*2, sys.stdout)' > compile_commands.json +// RUN: python -c 'import sys, json; json.dump([{"arguments": ["clang", "-c", r"%s"], "directory": r"%S", "file": r"%s"}]*2, sys.stdout)' > compile_commands.json // RUN: 3c -dump-stats -p %t -base-dir=%S %s | FileCheck -match-full-lines %s // RUN: python -c 'import sys, json; exit(any(e["Name"].startswith("ImplicitCastExpr") for e in json.load(sys.stdin)["RootCauseStats"]))' < PerWildPtrStats.json