-
Notifications
You must be signed in to change notification settings - Fork 341
[Safe Buffers][BoundsSafety] Fix a bug in the interop analysis that can cause infinite loops #10129
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -426,23 +426,37 @@ getDependentValuesFromCall(const CountAttributedType *CAT, | |
// Other kinds of function calls are not supported, so an expression of the | ||
// form `f(...)` is not supported. | ||
struct CompatibleCountExprVisitor | ||
: public ConstStmtVisitor<CompatibleCountExprVisitor, bool, const Expr *> { | ||
: public ConstStmtVisitor<CompatibleCountExprVisitor, bool, const Expr *, | ||
bool> { | ||
// The third 'bool' type parameter for each visit method indicates whether the | ||
// current visiting expression is the result of the formal parameter to actual | ||
// argument substitution. Since the argument expression may contain DREs | ||
// referencing to back to those parameters (in cases of recursive calls), the | ||
// analysis may hit an infinite loop if not knowing whether the substitution | ||
// has happened. A typical example that could introduce infinite loop without | ||
// this knowledge is shown below. | ||
// ``` | ||
// void f(int * __counted_by(n) p, size_t n) { | ||
// f(p, n); | ||
// } | ||
// ``` | ||
using BaseVisitor = | ||
ConstStmtVisitor<CompatibleCountExprVisitor, bool, const Expr *>; | ||
ConstStmtVisitor<CompatibleCountExprVisitor, bool, const Expr *, bool>; | ||
|
||
const Expr *MemberBase; | ||
const DependentValuesTy *DependentValues; | ||
ASTContext &Ctx; | ||
|
||
// If `Deref` has the form `*&e`, return `e`; otherwise return nullptr. | ||
const Expr *trySimplifyDerefAddressof(const UnaryOperator *Deref) { | ||
const Expr *trySimplifyDerefAddressof(const UnaryOperator *Deref, | ||
bool hasBeenSubstituted) { | ||
const Expr *DerefOperand = Deref->getSubExpr()->IgnoreParenImpCasts(); | ||
|
||
if (const auto *UO = dyn_cast<UnaryOperator>(DerefOperand)) | ||
if (UO->getOpcode() == UO_AddrOf) | ||
return UO->getSubExpr(); | ||
if (const auto *DRE = dyn_cast<DeclRefExpr>(DerefOperand)) { | ||
if (!DependentValues) | ||
if (!DependentValues || hasBeenSubstituted) | ||
return nullptr; | ||
|
||
auto I = DependentValues->find(DRE->getDecl()); | ||
|
@@ -460,18 +474,22 @@ struct CompatibleCountExprVisitor | |
ASTContext &Ctx) | ||
: MemberBase(MemberBase), DependentValues(DependentValues), Ctx(Ctx) {} | ||
|
||
bool VisitStmt(const Stmt *S, const Expr *E) { return false; } | ||
bool VisitStmt(const Stmt *S, const Expr *E, bool hasBeenSubstituted) { | ||
return false; | ||
} | ||
|
||
bool VisitImplicitCastExpr(const ImplicitCastExpr *SelfICE, | ||
const Expr *Other) { | ||
return Visit(SelfICE->getSubExpr(), Other); | ||
bool VisitImplicitCastExpr(const ImplicitCastExpr *SelfICE, const Expr *Other, | ||
bool hasBeenSubstituted) { | ||
return Visit(SelfICE->getSubExpr(), Other, hasBeenSubstituted); | ||
} | ||
|
||
bool VisitParenExpr(const ParenExpr *SelfPE, const Expr *Other) { | ||
return Visit(SelfPE->getSubExpr(), Other); | ||
bool VisitParenExpr(const ParenExpr *SelfPE, const Expr *Other, | ||
bool hasBeenSubstituted) { | ||
return Visit(SelfPE->getSubExpr(), Other, hasBeenSubstituted); | ||
} | ||
|
||
bool VisitIntegerLiteral(const IntegerLiteral *SelfIL, const Expr *Other) { | ||
bool VisitIntegerLiteral(const IntegerLiteral *SelfIL, const Expr *Other, | ||
bool hasBeenSubstituted) { | ||
if (const auto *IntLit = | ||
dyn_cast<IntegerLiteral>(Other->IgnoreParenImpCasts())) { | ||
return SelfIL == IntLit || | ||
|
@@ -481,7 +499,8 @@ struct CompatibleCountExprVisitor | |
} | ||
|
||
bool VisitUnaryExprOrTypeTraitExpr(const UnaryExprOrTypeTraitExpr *Self, | ||
const Expr *Other) { | ||
const Expr *Other, | ||
bool hasBeenSubstituted) { | ||
// If `Self` is a `sizeof` expression, try to evaluate and compare the two | ||
// expressions as constants: | ||
if (Self->getKind() == UnaryExprOrTypeTrait::UETT_SizeOf) { | ||
|
@@ -498,17 +517,19 @@ struct CompatibleCountExprVisitor | |
return false; | ||
} | ||
|
||
bool VisitCXXThisExpr(const CXXThisExpr *SelfThis, const Expr *Other) { | ||
bool VisitCXXThisExpr(const CXXThisExpr *SelfThis, const Expr *Other, | ||
bool hasBeenSubstituted) { | ||
return isa<CXXThisExpr>(Other->IgnoreParenImpCasts()); | ||
} | ||
|
||
bool VisitDeclRefExpr(const DeclRefExpr *SelfDRE, const Expr *Other) { | ||
bool VisitDeclRefExpr(const DeclRefExpr *SelfDRE, const Expr *Other, | ||
bool hasBeenSubstituted) { | ||
const ValueDecl *SelfVD = SelfDRE->getDecl(); | ||
|
||
if (DependentValues) { | ||
if (DependentValues && !hasBeenSubstituted) { | ||
const auto It = DependentValues->find(SelfVD); | ||
if (It != DependentValues->end()) | ||
return Visit(It->second, Other); | ||
return Visit(It->second, Other, true); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not clear to me what the iteration order is, could you explain what this class does? Does it visit everything as intended in something like this? If nothing else, I think it would be good to have test cases with shared variables, and the same variable appearing multiple times in the same expression.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've added this test. Let me try to explain it with more context.
where the left-hand side represents the expected length of the formal parameter Our implementation uses a visitor to traverse the two comparing expressions e1 vs. e2 and apply the substitution when it visits a DRE of a formal parameter in e1. Naturally, it must make sure each reference to a formal parameter in e1 gets substituted exactly once, otherwise it will enter an infinite loop in the example above. This is what the bug is. The fix lets the visitor take an extra argument representing whether a sub-expression being visited is the result of substitution. If so, no substitution shall happen during the visit of the sub-expression. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, that makes sense to me! |
||
} | ||
|
||
const auto *O = Other->IgnoreParenImpCasts(); | ||
|
@@ -523,58 +544,63 @@ struct CompatibleCountExprVisitor | |
const auto *OtherME = dyn_cast<MemberExpr>(O); | ||
if (MemberBase && OtherME) { | ||
return OtherME->getMemberDecl() == SelfVD && | ||
Visit(OtherME->getBase(), MemberBase); | ||
Visit(OtherME->getBase(), MemberBase, hasBeenSubstituted); | ||
} | ||
|
||
return false; | ||
} | ||
|
||
bool VisitMemberExpr(const MemberExpr *Self, const Expr *Other) { | ||
bool VisitMemberExpr(const MemberExpr *Self, const Expr *Other, | ||
bool hasBeenSubstituted) { | ||
// Even though we don't support member expression in counted-by, actual | ||
// arguments can be member expressions. | ||
if (Self == Other) | ||
return true; | ||
if (const auto *DRE = dyn_cast<DeclRefExpr>(Other->IgnoreParenImpCasts())) | ||
return MemberBase && Self->getMemberDecl() == DRE->getDecl() && | ||
Visit(Self->getBase(), MemberBase); | ||
Visit(Self->getBase(), MemberBase, hasBeenSubstituted); | ||
if (const auto *OtherME = | ||
dyn_cast<MemberExpr>(Other->IgnoreParenImpCasts())) { | ||
return Self->getMemberDecl() == OtherME->getMemberDecl() && | ||
Visit(Self->getBase(), OtherME->getBase()); | ||
Visit(Self->getBase(), OtherME->getBase(), hasBeenSubstituted); | ||
} | ||
return false; | ||
} | ||
|
||
bool VisitUnaryOperator(const UnaryOperator *SelfUO, const Expr *Other) { | ||
bool VisitUnaryOperator(const UnaryOperator *SelfUO, const Expr *Other, | ||
bool hasBeenSubstituted) { | ||
if (SelfUO->getOpcode() != UO_Deref) | ||
return false; // We don't support any other unary operator | ||
|
||
if (const auto *OtherUO = | ||
dyn_cast<UnaryOperator>(Other->IgnoreParenImpCasts())) { | ||
if (SelfUO->getOpcode() == OtherUO->getOpcode()) | ||
return Visit(SelfUO->getSubExpr(), OtherUO->getSubExpr()); | ||
return Visit(SelfUO->getSubExpr(), OtherUO->getSubExpr(), | ||
hasBeenSubstituted); | ||
} | ||
// If `Other` is not a dereference expression, try to simplify `SelfUO`: | ||
if (const auto *SimplifiedSelf = trySimplifyDerefAddressof(SelfUO)) { | ||
return Visit(SimplifiedSelf, Other); | ||
if (const auto *SimplifiedSelf = | ||
trySimplifyDerefAddressof(SelfUO, hasBeenSubstituted)) { | ||
return Visit(SimplifiedSelf, Other, hasBeenSubstituted); | ||
} | ||
return false; | ||
} | ||
|
||
bool VisitBinaryOperator(const BinaryOperator *SelfBO, const Expr *Other) { | ||
bool VisitBinaryOperator(const BinaryOperator *SelfBO, const Expr *Other, | ||
bool hasBeenSubstituted) { | ||
const auto *OtherBO = | ||
dyn_cast<BinaryOperator>(Other->IgnoreParenImpCasts()); | ||
if (OtherBO && OtherBO->getOpcode() == SelfBO->getOpcode()) { | ||
return Visit(SelfBO->getLHS(), OtherBO->getLHS()) && | ||
Visit(SelfBO->getRHS(), OtherBO->getRHS()); | ||
return Visit(SelfBO->getLHS(), OtherBO->getLHS(), hasBeenSubstituted) && | ||
Visit(SelfBO->getRHS(), OtherBO->getRHS(), hasBeenSubstituted); | ||
} | ||
|
||
return false; | ||
} | ||
|
||
// Support any overloaded operator[] so long as it is a const method. | ||
bool VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *SelfOpCall, | ||
const Expr *Other) { | ||
const Expr *Other, bool hasBeenSubstituted) { | ||
if (SelfOpCall->getOperator() != OverloadedOperatorKind::OO_Subscript) | ||
return false; | ||
|
||
|
@@ -585,8 +611,10 @@ struct CompatibleCountExprVisitor | |
if (const auto *OtherOpCall = | ||
dyn_cast<CXXOperatorCallExpr>(Other->IgnoreParenImpCasts())) | ||
if (SelfOpCall->getOperator() == OtherOpCall->getOperator()) { | ||
return Visit(SelfOpCall->getArg(0), OtherOpCall->getArg(0)) && | ||
Visit(SelfOpCall->getArg(1), OtherOpCall->getArg(1)); | ||
return Visit(SelfOpCall->getArg(0), OtherOpCall->getArg(0), | ||
hasBeenSubstituted) && | ||
Visit(SelfOpCall->getArg(1), OtherOpCall->getArg(1), | ||
hasBeenSubstituted); | ||
} | ||
return false; | ||
} | ||
|
@@ -595,17 +623,17 @@ struct CompatibleCountExprVisitor | |
// considered unsafe, they can be safely used on constant arrays with | ||
// known-safe literal indexes. | ||
bool VisitArraySubscriptExpr(const ArraySubscriptExpr *SelfAS, | ||
const Expr *Other) { | ||
const Expr *Other, bool hasBeenSubstituted) { | ||
if (const auto *OtherAS = | ||
dyn_cast<ArraySubscriptExpr>(Other->IgnoreParenImpCasts())) | ||
return Visit(SelfAS->getLHS(), OtherAS->getLHS()) && | ||
Visit(SelfAS->getRHS(), OtherAS->getRHS()); | ||
return Visit(SelfAS->getLHS(), OtherAS->getLHS(), hasBeenSubstituted) && | ||
Visit(SelfAS->getRHS(), OtherAS->getRHS(), hasBeenSubstituted); | ||
return false; | ||
} | ||
|
||
// Support non-static member call: | ||
bool VisitCXXMemberCallExpr(const CXXMemberCallExpr *SelfCall, | ||
const Expr *Other) { | ||
const Expr *Other, bool hasBeenSubstituted) { | ||
const CXXMethodDecl *MD = SelfCall->getMethodDecl(); | ||
|
||
// The callee member function must be a const function with no parameter: | ||
|
@@ -614,7 +642,8 @@ struct CompatibleCountExprVisitor | |
dyn_cast<CXXMemberCallExpr>(Other->IgnoreParenImpCasts())) { | ||
return OtherCall->getMethodDecl() == MD && | ||
Visit(SelfCall->getImplicitObjectArgument(), | ||
OtherCall->getImplicitObjectArgument()); | ||
OtherCall->getImplicitObjectArgument(), | ||
hasBeenSubstituted); | ||
} | ||
} | ||
return false; | ||
|
@@ -660,7 +689,7 @@ bool isCompatibleWithCountExpr(const Expr *E, const Expr *ExpectedCountExpr, | |
const DependentValuesTy *DependentValues, | ||
ASTContext &Ctx) { | ||
CompatibleCountExprVisitor Visitor(MemberBase, DependentValues, Ctx); | ||
return Visitor.Visit(ExpectedCountExpr, E); | ||
return Visitor.Visit(ExpectedCountExpr, E, /* hasBeenSubstituted*/ false); | ||
} | ||
|
||
// Returns if a pair of expressions contain method calls to .data()/.c_str() and | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't it be simpler to keep this flag as a member variable and then use
llvm::SaveAndRestore
to set the flag totrue
for recursive calls? In this case, you wouldn't have to propagate it in each call.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, using a global flag results in much less code change.
Adding a parameter to those visit methods is my personal preference because I feel it naturally indicates that the flag only affects sub-expressions during the visit. While using a global flag requires the programmer to not forget to manage the flag whenever they attempt to make a substitution.
Consider this is a somewhat urgent fix, let's merge it as is for now? If this visitor grows more complicated later, we re-evaluate which approach is better.