Skip to content

Commit

Permalink
Use simple facts about expression equality. Expand checking of bounds…
Browse files Browse the repository at this point in the history
… declarations. (#476)

This change extends the compiler to collect and use simple facts about expression equality when checking bounds declarations.  It also turns on checking of bounds declarations for unchecked scopes by default.  It was only on by default for checked scopes.  The checking of bounds declarations is still simple, so it may produce (many) unnecessary warnings.   It can be turned off using  `-Wno-check-bounds-decls` (everywhere), `-Wno-check-bounds-decl-unchecked-scope` (for unchecked scopes), and `-Wno-check-bounds-decls-checked-scope` (for checked scopes), and.  This change addresses issues #474 and #472.

Equality facts are gathered at assignments and initializers.  They are not propagated across statements. For example, given `x = y`, when checking the bounds for `x` are implied by the bounds for `y`, we assume `x == y`.  In general, given `e1 = e2`, we check that `e1` and `e2` are non-modifying expressions and collect the fact that `e1 == e2`.   (We still need to check that the assignment doesn't modify an lvalue used by` e2`, and will add that in the future.  We may miss issuing warnings that we should issue,)
- We pass a list of sets of equivalent expressions to CanonBounds, which checks whether two expressions compute the same value.  We try to compare expressions and if that fails, we consult the list of sets.  We check each expression to see if it is equivalent to an expression in a set (we don't recursively use the equality information).
- When setting up the equality fact `e1 == e2`, we add an lvalue-to-rvalue cast for the IR for `e1`, .   Given a declarator for a variable v with an initializer, we make sure to turn v into a lvalue-to-rvalue cast of a decl-ref.
- There was code in SemaBounds.cpp that puts an expression into a standard form by ignoring non-value changing operations.   That code is moved to CanonBounds.cpp.
- The types of subexpressions of an expression affect the meaning of an expression.  When comparing two expressions, check that the types of their subexpressions match.  This wasn't a concern before when the code was strictly comparing lexical  equality.  There is a TODO for work needed for bounds-safe interfaces, tracked by Github issue #475.

The change fixes a few bugs found during testing of the code for checking equivalence of expressions:
- CanonBounds.cpp needs to treat implicit and C-style casts as being equivalent if they cast to/from the same type.
- In SemaBounds.cpp, when we infer the rvalue bounds of a bounds cast expression of the form *_bounds_cast<T>(`e1`, bounds-expr), the bounds need to be for `(T) e1`, not `e1` (`e1` has the wrong type).
- In SemaExpr.cpp, when type checking bounds(`e1`, `e2`), we need to check that e1 and e2 point to compatible types, not exactly the same type.

Testing:
- Updated the Checked C repo tests.  This will be covered by a separate pull request:
  - Added more tests of expression equivalence (lexical_equality should be renamed, as it checks for expression equivalence using a few additional facts about C expressions).
  - Updated tests to handle the fact that checking of bounds declaration is on everywhere by default.  Replace a bunch of TODO's with an expected warning message. Also deleted some warnings that are no longer issued because the compiler can prove the bounds are valid.
  - Kept checking of bounds declarations on for most tests and only disabled it for some parsing and typechecking tests.
  - In some cases, made simple changes to avoid warnings about bounds declarations (the original cases are captured in the new Checked C clang repo file test/CheckedC/static-checking/bounds-decl-challenges.c)
- Update the Checked C clang tests:
  - In bounds-decl-checking.c, remove expected warnings that are now gone because the compiler understand equality facts.  Add code that checks the compiler understands equality facts involving arrays.  Include a negative test where the compiler concludes that an array is too small for the declared bounds for an array_ptr variable.
- Passed automated testing on Linux x64 (clang regression tests and LNT testing)
  • Loading branch information
dtarditi committed Apr 12, 2018
1 parent dfbae3d commit f0f94c7
Show file tree
Hide file tree
Showing 13 changed files with 380 additions and 179 deletions.
4 changes: 3 additions & 1 deletion include/clang/AST/ASTContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#include "clang/AST/ASTTypeTraits.h"
#include "clang/AST/CanonicalType.h"
#include "clang/AST/CanonBounds.h"
#include "clang/AST/CommentCommandTraits.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclarationName.h"
Expand Down Expand Up @@ -2515,7 +2516,8 @@ class ASTContext : public RefCountedBase<ASTContext> {
public:
bool EquivalentAnnotations(const BoundsAnnotations &Annots1,
const BoundsAnnotations &Annots2);
bool EquivalentBounds(const BoundsExpr *Expr1, const BoundsExpr *Expr2);
bool EquivalentBounds(const BoundsExpr *Expr1, const BoundsExpr *Expr2,
EquivExprSets *EquivExprs = nullptr);
bool EquivalentInteropTypes(const InteropTypeExpr *Expr1,
const InteropTypeExpr *Expr2);

Expand Down
20 changes: 10 additions & 10 deletions include/clang/AST/CanonBounds.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,8 @@ namespace clang {
class Expr;
class VarDecl;

// Abstract base class that provides information what variables
// currently are equal to each other.
class EqualityRelation {
public:
virtual const VarDecl *getRepresentative(const VarDecl *V) = 0;
};
// List of sets of equivalent expressions.
typedef SmallVector<SmallVector<Expr *, 4> *, 4> EquivExprSets;

class Lexicographic {
public:
Expand All @@ -52,7 +48,7 @@ namespace clang {

private:
ASTContext &Context;
EqualityRelation *EqualVars;
EquivExprSets *EquivExprs;
bool Trace;

template <typename T>
Expand All @@ -66,6 +62,10 @@ namespace clang {
return Lexicographic::CompareImpl(E1, E2);
}

// See if E1 and E2 are considered equivalent using EquivExprs. If
// they are not, return Current.
Result CheckEquivExprs(Result Current, const Expr *E1, const Expr *E2);

Result CompareInteger(signed I1, signed I2);
Result CompareInteger(unsigned I1, unsigned I2);
Result CompareRelativeBoundsClause(const RelativeBoundsClause *RC1,
Expand All @@ -86,8 +86,7 @@ namespace clang {
Result CompareImpl(const BinaryOperator *E1, const BinaryOperator *E2);
Result CompareImpl(const CompoundAssignOperator *E1,
const CompoundAssignOperator *E2);
Result CompareImpl(const ImplicitCastExpr *E1, const ImplicitCastExpr *E2);
Result CompareImpl(const CStyleCastExpr *E1, const CStyleCastExpr *E2);
Result CompareImpl(const CastExpr *E1, const CastExpr *E2);
Result CompareImpl(const CompoundLiteralExpr *E1,
const CompoundLiteralExpr *E2);
Result CompareImpl(const GenericSelectionExpr *E1,
Expand All @@ -105,7 +104,7 @@ namespace clang {


public:
Lexicographic(ASTContext &Ctx, EqualityRelation *EV);
Lexicographic(ASTContext &Ctx, EquivExprSets *EquivExprs);

/// \brief Lexicographic comparison of expressions that can occur in
/// bounds expressions.
Expand All @@ -115,6 +114,7 @@ namespace clang {
/// or types.
Result CompareDecl(const NamedDecl *D1, const NamedDecl *D2);
Result CompareType(QualType T1, QualType T2);
Result CompareTypeIgnoreCheckedness(QualType QT1, QualType QT2);
};
} // end namespace clang

Expand Down
6 changes: 5 additions & 1 deletion include/clang/Basic/DiagnosticGroups.td
Original file line number Diff line number Diff line change
Expand Up @@ -893,7 +893,11 @@ def MicrosoftEndOfFile : DiagGroup<"microsoft-end-of-file">;
def MicrosoftInaccessibleBase : DiagGroup<"microsoft-inaccessible-base">;

// Checked C warnings for bounds declaration checking.
def CheckBoundsDecls : DiagGroup<"check-bounds-decls">;
def CheckBoundsDeclsUnchecked : DiagGroup<"check-bounds-decls-unchecked-scope">;
def CheckBoundsDeclsChecked : DiagGroup<"check-bounds-decls-checked-scope">;
def CheckBoundsDecls : DiagGroup<"check-bounds-decls",
[CheckBoundsDeclsUnchecked,
CheckBoundsDeclsChecked]>;
// Checked C warnings about memory accesses determined to be out of
// declared bounds.
def CheckMemoryAccesses : DiagGroup<"check-memory-accesses">;
Expand Down
14 changes: 6 additions & 8 deletions include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -9640,13 +9640,12 @@ def err_bounds_type_annotation_lost_checking : Error<
def warn_bounds_declaration_invalid : Warning<
"cannot prove declared bounds for %1 are valid after "
"%select{assignment|initialization}0">,
DefaultIgnore,
InGroup<CheckBoundsDecls>;
InGroup<CheckBoundsDeclsUnchecked>;

def warn_checked_scope_bounds_declaration_invalid : Warning<
"cannot prove declared bounds for %1 are valid after "
"%select{assignment|initialization}0">,
InGroup<CheckBoundsDecls>;
InGroup<CheckBoundsDeclsChecked>;

def error_bounds_declaration_invalid : Error<
"declared bounds for %1 are invalid after "
Expand All @@ -9670,12 +9669,11 @@ def err_bounds_type_annotation_lost_checking : Error<

def warn_argument_bounds_invalid : Warning<
"cannot prove argument meets declared bounds for %ordinal0 parameter">,
DefaultIgnore,
InGroup<CheckBoundsDecls>;
InGroup<CheckBoundsDeclsUnchecked>;

def warn_checked_scope_argument_bounds_invalid : Warning<
"cannot prove argument meets declared bounds for %ordinal0 parameter">,
InGroup<CheckBoundsDecls>;
InGroup<CheckBoundsDeclsChecked>;

def error_argument_bounds_invalid : Error<
"argument does not meet declared bounds for %ordinal0 parameter">;
Expand All @@ -9686,11 +9684,11 @@ def err_bounds_type_annotation_lost_checking : Error<
def warn_static_cast_bounds_invalid : Warning<
"cannot prove cast source bounds are wide enough for %0">,
DefaultIgnore,
InGroup<CheckBoundsDecls>;
InGroup<CheckBoundsDeclsUnchecked>;

def warn_checked_scopestatic_cast_bounds_invalid : Warning<
"cannot prove cast source bounds are wide enough for %0">,
InGroup<CheckBoundsDecls>;
InGroup<CheckBoundsDeclsChecked>;

def error_static_cast_bounds_invalid : Error<
"cast source bounds are too narrow for %0">;
Expand Down
2 changes: 1 addition & 1 deletion include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -4587,7 +4587,7 @@ class Sema {
//===---------------------------- Checked C Extension ----------------------===//

private:
const Type *ValidateBoundsExprArgument(Expr *Arg);
QualType ValidateBoundsExprArgument(Expr *Arg);

public:
ExprResult ActOnNullaryBoundsExpr(SourceLocation BoundKWLoc,
Expand Down
5 changes: 3 additions & 2 deletions lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9047,9 +9047,10 @@ bool ASTContext::isNotAllowedForNoPrototypeFunction(QualType QT) const {
return false;
}

bool ASTContext::EquivalentBounds(const BoundsExpr *Expr1, const BoundsExpr *Expr2) {
bool ASTContext::EquivalentBounds(const BoundsExpr *Expr1, const BoundsExpr *Expr2,
EquivExprSets *EquivExprs) {
if (Expr1 && Expr2) {
Lexicographic::Result Cmp = Lexicographic(*this, nullptr).CompareExpr(Expr1, Expr2);
Lexicographic::Result Cmp = Lexicographic(*this, EquivExprs).CompareExpr(Expr1, Expr2);
return Cmp == Lexicographic::Result::Equal;
}

Expand Down
Loading

0 comments on commit f0f94c7

Please sign in to comment.