Skip to content

Commit

Permalink
[Sema/Attribute] Check for noderef attribute
Browse files Browse the repository at this point in the history
This patch adds the noderef attribute in clang and checks for dereferences of
types that have this attribute. This attribute is currently used by sparse and
would like to be ported to clang.

Differential Revision: https://reviews.llvm.org/D49511

llvm-svn: 348442
  • Loading branch information
PiJoules committed Dec 6, 2018
1 parent e13d099 commit ad7ac96
Show file tree
Hide file tree
Showing 14 changed files with 628 additions and 7 deletions.
5 changes: 5 additions & 0 deletions clang/include/clang/Basic/Attr.td
Expand Up @@ -1822,6 +1822,11 @@ def Regparm : TypeAttr {
let ASTNode = 0;
}

def NoDeref : TypeAttr {
let Spellings = [Clang<"noderef">];
let Documentation = [NoDerefDocs];
}

def ReqdWorkGroupSize : InheritableAttr {
// Does not have a [[]] spelling because it is an OpenCL-related attribute.
let Spellings = [GNU<"reqd_work_group_size">];
Expand Down
57 changes: 57 additions & 0 deletions clang/include/clang/Basic/AttrDocs.td
Expand Up @@ -3593,6 +3593,63 @@ corresponding line within the inlined callee.
}];
}

def NoDerefDocs : Documentation {
let Category = DocCatType;
let Content = [{
The ``noderef`` attribute causes clang to diagnose dereferences of annotated pointer types.
This is ideally used with pointers that point to special memory which cannot be read
from or written to, but allowing for the pointer to be used in pointer arithmetic.
The following are examples of valid expressions where dereferences are diagnosed:

.. code-block:: c

int __attribute__((noderef)) *p;
int x = *p; // warning

int __attribute__((noderef)) **p2;
x = **p2; // warning

int * __attribute__((noderef)) *p3;
p = *p3; // warning

struct S {
int a;
};
struct S __attribute__((noderef)) *s;
x = s->a; // warning
x = (*s).a; // warning

Not all dereferences may diagnose a warning if the value directed by the pointer may not be
accessed. The following are examples of valid expressions where may not be diagnosed:

.. code-block:: c

int *q;
int __attribute__((noderef)) *p;
q = &*p;
q = *&p;

struct S {
int a;
};
struct S __attribute__((noderef)) *s;
p = &s->a;
p = &(*s).a;

``noderef`` is currently only supported for pointers and arrays and not usable for
references or Objective-C object pointers.

.. code-block: c++

int x = 2;
int __attribute__((noderef)) &y = x; // warning: 'noderef' can only be used on an array or pointer type

.. code-block: objc

id __attribute__((noderef)) obj = [NSObject new]; // warning: 'noderef' can only be used on an array or pointer type
}];
}

def ReinitializesDocs : Documentation {
let Category = DocCatFunction;
let Content = [{
Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/Basic/DiagnosticGroups.td
Expand Up @@ -1039,3 +1039,5 @@ def ExperimentalISel : DiagGroup<"experimental-isel">;
// A warning group specifically for warnings related to function
// multiversioning.
def FunctionMultiVersioning : DiagGroup<"function-multiversion">;

def NoDeref : DiagGroup<"noderef">;
9 changes: 9 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Expand Up @@ -9548,4 +9548,13 @@ def err_std_compare_type_not_supported : Error<
"member '%2' is missing|"
"the type is not trivially copyable|"
"the type does not have the expected form}1">;

def warn_dereference_of_noderef_type : Warning<
"dereferencing %0; was declared with a 'noderef' type">, InGroup<NoDeref>;
def warn_dereference_of_noderef_type_no_decl : Warning<
"dereferencing expression marked as 'noderef'">, InGroup<NoDeref>;
def warn_noderef_on_non_pointer_or_array : Warning<
"'noderef' can only be used on an array or pointer type">, InGroup<IgnoredAttributes>;
def warn_noderef_to_dereferenceable_pointer : Warning<
"casting to dereferenceable pointer removes 'noderef' attribute">, InGroup<NoDeref>;
} // end of sema component.
16 changes: 16 additions & 0 deletions clang/include/clang/Sema/Sema.h
Expand Up @@ -999,6 +999,8 @@ class Sema {
/// expressions for which we have deferred checking the destructor.
SmallVector<CXXBindTemporaryExpr *, 8> DelayedDecltypeBinds;

llvm::SmallPtrSet<const Expr *, 8> PossibleDerefs;

/// \brief Describes whether we are in an expression constext which we have
/// to handle differently.
enum ExpressionKind {
Expand Down Expand Up @@ -1032,6 +1034,9 @@ class Sema {
/// A stack of expression evaluation contexts.
SmallVector<ExpressionEvaluationContextRecord, 8> ExprEvalContexts;

/// Emit a warning for all pending noderef expressions that we recorded.
void WarnOnPendingNoDerefs(ExpressionEvaluationContextRecord &Rec);

/// Compute the mangling number context for a lambda expression or
/// block literal.
///
Expand Down Expand Up @@ -1544,6 +1549,17 @@ class Sema {
};

private:
/// Methods for marking which expressions involve dereferencing a pointer
/// marked with the 'noderef' attribute. Expressions are checked bottom up as
/// they are parsed, meaning that a noderef pointer may not be accessed. For
/// example, in `&*p` where `p` is a noderef pointer, we will first parse the
/// `*p`, but need to check that `address of` is called on it. This requires
/// keeping a container of all pending expressions and checking if the address
/// of them are eventually taken.
void CheckSubscriptAccessOfNoDeref(const ArraySubscriptExpr *E);
void CheckAddressOfNoDeref(const Expr *E);
void CheckMemberAccessOfNoDeref(const MemberExpr *E);

bool RequireCompleteTypeImpl(SourceLocation Loc, QualType T,
TypeDiagnoser *Diagnoser);

Expand Down
3 changes: 3 additions & 0 deletions clang/lib/AST/TypePrinter.cpp
Expand Up @@ -1508,6 +1508,9 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
case attr::PreserveAll:
OS << "preserve_all";
break;
case attr::NoDeref:
OS << "noderef";
break;
}
OS << "))";
}
Expand Down
117 changes: 116 additions & 1 deletion clang/lib/Sema/SemaExpr.cpp
Expand Up @@ -4288,7 +4288,57 @@ Sema::ActOnArraySubscriptExpr(Scope *S, Expr *base, SourceLocation lbLoc,
return CreateOverloadedArraySubscriptExpr(lbLoc, rbLoc, base, idx);
}

return CreateBuiltinArraySubscriptExpr(base, lbLoc, idx, rbLoc);
ExprResult Res = CreateBuiltinArraySubscriptExpr(base, lbLoc, idx, rbLoc);

if (!Res.isInvalid() && isa<ArraySubscriptExpr>(Res.get()))
CheckSubscriptAccessOfNoDeref(cast<ArraySubscriptExpr>(Res.get()));

return Res;
}

void Sema::CheckAddressOfNoDeref(const Expr *E) {
ExpressionEvaluationContextRecord &LastRecord = ExprEvalContexts.back();
const Expr *StrippedExpr = E->IgnoreParenImpCasts();

// For expressions like `&(*s).b`, the base is recorded and what should be
// checked.
const MemberExpr *Member = nullptr;
while ((Member = dyn_cast<MemberExpr>(StrippedExpr)) && !Member->isArrow())
StrippedExpr = Member->getBase()->IgnoreParenImpCasts();

LastRecord.PossibleDerefs.erase(StrippedExpr);
}

void Sema::CheckSubscriptAccessOfNoDeref(const ArraySubscriptExpr *E) {
QualType ResultTy = E->getType();
ExpressionEvaluationContextRecord &LastRecord = ExprEvalContexts.back();

// Bail if the element is an array since it is not memory access.
if (isa<ArrayType>(ResultTy))
return;

if (ResultTy->hasAttr(attr::NoDeref)) {
LastRecord.PossibleDerefs.insert(E);
return;
}

// Check if the base type is a pointer to a member access of a struct
// marked with noderef.
const Expr *Base = E->getBase();
QualType BaseTy = Base->getType();
if (!(isa<ArrayType>(BaseTy) || isa<PointerType>(BaseTy)))
// Not a pointer access
return;

const MemberExpr *Member = nullptr;
while ((Member = dyn_cast<MemberExpr>(Base->IgnoreParenCasts())) &&
Member->isArrow())
Base = Member->getBase();

if (const auto *Ptr = dyn_cast<PointerType>(Base->getType())) {
if (Ptr->getPointeeType()->hasAttr(attr::NoDeref))
LastRecord.PossibleDerefs.insert(E);
}
}

ExprResult Sema::ActOnOMPArraySectionExpr(Expr *Base, SourceLocation LBLoc,
Expand Down Expand Up @@ -8157,6 +8207,17 @@ Sema::CheckSingleAssignmentConstraints(QualType LHSType, ExprResult &CallerRHS,
ExprResult LocalRHS = CallerRHS;
ExprResult &RHS = ConvertRHS ? CallerRHS : LocalRHS;

if (const auto *LHSPtrType = LHSType->getAs<PointerType>()) {
if (const auto *RHSPtrType = RHS.get()->getType()->getAs<PointerType>()) {
if (RHSPtrType->getPointeeType()->hasAttr(attr::NoDeref) &&
!LHSPtrType->getPointeeType()->hasAttr(attr::NoDeref)) {
Diag(RHS.get()->getExprLoc(),
diag::warn_noderef_to_dereferenceable_pointer)
<< RHS.get()->getSourceRange();
}
}
}

if (getLangOpts().CPlusPlus) {
if (!LHSType->isRecordType() && !LHSType->isAtomicType()) {
// C++ 5.17p3: If the left operand is not of class type, the
Expand Down Expand Up @@ -8277,6 +8338,7 @@ Sema::CheckSingleAssignmentConstraints(QualType LHSType, ExprResult &CallerRHS,
if (ConvertRHS)
RHS = ImpCastExprToType(E, Ty, Kind);
}

return result;
}

Expand Down Expand Up @@ -12825,6 +12887,7 @@ ExprResult Sema::CreateBuiltinUnaryOp(SourceLocation OpLoc,
break;
case UO_AddrOf:
resultType = CheckAddressOfOperand(Input, OpLoc);
CheckAddressOfNoDeref(InputExpr);
RecordModifiableNonNullParam(*this, InputExpr);
break;
case UO_Deref: {
Expand Down Expand Up @@ -12989,6 +13052,11 @@ ExprResult Sema::CreateBuiltinUnaryOp(SourceLocation OpLoc,

auto *UO = new (Context)
UnaryOperator(Input.get(), Opc, resultType, VK, OK, OpLoc, CanOverflow);

if (Opc == UO_Deref && UO->getType()->hasAttr(attr::NoDeref) &&
!isa<ArrayType>(UO->getType().getDesugaredType(Context)))
ExprEvalContexts.back().PossibleDerefs.insert(UO);

// Convert the result back to a half vector.
if (ConvertHalfVec)
return convertVector(UO, Context.HalfTy, *this);
Expand Down Expand Up @@ -14355,6 +14423,51 @@ Sema::PushExpressionEvaluationContext(
PushExpressionEvaluationContext(NewContext, ClosureContextDecl, ExprContext);
}

namespace {

const DeclRefExpr *CheckPossibleDeref(Sema &S, const Expr *PossibleDeref) {
PossibleDeref = PossibleDeref->IgnoreParenImpCasts();
if (const auto *E = dyn_cast<UnaryOperator>(PossibleDeref)) {
if (E->getOpcode() == UO_Deref)
return CheckPossibleDeref(S, E->getSubExpr());
} else if (const auto *E = dyn_cast<ArraySubscriptExpr>(PossibleDeref)) {
return CheckPossibleDeref(S, E->getBase());
} else if (const auto *E = dyn_cast<MemberExpr>(PossibleDeref)) {
return CheckPossibleDeref(S, E->getBase());
} else if (const auto E = dyn_cast<DeclRefExpr>(PossibleDeref)) {
QualType Inner;
QualType Ty = E->getType();
if (const auto *Ptr = Ty->getAs<PointerType>())
Inner = Ptr->getPointeeType();
else if (const auto *Arr = S.Context.getAsArrayType(Ty))
Inner = Arr->getElementType();
else
return nullptr;

if (Inner->hasAttr(attr::NoDeref))
return E;
}
return nullptr;
}

} // namespace

void Sema::WarnOnPendingNoDerefs(ExpressionEvaluationContextRecord &Rec) {
for (const Expr *E : Rec.PossibleDerefs) {
const DeclRefExpr *DeclRef = CheckPossibleDeref(*this, E);
if (DeclRef) {
const ValueDecl *Decl = DeclRef->getDecl();
Diag(E->getExprLoc(), diag::warn_dereference_of_noderef_type)
<< Decl->getName() << E->getSourceRange();
Diag(Decl->getLocation(), diag::note_previous_decl) << Decl->getName();
} else {
Diag(E->getExprLoc(), diag::warn_dereference_of_noderef_type_no_decl)
<< E->getSourceRange();
}
}
Rec.PossibleDerefs.clear();
}

void Sema::PopExpressionEvaluationContext() {
ExpressionEvaluationContextRecord& Rec = ExprEvalContexts.back();
unsigned NumTypos = Rec.NumTypos;
Expand Down Expand Up @@ -14394,6 +14507,8 @@ void Sema::PopExpressionEvaluationContext() {
}
}

WarnOnPendingNoDerefs(Rec);

// When are coming out of an unevaluated context, clear out any
// temporaries that we may have created as part of the evaluation of
// the expression in that context: they aren't relevant because they
Expand Down
28 changes: 25 additions & 3 deletions clang/lib/Sema/SemaExprMember.cpp
Expand Up @@ -1708,9 +1708,31 @@ ExprResult Sema::ActOnMemberAccessExpr(Scope *S, Expr *Base,
}

ActOnMemberAccessExtraArgs ExtraArgs = {S, Id, ObjCImpDecl};
return BuildMemberReferenceExpr(Base, Base->getType(), OpLoc, IsArrow, SS,
TemplateKWLoc, FirstQualifierInScope,
NameInfo, TemplateArgs, S, &ExtraArgs);
ExprResult Res = BuildMemberReferenceExpr(
Base, Base->getType(), OpLoc, IsArrow, SS, TemplateKWLoc,
FirstQualifierInScope, NameInfo, TemplateArgs, S, &ExtraArgs);

if (!Res.isInvalid() && isa<MemberExpr>(Res.get()))
CheckMemberAccessOfNoDeref(cast<MemberExpr>(Res.get()));

return Res;
}

void Sema::CheckMemberAccessOfNoDeref(const MemberExpr *E) {
QualType ResultTy = E->getType();

// Do not warn on member accesses to arrays since this returns an array
// lvalue and does not actually dereference memory.
if (isa<ArrayType>(ResultTy))
return;

if (E->isArrow()) {
if (const auto *Ptr = dyn_cast<PointerType>(
E->getBase()->getType().getDesugaredType(Context))) {
if (Ptr->getPointeeType()->hasAttr(attr::NoDeref))
ExprEvalContexts.back().PossibleDerefs.insert(E);
}
}
}

ExprResult
Expand Down
13 changes: 13 additions & 0 deletions clang/lib/Sema/SemaInit.cpp
Expand Up @@ -7687,6 +7687,18 @@ ExprResult InitializationSequence::Perform(Sema &S,

case SK_ConversionSequence:
case SK_ConversionSequenceNoNarrowing: {
if (const auto *FromPtrType =
CurInit.get()->getType()->getAs<PointerType>()) {
if (const auto *ToPtrType = Step->Type->getAs<PointerType>()) {
if (FromPtrType->getPointeeType()->hasAttr(attr::NoDeref) &&
!ToPtrType->getPointeeType()->hasAttr(attr::NoDeref)) {
S.Diag(CurInit.get()->getExprLoc(),
diag::warn_noderef_to_dereferenceable_pointer)
<< CurInit.get()->getSourceRange();
}
}
}

Sema::CheckedConversionKind CCK
= Kind.isCStyleCast()? Sema::CCK_CStyleCast
: Kind.isFunctionalCast()? Sema::CCK_FunctionalCast
Expand Down Expand Up @@ -7853,6 +7865,7 @@ ExprResult InitializationSequence::Perform(Sema &S,

case SK_CAssignment: {
QualType SourceType = CurInit.get()->getType();

// Save off the initial CurInit in case we need to emit a diagnostic
ExprResult InitialCurInit = CurInit;
ExprResult Result = CurInit;
Expand Down

0 comments on commit ad7ac96

Please sign in to comment.