Skip to content
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ec74226
Add FunctionDeclaration and ReturnVal members to CheckBoundsDeclarati…
Aug 6, 2021
e589681
Add ReturnStmt to the ProofStmtKind enum
Aug 7, 2021
e0e2bc3
Do not check for free variables if E1 or E2 is a _Return_value expres…
Aug 7, 2021
b53da94
Add ProveReturnBoundsValidity method
Aug 7, 2021
3d860c2
Add diagnostic note messages for declared and inferred return bounds
Aug 7, 2021
3055184
Add DiagnoseUnknownReturnBounds method
Aug 7, 2021
f821bf5
Add ValidateReturnBounds method
Aug 7, 2021
91a6128
Validate the return value bounds in CheckReturnStmt
Aug 7, 2021
0d29c2b
Change return values in bounds-decl-checking test to avoid return val…
Aug 7, 2021
bb3e9db
Mark the 3C test files functionDeclEnd.c and itype_nt_arr_cast.c as X…
Aug 7, 2021
1197e7c
Add return-bounds.c test file containing some initial tests for check…
Aug 7, 2021
9d369fd
Change 2 return values in dump-dataflow-facts.c test in order to avoi…
Aug 7, 2021
7bbc1f6
Add bounds-safe interface test cases to return-bounds.c
Aug 10, 2021
9013f6d
Fix the logic for recording equality between RetExpr and ReturnVal by…
Aug 10, 2021
8237d6c
Add tests for bounds casts to return-bounds.c
Aug 10, 2021
df46f91
Add tests for function calls to return-bounds.c
Aug 10, 2021
b613afb
Add no warnings/errors test case for expanded declared return bounds
Aug 12, 2021
f8a72a6
Skip checking return bounds in an unchecked scope, for functions with…
Aug 17, 2021
86ccdbf
Update return bounds tests (add more tests for bounds-safe interfaces)
Aug 17, 2021
489ec1a
Enable 3C/itype_nt_arr_cast.c test
Aug 17, 2021
7600025
Add checked/unchecked scope comments to 3C/functionDeclEnd.c test
Aug 17, 2021
7e966a6
Change test7 in functionDeclEnd.c to infer a valid bound
john-h-kastner Aug 18, 2021
d4a7eab
Merge branch 'master' of https://github.com/microsoft/checkedc-clang …
Aug 27, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions clang/include/clang/AST/ExprUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ class ExprUtil {
// pointer). Returns false if E is nullptr.
static bool ReadsMemoryViaPointer(Expr *E, bool IncludeAllMemberExprs = false);

// IsReturnValueExpr return true if the expression E is a _Return_value
// expression.
static bool IsReturnValueExpr(Expr *E);

// FindLValue returns true if the given lvalue expression occurs in E.
static bool FindLValue(Sema &S, Expr *LValue, Expr *E);

Expand Down
44 changes: 35 additions & 9 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -11386,6 +11386,10 @@ def err_bounds_type_annotation_lost_checking : Error<
"argument has unknown bounds, bounds expected because the "
"%ordinal0 parameter has bounds">;

def err_expected_bounds_for_return : Error<
"return value has unknown bounds, bounds expected because the "
"function %0 has bounds">;

def err_initializer_expected_with_bounds : Error<
"automatic variable %0 with bounds must have initializer">;

Expand Down Expand Up @@ -11619,8 +11623,29 @@ def err_bounds_type_annotation_lost_checking : Error<
def error_static_cast_bounds_invalid : Error<
"cast source bounds are too narrow for %0">;

def error_return_bounds_invalid : Error<
"return value bounds do not imply declared return bounds for %0">;

def error_return_bounds_unprovable: Error<
"it is not possible to prove that return value bounds "
"imply declared return bounds for %0">;

def warn_return_bounds_invalid: Warning<
"cannot prove return value bounds imply declared return bounds for %0">,
InGroup<CheckBoundsDeclsUnchecked>;

def warn_checked_scope_return_bounds_invalid : Warning<
"cannot prove return value bounds imply declared return bounds for %0">,
InGroup<CheckBoundsDeclsChecked>;

def note_declared_return_bounds : Note<
"(expanded) declared return bounds are '%0'">;

def note_inferred_return_bounds : Note<
"(expanded) inferred return value bounds are '%0'">;

def error_out_of_bounds_access : Error<
"out-of-bounds %select{||memory access|base value}0">;
"out-of-bounds %select{|||memory access|base value}0">;

def note_source_bounds_empty : Note<"source bounds are an empty range">;

Expand All @@ -11631,21 +11656,22 @@ def err_bounds_type_annotation_lost_checking : Error<
def note_destination_bounds_invalid : Note<"destination bounds are an invalid range">;

def note_bounds_too_narrow : Note<
"%select{destination bounds are|target bounds are|memory accessed is|"
"struct/union pointed to by base is}0 wider than the "
"%select{source|source||}0 bounds">;
"%select{destination bounds are|target bounds are|declared return bounds are|"
"memory accessed is|struct/union pointed to by base is|}0 wider "
"than the %select{source|source|return value|source|source}0 bounds">;

def note_lower_out_of_bounds : Note<
"%select{destination lower bound is|target lower bound is|accesses memory|"
"base value is}0 below %select{source|source|the|its}0 lower bound">;
"%select{destination lower bound is|target lower bound is|"
"declared return lower bound is|accesses memory|base value is}0 "
"below %select{source|source|return value|the|its}0 lower bound">;

def note_upper_out_of_bounds : Note<
"%select{destination upper bound is|target upper bound is|"
"accesses memory at or|base value is}0 "
"above %select{source|source|the|its}0 upper bound">;
"declared return upper bound is|accesses memory at or|base value is}0 "
"above %select{source|source|return value|the|its}0 upper bound">;

def note_bounds_partially_overlap : Note<
"%select{||accesses memory that|struct/union pointed to by base value}0 is "
"%select{|||accesses memory that|struct/union pointed to by base value}0 is "
"only partially in bounds">;

def no_prototype_generic_function : Error<
Expand Down
7 changes: 7 additions & 0 deletions clang/lib/AST/ExprUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,13 @@ bool ExprUtil::ReadsMemoryViaPointer(Expr *E, bool IncludeAllMemberExprs) {
}
}

bool ExprUtil::IsReturnValueExpr(Expr *E) {
BoundsValueExpr *BVE = dyn_cast_or_null<BoundsValueExpr>(E);
if (!BVE)
return false;
return BVE->getKind() == BoundsValueExpr::Kind::Return;
}

namespace {
class FindLValueHelper : public RecursiveASTVisitor<FindLValueHelper> {
private:
Expand Down
201 changes: 189 additions & 12 deletions clang/lib/Sema/SemaBounds.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -652,8 +652,20 @@ namespace {
uint64_t PointerWidth;
Stmt *Body;
CFG *Cfg;
BoundsExpr *ReturnBounds; // return bounds expression for enclosing
// function, if any.

// Declaration for enclosing function. Having this here allows us to emit
// the name of the function in any diagnostic message when checking return
// bounds.
FunctionDecl *FunctionDeclaration;
// Return value expression for enclosing function, if any. Having this
// here allows us to avoid reconstructing a return value for each
// return statement.
BoundsValueExpr *ReturnVal;
// Expanded declared return bounds expression for enclosing function, if
// any. Having this here allows us to avoid re-expanding the return bounds
// for each return statement.
BoundsExpr *ReturnBounds;

ASTContext &Context;
std::pair<ComparisonSet, ComparisonSet> &Facts;

Expand Down Expand Up @@ -946,6 +958,7 @@ namespace {
enum class ProofStmtKind : unsigned {
BoundsDeclaration,
StaticBoundsCast,
ReturnStmt,
MemoryAccess,
MemberArrowBase
};
Expand Down Expand Up @@ -1366,6 +1379,11 @@ namespace {
ExprUtil::ReadsMemoryViaPointer(E2, true))
return false;

// If E1 or E2 is a _Return_value expression, we skip since we cannot
// determine the set of variables that occur in these expressions.
if (ExprUtil::IsReturnValueExpr(E1) || ExprUtil::IsReturnValueExpr(E2))
return false;

bool HasFreeVariables = false;
EqualExprTy Vars1 = CollectVariableSet(S, E1);
EqualExprTy Vars2 = CollectVariableSet(S, E2);
Expand Down Expand Up @@ -1943,6 +1961,9 @@ namespace {
return false;
}

// Methods to try to prove that an inferred bounds expression implies
// the validity of a target bounds expression.

// Try to prove that SrcBounds implies the validity of DeclaredBounds.
//
// If Kind is StaticBoundsCast, check whether a static cast between Ptr
Expand Down Expand Up @@ -2020,6 +2041,80 @@ namespace {
return ProofResult::Maybe;
}

// Try to prove that RetExprBounds implies the validity of ReturnBounds
// (the declared return bounds for the enclosing function).
ProofResult ProveReturnBoundsValidity(Expr *RetExpr,
BoundsExpr *RetExprBounds,
const EquivExprSets EQ,
const EqualExprTy G,
ProofFailure &Cause,
FreeVariableListTy &FreeVariables) {
// Check some basic properties of the declared ReturnBounds and the
// source RetExprBounds. Even though these checks will also be done
// in ProveBoundsDeclValidity, if any of these checks result in an
// early return, we can avoid constructing a modified EquivExprs set
// that records equality between RetVal and RetExpr.

// Null declared return bounds or declared return bounds(unknown)
// implied by any other bounds.
if (!ReturnBounds || ReturnBounds->isUnknown())
return ProofResult::True;

// Ignore invalid bounds.
if (RetExprBounds->isInvalid() || ReturnBounds->isInvalid())
return ProofResult::True;

// Return expression bounds(any) implies any declared return bounds.
if (RetExprBounds->isAny())
return ProofResult::True;

// Return expression bounds(unknown) cannot imply any non-unknown
// declared return bounds.
if (RetExprBounds->isUnknown())
return ProofResult::False;

// Record equality between ReturnVal and all expressions that produce
// the same value as RetExpr. This allows ProveBoundsDeclValidity to
// validate bounds that depend on the return value of the function.
// For example, if the declared function bounds are count(1), then the
// expanded ReturnBounds will be bounds(RetVal, RetVal + 1).
EquivExprSets EquivExprs = EQ;

// Determine the set of expressions that produce the same value as
// RetExpr.
// If G (the set of expressions that produce the same value as RetExpr)
// is empty, then RetExpr may be an expression that is not allowed to
// be recorded in State.EquivExprs (e.g. an expression that reads memory
// via a pointer). The EquivExprs set that we construct here is temporary
// and is only used to check the return bounds for one return statement,
// so we can add RetExpr to EquivExprs in this case.
EqualExprTy RetSameValue = G;
if (RetSameValue.size() == 0)
RetSameValue.push_back(RetExpr);

bool FoundRetExpr = false;
for (auto F = EquivExprs.begin(); F != EquivExprs.end(); ++F) {
if (DoExprSetsIntersect(*F, RetSameValue)) {
// Add all expressions in RetSameValue to F that are not already in F.
for (Expr *E : RetSameValue)
if (!EqualExprsContainsExpr(*F, E))
F->push_back(E);
F->push_back(ReturnVal);
FoundRetExpr = true;
break;
}
}
if (!FoundRetExpr) {
EqualExprTy F = RetSameValue;
F.push_back(ReturnVal);
EquivExprs.push_back(F);
}

return ProveBoundsDeclValidity(ReturnBounds, RetExprBounds, Cause,
&EquivExprs, FreeVariables,
ProofStmtKind::ReturnStmt);
}

// CompareNormalizedBounds returns true if SrcBounds implies DeclaredBounds
// after applying certain transformations to the upper bound expressions
// of both bounds.
Expand Down Expand Up @@ -2569,14 +2664,16 @@ namespace {


public:
CheckBoundsDeclarations(Sema &SemaRef, PrepassInfo &Info, Stmt *Body, CFG *Cfg, BoundsExpr *ReturnBounds, std::pair<ComparisonSet, ComparisonSet> &Facts) : S(SemaRef),
CheckBoundsDeclarations(Sema &SemaRef, PrepassInfo &Info, Stmt *Body, CFG *Cfg, FunctionDecl *FD, std::pair<ComparisonSet, ComparisonSet> &Facts) : S(SemaRef),
DumpBounds(SemaRef.getLangOpts().DumpInferredBounds),
DumpState(SemaRef.getLangOpts().DumpCheckingState),
DumpSynthesizedMembers(SemaRef.getLangOpts().DumpSynthesizedMembers),
PointerWidth(SemaRef.Context.getTargetInfo().getPointerWidth(0)),
Body(Body),
Cfg(Cfg),
ReturnBounds(ReturnBounds),
FunctionDeclaration(FD),
ReturnVal(nullptr),
ReturnBounds(nullptr),
Context(SemaRef.Context),
Facts(Facts),
BoundsWideningAnalyzer(BoundsWideningAnalysis(SemaRef, Cfg,
Expand All @@ -2585,7 +2682,16 @@ namespace {
Info.CheckedScopeMap)),
AbstractSetMgr(AbstractSetManager(SemaRef, Info.VarUses)),
BoundsSiblingFields(Info.BoundsSiblingFields),
IncludeNullTerminator(false) {}
IncludeNullTerminator(false) {
if (FD) {
ReturnVal =
new (S.Context) BoundsValueExpr(SourceLocation(),
FD->getReturnType(),
BoundsValueExpr::Kind::Return);
ReturnBounds =
BoundsUtil::ExpandToRange(S, ReturnVal, FD->getBoundsExpr());

@sulekhark sulekhark Aug 12, 2021

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. The function ExpandToRange only expands count (or, byte_count) bounds expressions. Does this mean that in a range bounds expression like bounds(_Return_value, _Return_value + 2), the abstract place holder _Return_value will not be replaced by the concrete value ReturnVal?

  2. It will be great if a positive test case (no errors or warnings) with similar return bounds as in the test case f10 in clang/test/CheckedC/static-checking/return-bounds.c is added.

@kkjeer kkjeer Aug 12, 2021

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ReturnVal is a _Return_value expression (a BoundsValueExpr with the Kind Return). If a function has declared bounds of count(size), ExpandToRange will return bounds(_Return_value, _Return_value + size). If a function has declared bounds of bounds(_Return_value, _Return_value + size), ExpandToRange will return bounds(_Return_value, _Return_value + size).

I've added a test case for this (function f7 with no errors or warnings) to return-bounds.c.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, thanks for the clarification.

}
}

CheckBoundsDeclarations(Sema &SemaRef, PrepassInfo &Info, std::pair<ComparisonSet, ComparisonSet> &Facts) : S(SemaRef),
DumpBounds(SemaRef.getLangOpts().DumpInferredBounds),
Expand All @@ -2594,6 +2700,8 @@ namespace {
PointerWidth(SemaRef.Context.getTargetInfo().getPointerWidth(0)),
Body(nullptr),
Cfg(nullptr),
FunctionDeclaration(nullptr),
ReturnVal(nullptr),
ReturnBounds(nullptr),
Context(SemaRef.Context),
Facts(Facts),
Expand Down Expand Up @@ -3922,14 +4030,13 @@ namespace {
return ResultBounds;

// Check the return value if it exists.
Check(RetValue, CSS, State);
BoundsExpr *RetValueBounds = Check(RetValue, CSS, State);

if (!ReturnBounds)
return ResultBounds;
// Check that the return expression bounds imply the return bounds.
ValidateReturnBounds(RS, RetValue, RetValueBounds, State.EquivExprs,
State.SameValue, CSS);

// TODO: Actually check that the return expression bounds imply the
// return bounds.
// TODO: Also check that any parameters used in the return bounds are
// TODO: Check that any parameters used in the return bounds are
// unmodified.
return ResultBounds;
}
Expand Down Expand Up @@ -4500,6 +4607,76 @@ namespace {
return Loc;
}

// ValidateReturnBounds checks that the observed bounds for the return
// value RetExpr imply the declared bounds for the enclosing function.
void ValidateReturnBounds(ReturnStmt *RS, Expr *RetExpr,
BoundsExpr *RetExprBounds,
const EquivExprSets EquivExprs,
const EqualExprTy RetSameValue,
CheckedScopeSpecifier CSS) {
// In an unchecked scope, if the enclosing function has a bounds-safe
// interface, and the return value has not been implicitly converted
// to an unchecked pointer, we skip checking the return value bounds.
if (CSS == CheckedScopeSpecifier::CSS_Unchecked) {
if (FunctionDeclaration->hasBoundsSafeInterface(Context)) {
if (!IsBoundsSafeInterfaceAssignment(FunctionDeclaration->getReturnType(), RetExpr))
return;
}
}

ProofFailure Cause;
FreeVariableListTy FreeVars;
ProofResult Result = ProveReturnBoundsValidity(RetExpr, RetExprBounds,
EquivExprs, RetSameValue,
Cause, FreeVars);

if (Result == ProofResult::True)
return;

if (RetExprBounds->isUnknown()) {
DiagnoseUnknownReturnBounds(RS, RetExpr);
return;
}

// Which diagnostic message to print?
unsigned DiagId =
(Result == ProofResult::False)
? (TestFailure(Cause, ProofFailure::HasFreeVariables)
? diag::error_return_bounds_unprovable
: diag::error_return_bounds_invalid)
: (CSS != CheckedScopeSpecifier::CSS_Unchecked
? diag::warn_checked_scope_return_bounds_invalid
: diag::warn_return_bounds_invalid);

SourceLocation Loc = RetExpr->getBeginLoc();
S.Diag(Loc, DiagId) << FunctionDeclaration << RS->getSourceRange();
if (Result == ProofResult::False)
ExplainProofFailure(Loc, Cause, ProofStmtKind::ReturnStmt);

if (TestFailure(Cause, ProofFailure::HasFreeVariables))
DiagnoseFreeVariables(diag::note_free_variable_decl_or_inferred, Loc,
FreeVars);

SourceLocation DeclaredLoc = FunctionDeclaration->getLocation();
S.Diag(DeclaredLoc, diag::note_declared_return_bounds)
<< ReturnBounds << ReturnBounds->getSourceRange();
S.Diag(Loc, diag::note_inferred_return_bounds)
<< RetExprBounds << RetExprBounds->getSourceRange();
}

// DiagnoseUnknownReturnBounds emits an error message at the return
// statement RS and return expression RetExpr whose inferred bounds are
// unknown, where the enclosing function has declared bounds ReturnBounds.
void DiagnoseUnknownReturnBounds(ReturnStmt *RS, Expr *RetExpr) {
SourceLocation Loc = RetExpr->getBeginLoc();
S.Diag(Loc, diag::err_expected_bounds_for_return)
<< FunctionDeclaration << RS->getSourceRange();

SourceLocation DeclaredLoc = FunctionDeclaration->getLocation();
S.Diag(DeclaredLoc, diag::note_declared_return_bounds)
<< ReturnBounds << ReturnBounds->getSourceRange();
}

// Methods to update the checking state.

// UpdateAfterAssignment updates the checking state after the lvalue
Expand Down Expand Up @@ -6134,7 +6311,7 @@ void Sema::CheckFunctionBodyBoundsDecls(FunctionDecl *FD, Stmt *Body) {
BO.AddLifetime = true;
BO.AddNullStmt = true;
std::unique_ptr<CFG> Cfg = CFG::buildCFG(nullptr, Body, &getASTContext(), BO);
CheckBoundsDeclarations Checker(*this, Info, Body, Cfg.get(), FD->getBoundsExpr(), EmptyFacts);
CheckBoundsDeclarations Checker(*this, Info, Body, Cfg.get(), FD, EmptyFacts);
if (Cfg != nullptr) {
AvailableFactsAnalysis Collector(*this, Cfg.get());
Collector.Analyze();
Expand Down
Loading