-
Notifications
You must be signed in to change notification settings - Fork 14.6k
Thread Safety Analysis: Very basic capability alias-analysis #142955
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
base: main
Are you sure you want to change the base?
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 |
---|---|---|
|
@@ -19,6 +19,7 @@ | |
#include "clang/AST/Expr.h" | ||
#include "clang/AST/ExprCXX.h" | ||
#include "clang/AST/OperationKinds.h" | ||
#include "clang/AST/RecursiveASTVisitor.h" | ||
#include "clang/AST/Stmt.h" | ||
#include "clang/AST/Type.h" | ||
#include "clang/Analysis/Analyses/ThreadSafetyTIL.h" | ||
|
@@ -241,7 +242,21 @@ CapabilityExpr SExprBuilder::translateAttrExpr(const Expr *AttrExp, | |
return CapabilityExpr(E, AttrExp->getType(), Neg); | ||
} | ||
|
||
til::LiteralPtr *SExprBuilder::createVariable(const VarDecl *VD) { | ||
til::SExpr *SExprBuilder::translateVarDecl(const VarDecl *VD, | ||
CallingContext *Ctx) { | ||
assert(VD); | ||
// Substitute local pointer variables with their initializers if they are | ||
// explicitly const or never reassigned. | ||
QualType Ty = VD->getType(); | ||
if (Ty->isPointerType() && VD->hasInit() && !isVariableReassigned(VD)) { | ||
const Expr *Init = VD->getInit()->IgnoreParenImpCasts(); | ||
// Check for self-initialization to prevent infinite recursion. | ||
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. First, is there actually a common pattern that involves self-referential initialization? Second, this doesn't seem like a comprehensive check to me. My approach would be to verify that the init expression is not self-referential within the analysis pass within isVariableReassigned, and disallow the variable inside that function if it is. (That will also automatically cache the result of the check.) |
||
if (const auto *InitDRE = dyn_cast<DeclRefExpr>(Init)) { | ||
if (InitDRE->getDecl()->getCanonicalDecl() == VD->getCanonicalDecl()) | ||
return new (Arena) til::LiteralPtr(VD); | ||
} | ||
return translate(Init, Ctx); | ||
} | ||
return new (Arena) til::LiteralPtr(VD); | ||
} | ||
|
||
|
@@ -353,6 +368,9 @@ til::SExpr *SExprBuilder::translateDeclRefExpr(const DeclRefExpr *DRE, | |
: cast<ObjCMethodDecl>(D)->getCanonicalDecl()->getParamDecl(I); | ||
} | ||
|
||
if (const auto *VarD = dyn_cast<VarDecl>(VD)) | ||
return translateVarDecl(VarD, Ctx); | ||
|
||
// For non-local variables, treat it as a reference to a named object. | ||
return new (Arena) til::LiteralPtr(VD); | ||
} | ||
|
@@ -1012,6 +1030,107 @@ void SExprBuilder::exitCFG(const CFGBlock *Last) { | |
IncompleteArgs.clear(); | ||
} | ||
|
||
bool SExprBuilder::isVariableReassigned(const VarDecl *VD) { | ||
// Note: The search is performed lazily per-variable and result is cached. An | ||
// alternative would have been to eagerly create a set of all reassigned | ||
// variables, but that would consume significantly more memory. The number of | ||
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. llvm::DenseMap is initialized to a minimum of size 64 by default, so you won't use more memory unless there are more than 64 reassigned local pointer variables. Recursive AST traversals are expensive; I would do it lazily, but only once, and build the set of all pointer variables that are reassigned. If you do it in a single pass, you can also keep only the set of local pointer variables that are not reassigned, and which have valid init expressions. |
||
// variables needing the reassignment check in a single function is expected | ||
// to be small. Also see createVariable(). | ||
struct ReassignmentFinder : RecursiveASTVisitor<ReassignmentFinder> { | ||
explicit ReassignmentFinder(const VarDecl *VD) : QueryVD(VD) { | ||
assert(QueryVD->getCanonicalDecl() == QueryVD); | ||
} | ||
|
||
bool VisitBinaryOperator(BinaryOperator *BO) { | ||
if (!BO->isAssignmentOp()) | ||
return true; | ||
auto *DRE = dyn_cast<DeclRefExpr>(BO->getLHS()->IgnoreParenImpCasts()); | ||
if (!DRE) | ||
return true; | ||
auto *AssignedVD = dyn_cast<VarDecl>(DRE->getDecl()); | ||
if (!AssignedVD) | ||
return true; | ||
// Don't count the initialization itself as a reassignment. | ||
if (AssignedVD->hasInit() && | ||
AssignedVD->getInit()->getBeginLoc() == BO->getBeginLoc()) | ||
return true; | ||
// If query variable appears as LHS of assignment. | ||
if (QueryVD == AssignedVD->getCanonicalDecl()) { | ||
FoundReassignment = true; | ||
return false; // stop | ||
} | ||
return true; | ||
} | ||
|
||
bool VisitCallExpr(CallExpr *CE) { | ||
const FunctionDecl *FD = CE->getDirectCallee(); | ||
if (!FD) | ||
return true; | ||
|
||
for (unsigned Idx = 0; Idx < CE->getNumArgs(); ++Idx) { | ||
if (FoundReassignment) | ||
return false; | ||
if (Idx >= FD->getNumParams()) | ||
break; | ||
|
||
const Expr *Arg = CE->getArg(Idx)->IgnoreParenImpCasts(); | ||
const ParmVarDecl *PVD = FD->getParamDecl(Idx); | ||
QualType ParamType = PVD->getType(); | ||
|
||
// Check if the argument is a reference to our QueryVD. | ||
if (const auto *DRE = dyn_cast<DeclRefExpr>(Arg)) { | ||
if (DRE->getDecl()->getCanonicalDecl() == QueryVD) { | ||
// Potential reassignment if passed by non-const reference. | ||
if (ParamType->isReferenceType() && | ||
!ParamType->getPointeeType().isConstQualified()) { | ||
FoundReassignment = true; | ||
} | ||
} | ||
} | ||
// Check if we are taking the address of the variable. | ||
if (const auto *UO = dyn_cast<UnaryOperator>(Arg); | ||
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 would do this within a VisitUnaryOperator instead. IMO, the conservative rule should be that if a local variable is single-assignment, then you can't take the address of that variable at all. Once you are allowed to take the address, then are other potential analysis breaking paths, e.g. Foo *ptr = f; |
||
UO && UO->getOpcode() == UO_AddrOf) { | ||
const Expr *SE = UO->getSubExpr()->IgnoreParenImpCasts(); | ||
if (const auto *DRE = dyn_cast<DeclRefExpr>(SE)) { | ||
if (DRE->getDecl()->getCanonicalDecl() == QueryVD) { | ||
// Potential reassignment if passed by non-const pointer. | ||
if (ParamType->isPointerType() && | ||
!ParamType->getPointeeType().isConstQualified()) { | ||
FoundReassignment = true; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
return !FoundReassignment; | ||
} | ||
|
||
const VarDecl *QueryVD; | ||
bool FoundReassignment = false; | ||
}; | ||
|
||
if (VD->getType().isConstQualified()) | ||
return false; // Assume UB-freedom. | ||
melver marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (!VD->isLocalVarDecl()) | ||
return true; // Not a local variable (assume reassigned). | ||
auto *FD = dyn_cast<FunctionDecl>(VD->getDeclContext()); | ||
if (!FD) | ||
return true; // Assume reassigned. | ||
|
||
// Try to look up in cache; use the canonical declaration to ensure consistent | ||
// lookup in the cache. | ||
VD = VD->getCanonicalDecl(); | ||
auto It = LocalVariableReassigned.find(VD); | ||
if (It != LocalVariableReassigned.end()) | ||
return It->second; | ||
|
||
ReassignmentFinder Visitor(VD); | ||
// const_cast ok: FunctionDecl not modified. | ||
Visitor.TraverseDecl(const_cast<FunctionDecl *>(FD)); | ||
return LocalVariableReassigned[VD] = Visitor.FoundReassignment; | ||
} | ||
|
||
#ifndef NDEBUG | ||
namespace { | ||
|
||
|
Uh oh!
There was an error while loading. Please reload this page.