Skip to content

Commit 025c5d4

Browse files
v01dxyzresistor
v01dxyz
authored andcommitted
cheri_compartment: Warn if return type is void or return value is unused
1 parent 7f8b05c commit 025c5d4

File tree

12 files changed

+234
-16
lines changed

12 files changed

+234
-16
lines changed

clang/include/clang/AST/TypeLoc.h

+4
Original file line numberDiff line numberDiff line change
@@ -883,6 +883,10 @@ class AttributedTypeLoc : public ConcreteTypeLoc<UnqualTypeLoc,
883883
return getInnerTypeLoc();
884884
}
885885

886+
TypeLoc getEquivalentTypeLoc() const {
887+
return TypeLoc(getTypePtr()->getEquivalentType(), getNonLocalData());
888+
}
889+
886890
/// The type attribute.
887891
const Attr *getAttr() const {
888892
return getLocalData()->TypeAttr;

clang/include/clang/Basic/DiagnosticGroups.td

+2-1
Original file line numberDiff line numberDiff line change
@@ -158,10 +158,11 @@ def CHERIPrototypesStrict: DiagGroup<"cheri-prototypes-strict">;
158158
// Remarks about setting/not setting subobject bounds
159159
def CheriSubobjectBoundsSuspicous : DiagGroup<"cheri-subobject-bounds-suspicious">;
160160
def CheriSubobjectBoundsRemarks : DiagGroup<"cheri-subobject-bounds">;
161+
def CHERICompartmentReturnVoid : DiagGroup<"cheri-compartment-return-void">;
161162

162163
def CheriAll : DiagGroup<"cheri",
163164
[CHERICaps, CHERIBitwiseOps, CHERIMisaligned, CHERIImplicitConversion, CheriSubobjectBoundsSuspicous,
164-
CHERIProvenance, CHERIImplicitConversionSign, CHERIPrototypes]>;
165+
CHERIProvenance, CHERIImplicitConversionSign, CHERIPrototypes, CHERICompartmentReturnVoid]>;
165166
// CHERI warnings that are too noisy to turn on by default
166167
def CHERICapabilityToIntegerCast : DiagGroup<"capability-to-integer-cast">;
167168
def CheriPedantic : DiagGroup<"cheri-pedantic", [CHERICapabilityToIntegerCast, CHERIPrototypesStrict, CHERIProvenancePedantic]>;

clang/include/clang/Basic/DiagnosticSemaKinds.td

+9
Original file line numberDiff line numberDiff line change
@@ -2336,6 +2336,15 @@ def note_in_reference_temporary_list_initializer : Note<
23362336
"list-initialize this reference">;
23372337
def note_var_fixit_add_initialization : Note<
23382338
"initialize the variable %0 to silence this warning">;
2339+
def warn_cheri_compartment_void_return_type : Warning <
2340+
"void return on a cross-compartment call makes it impossible for callers to detect failure">,
2341+
InGroup<CHERICompartmentReturnVoid>,
2342+
DefaultIgnore;
2343+
def note_cheri_compartment_void_return_type : Note<"replace void return type with int">;
2344+
def warn_cheri_compartment_return_void_or_falloff : Warning <
2345+
"cross-compartement calls that always succeed should return 0 instead">,
2346+
InGroup<CHERICompartmentReturnVoid>,
2347+
DefaultIgnore;
23392348
def note_uninit_fixit_remove_cond : Note<
23402349
"remove the %select{'%1' if its condition|condition if it}0 "
23412350
"is always %select{false|true}2">;

clang/include/clang/Sema/Sema.h

+8
Original file line numberDiff line numberDiff line change
@@ -4717,8 +4717,16 @@ class Sema final {
47174717
bool IgnoreTypeAttributes;
47184718
};
47194719

4720+
enum DeclAttributeLocation {
4721+
DAL_Unspecified,
4722+
DAL_DeclSpec,
4723+
DAL_DeclChunk,
4724+
DAL_Decl,
4725+
};
4726+
47204727
void ProcessDeclAttributeList(Scope *S, Decl *D,
47214728
const ParsedAttributesView &AttrList,
4729+
DeclAttributeLocation DAL = DAL_Unspecified,
47224730
const ProcessDeclAttributeOptions &Options =
47234731
ProcessDeclAttributeOptions());
47244732
bool ProcessAccessDeclAttributeList(AccessSpecDecl *ASDecl,

clang/lib/AST/Decl.cpp

+23-2
Original file line numberDiff line numberDiff line change
@@ -3745,8 +3745,29 @@ bool FunctionDecl::doesDeclarationForceExternallyVisibleDefinition() const {
37453745

37463746
FunctionTypeLoc FunctionDecl::getFunctionTypeLoc() const {
37473747
const TypeSourceInfo *TSI = getTypeSourceInfo();
3748-
return TSI ? TSI->getTypeLoc().IgnoreParens().getAs<FunctionTypeLoc>()
3749-
: FunctionTypeLoc();
3748+
// CHERIOT: The following code diverges from upstream. The previous
3749+
// code returned a null FunctionTypeLoc when the function was
3750+
// annotated (impacting getReturnTypeSourceRange too). It is
3751+
// necessary to provide hints to replace the return type of a
3752+
// compartment entry point which returns void instead of int.
3753+
//
3754+
// https://github.com/llvm/llvm-project/pull/118420
3755+
if (!TSI)
3756+
return FunctionTypeLoc();
3757+
3758+
TypeLoc TL = TSI->getTypeLoc();
3759+
FunctionTypeLoc FTL;
3760+
3761+
while (!(FTL = TL.getAs<FunctionTypeLoc>())) {
3762+
if (auto PTL = TL.getAs<ParenTypeLoc>())
3763+
TL = PTL.getInnerLoc();
3764+
else if (auto ATL = TL.getAs<AttributedTypeLoc>())
3765+
TL = ATL.getEquivalentTypeLoc();
3766+
else
3767+
break;
3768+
}
3769+
3770+
return FTL;
37503771
}
37513772

37523773
SourceRange FunctionDecl::getReturnTypeSourceRange() const {

clang/lib/Sema/AnalysisBasedWarnings.cpp

+24-4
Original file line numberDiff line numberDiff line change
@@ -621,7 +621,7 @@ struct CheckFallThroughDiagnostics {
621621
}
622622

623623
bool checkDiagnostics(DiagnosticsEngine &D, bool ReturnsVoid,
624-
bool HasNoReturn) const {
624+
bool HasNoReturn, bool HasCHERICompartmentName) const {
625625
if (funMode == Function) {
626626
return (ReturnsVoid ||
627627
D.isIgnored(diag::warn_maybe_falloff_nonvoid_function,
@@ -630,7 +630,8 @@ struct CheckFallThroughDiagnostics {
630630
D.isIgnored(diag::warn_noreturn_function_has_return_expr,
631631
FuncLoc)) &&
632632
(!ReturnsVoid ||
633-
D.isIgnored(diag::warn_suggest_noreturn_block, FuncLoc));
633+
D.isIgnored(diag::warn_suggest_noreturn_block, FuncLoc)) &&
634+
!HasCHERICompartmentName;
634635
}
635636
if (funMode == Coroutine) {
636637
return (ReturnsVoid ||
@@ -658,6 +659,7 @@ static void CheckFallThroughForBody(Sema &S, const Decl *D, const Stmt *Body,
658659

659660
bool ReturnsVoid = false;
660661
bool HasNoReturn = false;
662+
bool HasCHERICompartmentName = false;
661663
bool IsCoroutine = FSI->isCoroutine();
662664

663665
if (const auto *FD = dyn_cast<FunctionDecl>(D)) {
@@ -666,6 +668,7 @@ static void CheckFallThroughForBody(Sema &S, const Decl *D, const Stmt *Body,
666668
else
667669
ReturnsVoid = FD->getReturnType()->isVoidType();
668670
HasNoReturn = FD->isNoReturn();
671+
HasCHERICompartmentName = FD->hasAttr<CHERICompartmentNameAttr>();
669672
}
670673
else if (const auto *MD = dyn_cast<ObjCMethodDecl>(D)) {
671674
ReturnsVoid = MD->getReturnType()->isVoidType();
@@ -684,8 +687,9 @@ static void CheckFallThroughForBody(Sema &S, const Decl *D, const Stmt *Body,
684687
DiagnosticsEngine &Diags = S.getDiagnostics();
685688

686689
// Short circuit for compilation speed.
687-
if (CD.checkDiagnostics(Diags, ReturnsVoid, HasNoReturn))
688-
return;
690+
if (CD.checkDiagnostics(Diags, ReturnsVoid, HasNoReturn,
691+
HasCHERICompartmentName))
692+
return;
689693
SourceLocation LBrace = Body->getBeginLoc(), RBrace = Body->getEndLoc();
690694
auto EmitDiag = [&](SourceLocation Loc, unsigned DiagID) {
691695
if (IsCoroutine)
@@ -708,12 +712,28 @@ static void CheckFallThroughForBody(Sema &S, const Decl *D, const Stmt *Body,
708712
EmitDiag(RBrace, CD.diag_MaybeFallThrough_HasNoReturn);
709713
else if (!ReturnsVoid)
710714
EmitDiag(RBrace, CD.diag_MaybeFallThrough_ReturnsNonVoid);
715+
716+
if (HasCHERICompartmentName) {
717+
if (!ReturnsVoid)
718+
S.Diag(RBrace, diag::warn_cheri_compartment_return_void_or_falloff);
719+
else
720+
S.Diag(RBrace, diag::warn_cheri_compartment_return_void_or_falloff)
721+
<< FixItHint::CreateInsertion(RBrace, "return 0;");
722+
}
711723
break;
712724
case AlwaysFallThrough:
713725
if (HasNoReturn)
714726
EmitDiag(RBrace, CD.diag_AlwaysFallThrough_HasNoReturn);
715727
else if (!ReturnsVoid)
716728
EmitDiag(RBrace, CD.diag_AlwaysFallThrough_ReturnsNonVoid);
729+
730+
if (HasCHERICompartmentName) {
731+
if (!ReturnsVoid)
732+
S.Diag(RBrace, diag::warn_cheri_compartment_return_void_or_falloff);
733+
else
734+
S.Diag(RBrace, diag::warn_cheri_compartment_return_void_or_falloff)
735+
<< FixItHint::CreateInsertion(RBrace, "return 0;");
736+
}
717737
break;
718738
case NeverFallThroughOrReturn:
719739
if (ReturnsVoid && !HasNoReturn && CD.diag_NeverFallThroughOrReturn) {

clang/lib/Sema/SemaDeclAttr.cpp

+55-5
Original file line numberDiff line numberDiff line change
@@ -2388,11 +2388,57 @@ static void handleCHERIMethodSuffix(Sema &S, Decl *D, const ParsedAttr &Attr) {
23882388
D->addAttr(::new (S.Context) CHERIMethodSuffixAttr(S.Context, Attr, Str));
23892389
}
23902390

2391-
static void handleCHERICompartmentName(Sema &S, Decl *D, const ParsedAttr &Attr) {
2391+
static void handleCHERICompartmentName(Sema &S, Decl *D, const ParsedAttr &Attr,
2392+
Sema::DeclAttributeLocation DAL) {
2393+
// cheri_compartment is both:
2394+
//
2395+
// * a Declaration attribute: marks the function as a compartment
2396+
// entry point
2397+
// * a Function Type attribute: affects the calling convention
2398+
//
2399+
// That's the reason why we don't short-circuit using hasDeclarator
2400+
// (as other handlers do) as Sema::GetTypeForDeclarator only does
2401+
// the Function Type part.
2402+
//
2403+
// BUT because it is a Function Type attribute, when the attribute
2404+
// is initially attached to the DeclSpec, Sema::GetTypeForDeclarator
2405+
// distributes it to the first DeclChunk which is a function. Thus,
2406+
// the attribute is always be present at the DeclChunk level (but
2407+
// not always at the DeclSpec level). In order to not possibly
2408+
// process it twice, we always skip the DeclSpec level.
2409+
//
2410+
// **Attention**: The attribute is not always initially attached to
2411+
// the DeclSpec especially when it follows a pointer:
2412+
//
2413+
// - cheri_compartment(...) void f(): attached to DeclSpec
2414+
// - void * cheri_compartment(...) f(): attached to DeclChunk
2415+
if (DAL == Sema::DAL_DeclSpec)
2416+
return;
2417+
23922418
StringRef Str;
23932419
SourceLocation LiteralLoc;
23942420
if (!S.checkStringLiteralArgumentAttr(Attr, 0, Str, &LiteralLoc))
23952421
return;
2422+
2423+
// cheri_compartment is considered as function type attribute
2424+
2425+
const auto *FD = dyn_cast<FunctionDecl>(D);
2426+
2427+
if (FD && FD->getReturnType()->isVoidType()) {
2428+
S.Diag(Attr.getLoc(), diag::warn_cheri_compartment_void_return_type);
2429+
2430+
if (SourceRange SR = FD->getReturnTypeSourceRange(); SR.isValid()) {
2431+
S.Diag(SR.getBegin(), diag::note_cheri_compartment_void_return_type)
2432+
<< FixItHint::CreateReplacement(SR, "int");
2433+
}
2434+
} else {
2435+
if (!S.Diags.isIgnored(diag::warn_cheri_compartment_void_return_type,
2436+
LiteralLoc)) {
2437+
D->addAttr(::new (S.Context) WarnUnusedResultAttr(
2438+
S.Context, Attr, "CHERI compartment call"));
2439+
}
2440+
}
2441+
23962442
D->addAttr(::new (S.Context) CHERICompartmentNameAttr(S.Context, Attr, Str));
23972443
}
23982444

@@ -8932,7 +8978,8 @@ static bool MustDelayAttributeArguments(const ParsedAttr &AL) {
89328978
/// silently ignore it if a GNU attribute.
89338979
static void
89348980
ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
8935-
const Sema::ProcessDeclAttributeOptions &Options) {
8981+
const Sema::ProcessDeclAttributeOptions &Options,
8982+
Sema::DeclAttributeLocation DAL = Sema::DAL_Unspecified) {
89368983
if (AL.isInvalid() || AL.getKind() == ParsedAttr::IgnoredAttribute)
89378984
return;
89388985

@@ -9441,7 +9488,7 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
94419488
handleCHERIMethodSuffix(S, D, AL);
94429489
break;
94439490
case ParsedAttr::AT_CHERICompartmentName:
9444-
handleCHERICompartmentName(S, D, AL);
9491+
handleCHERICompartmentName(S, D, AL, DAL);
94459492
break;
94469493
case ParsedAttr::AT_InterruptState:
94479494
handleInterruptState(S, D, AL);
@@ -9755,12 +9802,13 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
97559802
/// attribute list to the specified decl, ignoring any type attributes.
97569803
void Sema::ProcessDeclAttributeList(
97579804
Scope *S, Decl *D, const ParsedAttributesView &AttrList,
9805+
Sema::DeclAttributeLocation DAL,
97589806
const ProcessDeclAttributeOptions &Options) {
97599807
if (AttrList.empty())
97609808
return;
97619809

97629810
for (const ParsedAttr &AL : AttrList)
9763-
ProcessDeclAttribute(*this, S, D, AL, Options);
9811+
ProcessDeclAttribute(*this, S, D, AL, Options, DAL);
97649812

97659813
// FIXME: We should be able to handle these cases in TableGen.
97669814
// GCC accepts
@@ -10011,6 +10059,7 @@ void Sema::ProcessDeclAttributes(Scope *S, Decl *D, const Declarator &PD) {
1001110059
// Apply decl attributes from the DeclSpec if present.
1001210060
if (!PD.getDeclSpec().getAttributes().empty()) {
1001310061
ProcessDeclAttributeList(S, D, PD.getDeclSpec().getAttributes(),
10062+
DAL_DeclSpec,
1001410063
ProcessDeclAttributeOptions()
1001510064
.WithIncludeCXX11Attributes(false)
1001610065
.WithIgnoreTypeAttributes(true));
@@ -10022,13 +10071,14 @@ void Sema::ProcessDeclAttributes(Scope *S, Decl *D, const Declarator &PD) {
1002210071
// when X is a decl attribute.
1002310072
for (unsigned i = 0, e = PD.getNumTypeObjects(); i != e; ++i) {
1002410073
ProcessDeclAttributeList(S, D, PD.getTypeObject(i).getAttrs(),
10074+
DAL_DeclChunk,
1002510075
ProcessDeclAttributeOptions()
1002610076
.WithIncludeCXX11Attributes(false)
1002710077
.WithIgnoreTypeAttributes(true));
1002810078
}
1002910079

1003010080
// Finally, apply any attributes on the decl itself.
10031-
ProcessDeclAttributeList(S, D, PD.getAttributes());
10081+
ProcessDeclAttributeList(S, D, PD.getAttributes(), DAL_Decl);
1003210082

1003310083
// Apply additional attributes specified by '#pragma clang attribute'.
1003410084
AddPragmaAttributes(S, D);

clang/lib/Sema/SemaStmt.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -4120,6 +4120,12 @@ StmtResult Sema::BuildReturnStmt(SourceLocation ReturnLoc, Expr *RetValExp,
41204120
return StmtError();
41214121
RetValExp = ER.get();
41224122
}
4123+
} else if (getCurFunctionOrMethodDecl()
4124+
->hasAttr<CHERICompartmentNameAttr>()) {
4125+
SourceLocation AfterReturnLoc = getLocForEndOfToken(ReturnLoc);
4126+
/* Compartment call */
4127+
Diag(ReturnLoc, diag::warn_cheri_compartment_return_void_or_falloff)
4128+
<< FixItHint::CreateInsertion(AfterReturnLoc, " 0");
41234129
}
41244130

41254131
Result = ReturnStmt::Create(Context, ReturnLoc, RetValExp,

clang/test/CodeGen/cheri/cheri-mcu-interrupts.c

+6-4
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,11 @@ int inherit(void)
2323

2424
// The default for exported functions should be interrupts enabled
2525
//
26-
// CHECK: define dso_local chericcallcce void @_Z21default_enable_calleev() local_unnamed_addr addrspace(200) #[[DEFEN:[0-9]]]
26+
// CHECK: define dso_local chericcallcce i32 @_Z21default_enable_calleev() local_unnamed_addr addrspace(200) #[[DEFEN:[0-9]]]
2727
__attribute__((cheri_compartment("example")))
28-
void default_enable_callee(void)
28+
int default_enable_callee(void)
2929
{
30+
return 0;
3031
}
3132

3233
// CHECK: define dso_local chericcallcc void @default_enable_callback() local_unnamed_addr addrspace(200) #[[DEFEN]]
@@ -37,11 +38,12 @@ void default_enable_callback(void)
3738

3839
// Explicitly setting interrupt status should override the default
3940

40-
// CHECK: define dso_local chericcallcce void @_Z23explicit_disable_calleev() local_unnamed_addr addrspace(200) #[[EXPDIS:[0-9]]]
41+
// CHECK: define dso_local chericcallcce i32 @_Z23explicit_disable_calleev() local_unnamed_addr addrspace(200) #[[EXPDIS:[0-9]]]
4142
__attribute__((cheri_interrupt_state(disabled)))
4243
__attribute__((cheri_compartment("example")))
43-
void explicit_disable_callee(void)
44+
int explicit_disable_callee(void)
4445
{
46+
return 0;
4547
}
4648

4749
// CHECK: define dso_local chericcallcc void @explicit_disable_callback() local_unnamed_addr addrspace(200) #[[EXPDIS]]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// RUN: %clang_cc1 %s -o - -triple riscv32-unknown-unknown -emit-llvm -mframe-pointer=none -mcmodel=small -target-cpu cheriot -target-feature +xcheri -target-feature -64bit -target-feature -relax -target-feature -xcheri-rvc -target-feature -save-restore -target-abi cheriot -Oz -cheri-compartment=example -Wcheri-compartment-return-void -verify -fsyntax-only
2+
// RUN: %clang_cc1 %s -o - -triple riscv32-unknown-unknown -emit-llvm -mframe-pointer=none -mcmodel=small -target-cpu cheriot -target-feature +xcheri -target-feature -64bit -target-feature -relax -target-feature -xcheri-rvc -target-feature -save-restore -target-abi cheriot -Oz -cheri-compartment=example -Wcheri-compartment-return-void -fdiagnostics-parseable-fixits -fsyntax-only 2>&1 | FileCheck %s
3+
4+
__attribute__((cheri_compartment("example"))) void void_return_type_f(int a) // expected-warning{{void return on a cross-compartment call makes it impossible for callers to detect failure}} expected-note{{replace void return type with int}}
5+
{
6+
if (a) {
7+
/// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:[[COL:[0-9]+]]-[[@LINE+1]]:[[COL]]}:" 0"
8+
return; // expected-warning{{cross-compartement calls that always succeed should return 0 instead}}
9+
}
10+
} // expected-warning{{cross-compartement calls that always succeed should return 0 instead}}
11+
12+
__attribute__((cheri_compartment("example"))) int int_return_type_f() {
13+
return 0;
14+
}
15+
16+
void unused_int_return_type_f() {
17+
int_return_type_f(); // expected-warning{{ignoring return value of function declared with 'nodiscard' attribute: CHERI compartment call}}
18+
}

clang/unittests/AST/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ add_clang_unittest(ASTTests
3636
TemplateNameTest.cpp
3737
TypePrinterTest.cpp
3838
UnresolvedSetTest.cpp
39+
40+
cheri/AttrTest.cpp
3941
)
4042

4143
clang_target_link_libraries(ASTTests

0 commit comments

Comments
 (0)