Skip to content

Target a single assignment in bounds checking error messages #893

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Aug 27, 2020

Conversation

dopelsunce
Copy link
Contributor

@dopelsunce dopelsunce commented Aug 3, 2020

  • In bounds checking related diagnostic messages, highlight the assignment within a top-level statement that causes the error or warning. This PR considers two types of bounds checking error messages for a variable V:
    1. The compiler cannot prove or can disprove the declared bounds for V are valid after an assignment to a variable in the bounds of V; and
    2. The inferred bounds of V become unknown after an assignment to a variable used in the declared bounds for V.
  • Print 'assignment', 'increment', 'decrement' or 'initialization' in the error message depending on the "type" of the target expression.

An example

array_ptr<int> p : count(len) = ...;
other = 0, len = len * 2;

Error message before the change (highlight the whole statement):

error: inferred bounds for 'p' are unknown after statement
  other = 0, len = len * 2;
  ^~~~~~~~~~~~~~~~~~~~~~~~~

After the change (blame the embedded assignment that caused the error; print 'assignment' instead of 'statement'):

error: inferred bounds for 'p' are unknown after assignment
  other = 0, len = len * 2;
             ^~~~~~~~~~~~~

Copy link
Contributor

@kkjeer kkjeer left a comment

Choose a reason for hiding this comment

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

Looks good so far! I think there is a missing case where the compiler cannot prove (or can disprove) that the updated observed bounds imply the declared bounds of a variable v after an assignment to a variable i that is used in the declared bounds of v. Also, please remove extraneous whitespace changes from the PR.

BDCType = Sema::BoundsDeclarationCheck::BDC_Initialization;
}

// Find the last assignment to V to blame in the diagnostic message if it
Copy link
Contributor

Choose a reason for hiding this comment

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

The expression in BlameAssignments (if it exists) might not be an assignment to V. It could be an assignment to a variable used in the bounds of V.

@@ -4069,8 +4134,10 @@ namespace {
const VarDecl *W = Pair.first;
BoundsExpr *Bounds = Pair.second;
BoundsExpr *AdjustedBounds = ReplaceVariableInBounds(Bounds, V, OriginalValue, CSS);
if (!Pair.second->isUnknown() && AdjustedBounds->isUnknown())
if (!Pair.second->isUnknown() && AdjustedBounds->isUnknown()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems to only update BlameAssignments if the updated observed bounds of W are unknown. For a case like:

unsigned int i;
array_ptr<int> p : count(i) = 0;
// Original value of i in i++ is i - 1
// i++ causes the updated observed bounds of p to be (p, p + i - 1)
i++, unrelated_variable = 0;

We want BlameAssignments[p] => i++.

//
// BlameAssignments is used to provide more context for two types of
// diagnostic messages:
// 1. The compiler cannot prove or can disprove the declared bounds for
Copy link
Contributor

Choose a reason for hiding this comment

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

It is also possible that the compiler cannot prove or can disprove the declared bounds for V are valid after an assignment to a variable used in the declared bounds of V. For example:

unsigned int len;
array_ptr<int> p : count(len) = 0;
// Original value of len is len + 2
// Updated observed bounds of p are (p, p + len - 2)
// Cannot prove that bounds(p, p + len - 2) imply (p, p + len)
len = len + 2;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've changed the comment to this general case.

@dopelsunce dopelsunce marked this pull request as ready for review August 4, 2020 00:14
}

// Find the assignment (if it exists) to blame for the error or warning.
auto It = State.BlameAssignments.find(V);
Copy link
Contributor

Choose a reason for hiding this comment

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

Can this logic move into a helper method that sets a SourceLocation, SourceRange, and BDCType based on St and BlameAssignments?

// the whole St in the error message.
SourceLocation Loc = St->getBeginLoc();
SourceRange SrcRange = St->getSourceRange();
auto BDCType = Sema::BoundsDeclarationCheck::BDC_Statement;
Copy link
Contributor

Choose a reason for hiding this comment

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

This could miss the case where the bounds of V are unknown after an initialization, e.g.

array_ptr<int> unknown_src : bounds(unknown);
// Bounds of arr are unknown after initialization
array_ptr<int> arr : count(1) = unknown_src;

// BlameAssignmentWithinStmt returns the source location of the blamed
// assignment.
SourceLocation BlameAssignmentWithinStmt(Stmt *St,
const VarDecl *V, CheckingState State, unsigned DiagId) const {
Copy link
Contributor

Choose a reason for hiding this comment

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

Please align const VarDecl *V underneath Stmt *St (and ensure the remaining parameters are on separate lines if necessary in order to prevent lines longer than 80 characters).

@@ -4017,11 +4023,55 @@ namespace {
<< ObservedBounds << ObservedBounds->getSourceRange();
}

// BlameAssignmentWithinStmt prints a diagnostic message that highlights the
// assignment expression in St that causes V's observed bounds to be unknown
// or invalid. If St is a DeclStmt, St itself and V are highlighted.
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: BlameAssignmentWithinStmt can also be used to print a diagnostic message if V's bounds are not necessarily invalid, but are not provably valid. I suggest updating the comment to something like "not provably valid" instead of "invalid".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the suggestion!

// starts at the beginning of a declaration T v = e, then extra
// diagnostics may be emitted for T.
SourceLocation Loc = St->getBeginLoc();
if (isa<DeclStmt>(St)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is is possible for a DeclStmt to have an entry in State.BlameAssignments? If not, you can early return here rather than searching for V in BlameAssignments below.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

DeclStmt does not have entries in State.BlameAssignments. I made the function return early inside the if block.

Copy link
Contributor

@kkjeer kkjeer left a comment

Choose a reason for hiding this comment

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

Looks good! @dtarditi @mgrang this is ready for a quick further review.

Copy link

@mgrang mgrang left a comment

Choose a reason for hiding this comment

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

Overall looks fine to me. Just a couple of clarifying comments.

if (UnaryOperator *UO = dyn_cast<UnaryOperator>(BlameExpr)) {
if (UO->isIncrementOp())
BDCType = Sema::BoundsDeclarationCheck::BDC_Increment;
else if (UO->isDecrementOp())
Copy link

Choose a reason for hiding this comment

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

Are there plans to handle other unary operators like unary minus, deref, address-of, etc?

Copy link
Contributor Author

@dopelsunce dopelsunce Aug 7, 2020

Choose a reason for hiding this comment

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

No. Here UO must be a self-modifying unary operator (because UO causes the bounds to be updated), and I can only think of ++ and --.

I can also put an assert here.

Copy link
Member

@dtarditi dtarditi left a comment

Choose a reason for hiding this comment

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

Looks great. Thank you! I have one suggestion for improving a comment.


// Choose the type of assignment E to show in the diagnostic messages
// from: assignment (=), decrement (--) or increment (++). These are all
// modifying operators that can update observed bounds.
Copy link
Member

@dtarditi dtarditi Aug 27, 2020

Choose a reason for hiding this comment

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

Could you add a comment that says "If none of these cases match, the diagnostic message reports that the error is for a statement."? When I first read the code, I noticed there were several ways we could fall off the end of an if-statement chain. It doesn't matter because the code just reports a diagnostic message for a statement type.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants