Skip to content

Commit 886715a

Browse files
author
serge-sans-paille
committed
[clang] Introduce -fstrict-flex-arrays=<n> for stricter handling of flexible arrays
Some code [0] consider that trailing arrays are flexible, whatever their size. Support for these legacy code has been introduced in f8f6324 but it prevents evaluation of __builtin_object_size and __builtin_dynamic_object_size in some legit cases. Introduce -fstrict-flex-arrays=<n> to have stricter conformance when it is desirable. n = 0: current behavior, any trailing array member is a flexible array. The default. n = 1: any trailing array member of undefined, 0 or 1 size is a flexible array member n = 2: any trailing array member of undefined or 0 size is a flexible array member n = 3: any trailing array member of undefined size is a flexible array member (strict c99 conformance) Similar patch for gcc discuss here: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=101836 [0] https://docs.freebsd.org/en/books/developers-handbook/sockets/#sockets-essential-functions
1 parent 871197d commit 886715a

File tree

17 files changed

+267
-176
lines changed

17 files changed

+267
-176
lines changed

clang/docs/ClangCommandLineReference.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2639,6 +2639,12 @@ Enable unstable and experimental features
26392639

26402640
.. option:: -fuse-init-array, -fno-use-init-array
26412641

2642+
.. option:: -fstrict-flex-arrays=<arg>, -fno-strict-flex-arrays
2643+
2644+
Control which arrays are considered as flexible arrays members. <arg>
2645+
can be 1 (array of size 0, 1 and undefined are considered), 2 (array of size 0
2646+
and undefined are considered) or 3 (only array of undefined size are considered).
2647+
26422648
.. option:: -fuse-ld=<arg>
26432649

26442650
.. option:: -fuse-line-directives, -fno-use-line-directives

clang/docs/ReleaseNotes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ Major New Features
6868

6969
Randomizing structure layout is a C-only feature.
7070

71+
- Clang now supports the ``-fstrict-flex-arrays=<arg>`` option to control which
72+
array bounds lead to flexible array members. The option yields more accurate
73+
``__builtin_object_size`` and ``__builtin_dynamic_object_size`` results in
74+
most cases but may be overly conservative for some legacy code.
75+
7176
Bug Fixes
7277
---------
7378
- ``CXXNewExpr::getArraySize()`` previously returned a ``llvm::Optional``

clang/include/clang/AST/Expr.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,16 @@ class Expr : public ValueStmt {
443443
return (OK == OK_Ordinary || OK == OK_BitField);
444444
}
445445

446+
/// True when this expression refers to a flexible array member in a
447+
/// struct. \c StrictFlexArraysLevel controls which array bounds are
448+
/// acceptable for such arrays:
449+
///
450+
/// - 0 => any array bound,
451+
/// - 1 => [0], [1], [ ]
452+
/// - 2 => [0], [ ]
453+
/// - 3 => [ ]
454+
bool isFlexibleArrayMember(ASTContext &Ctx, int StrictFlexArraysLevel) const;
455+
446456
/// setValueKind - Set the value kind produced by this expression.
447457
void setValueKind(ExprValueKind Cat) { ExprBits.ValueKind = Cat; }
448458

clang/include/clang/Basic/LangOptions.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,7 @@ LANGOPT(PaddingOnUnsignedFixedPoint, 1, 0,
424424
LANGOPT(RegisterStaticDestructors, 1, 1, "Register C++ static destructors")
425425

426426
LANGOPT(MatrixTypes, 1, 0, "Enable or disable the builtin matrix type")
427+
LANGOPT(StrictFlexArrays, 2, 0, "Rely on strict definition of flexible arrays")
427428

428429
COMPATIBLE_VALUE_LANGOPT(MaxTokens, 32, 0, "Max number of tokens per TU or 0")
429430

clang/include/clang/Driver/Options.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1134,6 +1134,12 @@ def fallow_unsupported : Flag<["-"], "fallow-unsupported">, Group<f_Group>;
11341134
def fapple_kext : Flag<["-"], "fapple-kext">, Group<f_Group>, Flags<[CC1Option]>,
11351135
HelpText<"Use Apple's kernel extensions ABI">,
11361136
MarshallingInfoFlag<LangOpts<"AppleKext">>;
1137+
def fstrict_flex_arrays_EQ : Joined<["-"], "fstrict-flex-arrays=">,Group<f_Group>,
1138+
MetaVarName<"<n">, Values<"1,2,3">,
1139+
LangOpts<"StrictFlexArrays">,
1140+
Flags<[CC1Option]>,
1141+
HelpText<"Enable optimizations based on the strict definition of flexible arrays">,
1142+
MarshallingInfoInt<LangOpts<"StrictFlexArrays">>;
11371143
defm apple_pragma_pack : BoolFOption<"apple-pragma-pack",
11381144
LangOpts<"ApplePragmaPack">, DefaultFalse,
11391145
PosFlag<SetTrue, [CC1Option], "Enable Apple gcc-compatible #pragma pack handling">,

clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1364,6 +1364,7 @@ class MemRegionManager {
13641364
~MemRegionManager();
13651365

13661366
ASTContext &getContext() { return Ctx; }
1367+
const ASTContext &getContext() const { return Ctx; }
13671368

13681369
llvm::BumpPtrAllocator &getAllocator() { return A; }
13691370

clang/lib/AST/Expr.cpp

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,91 @@ bool Expr::isKnownToHaveBooleanValue(bool Semantic) const {
203203
return false;
204204
}
205205

206+
bool Expr::isFlexibleArrayMember(ASTContext &Ctx,
207+
int StrictFlexArraysLevel) const {
208+
const NamedDecl *ND = nullptr;
209+
if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(this))
210+
ND = DRE->getDecl();
211+
if (const MemberExpr *ME = dyn_cast<MemberExpr>(this))
212+
ND = ME->getMemberDecl();
213+
if (const ObjCIvarRefExpr *RE = dyn_cast<ObjCIvarRefExpr>(this))
214+
ND = RE->getDecl();
215+
if (!ND)
216+
return false;
217+
218+
const ConstantArrayType *ArrayTy = Ctx.getAsConstantArrayType(getType());
219+
220+
const Type *BaseType =
221+
ArrayTy == nullptr ? nullptr : ArrayTy->getElementType().getTypePtr();
222+
bool IsUnboundedArray = (BaseType == nullptr);
223+
224+
if (!IsUnboundedArray) {
225+
llvm::APInt Size = ArrayTy->getSize();
226+
227+
switch (StrictFlexArraysLevel) {
228+
case 3:
229+
return false;
230+
case 2:
231+
if (Size != 0)
232+
return false;
233+
break;
234+
case 1:
235+
if (Size.ugt(1))
236+
return false;
237+
break;
238+
case 0:
239+
break;
240+
default:
241+
llvm_unreachable("Invalid strict flex arrays level");
242+
}
243+
}
244+
245+
const FieldDecl *FD = dyn_cast<FieldDecl>(ND);
246+
if (!FD)
247+
return false;
248+
249+
// Don't consider sizes resulting from macro expansions or template argument
250+
// substitution to form C89 tail-padded arrays.
251+
252+
TypeSourceInfo *TInfo = FD->getTypeSourceInfo();
253+
while (TInfo) {
254+
TypeLoc TL = TInfo->getTypeLoc();
255+
// Look through typedefs.
256+
if (TypedefTypeLoc TTL = TL.getAs<TypedefTypeLoc>()) {
257+
const TypedefNameDecl *TDL = TTL.getTypedefNameDecl();
258+
TInfo = TDL->getTypeSourceInfo();
259+
continue;
260+
}
261+
if (ConstantArrayTypeLoc CTL = TL.getAs<ConstantArrayTypeLoc>()) {
262+
const Expr *SizeExpr = dyn_cast<IntegerLiteral>(CTL.getSizeExpr());
263+
if (!SizeExpr || SizeExpr->getExprLoc().isMacroID())
264+
return false;
265+
}
266+
break;
267+
}
268+
269+
const ObjCInterfaceDecl *ID =
270+
dyn_cast<ObjCInterfaceDecl>(FD->getDeclContext());
271+
const RecordDecl *RD = dyn_cast<RecordDecl>(FD->getDeclContext());
272+
if (RD) {
273+
if (RD->isUnion())
274+
return false;
275+
if (const CXXRecordDecl *CRD = dyn_cast<CXXRecordDecl>(RD)) {
276+
if (!CRD->isStandardLayout())
277+
return false;
278+
}
279+
} else if (!ID)
280+
return false;
281+
282+
// See if this is the last field decl in the record.
283+
const Decl *D = FD;
284+
while ((D = D->getNextDeclInContext()))
285+
if (isa<FieldDecl>(D))
286+
return false;
287+
288+
return true;
289+
}
290+
206291
const ValueDecl *
207292
Expr::getAsBuiltinConstantDeclRef(const ASTContext &Context) const {
208293
Expr::EvalResult Eval;

clang/lib/AST/ExprConstant.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11592,9 +11592,16 @@ static bool isUserWritingOffTheEnd(const ASTContext &Ctx, const LValue &LVal) {
1159211592
// conservative with the last element in structs (if it's an array), so our
1159311593
// current behavior is more compatible than an explicit list approach would
1159411594
// be.
11595+
int StrictFlexArraysLevel = Ctx.getLangOpts().StrictFlexArrays;
1159511596
return LVal.InvalidBase &&
1159611597
Designator.Entries.size() == Designator.MostDerivedPathLength &&
1159711598
Designator.MostDerivedIsArrayElement &&
11599+
(Designator.isMostDerivedAnUnsizedArray() ||
11600+
(Designator.getMostDerivedArraySize() == 0 &&
11601+
StrictFlexArraysLevel < 3) ||
11602+
(Designator.getMostDerivedArraySize() == 1 &&
11603+
StrictFlexArraysLevel < 2) ||
11604+
StrictFlexArraysLevel == 0) &&
1159811605
isDesignatorAtObjectEnd(Ctx, LVal);
1159911606
}
1160011607

clang/lib/CodeGen/CGExpr.cpp

Lines changed: 9 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -875,44 +875,6 @@ void CodeGenFunction::EmitTypeCheck(TypeCheckKind TCK, SourceLocation Loc,
875875
}
876876
}
877877

878-
/// Determine whether this expression refers to a flexible array member in a
879-
/// struct. We disable array bounds checks for such members.
880-
static bool isFlexibleArrayMemberExpr(const Expr *E) {
881-
// For compatibility with existing code, we treat arrays of length 0 or
882-
// 1 as flexible array members.
883-
// FIXME: This is inconsistent with the warning code in SemaChecking. Unify
884-
// the two mechanisms.
885-
const ArrayType *AT = E->getType()->castAsArrayTypeUnsafe();
886-
if (const auto *CAT = dyn_cast<ConstantArrayType>(AT)) {
887-
// FIXME: Sema doesn't treat [1] as a flexible array member if the bound
888-
// was produced by macro expansion.
889-
if (CAT->getSize().ugt(1))
890-
return false;
891-
} else if (!isa<IncompleteArrayType>(AT))
892-
return false;
893-
894-
E = E->IgnoreParens();
895-
896-
// A flexible array member must be the last member in the class.
897-
if (const auto *ME = dyn_cast<MemberExpr>(E)) {
898-
// FIXME: If the base type of the member expr is not FD->getParent(),
899-
// this should not be treated as a flexible array member access.
900-
if (const auto *FD = dyn_cast<FieldDecl>(ME->getMemberDecl())) {
901-
// FIXME: Sema doesn't treat a T[1] union member as a flexible array
902-
// member, only a T[0] or T[] member gets that treatment.
903-
if (FD->getParent()->isUnion())
904-
return true;
905-
RecordDecl::field_iterator FI(
906-
DeclContext::decl_iterator(const_cast<FieldDecl *>(FD)));
907-
return ++FI == FD->getParent()->field_end();
908-
}
909-
} else if (const auto *IRE = dyn_cast<ObjCIvarRefExpr>(E)) {
910-
return IRE->getDecl()->getNextIvar() == nullptr;
911-
}
912-
913-
return false;
914-
}
915-
916878
llvm::Value *CodeGenFunction::LoadPassedObjectSize(const Expr *E,
917879
QualType EltTy) {
918880
ASTContext &C = getContext();
@@ -954,8 +916,11 @@ llvm::Value *CodeGenFunction::LoadPassedObjectSize(const Expr *E,
954916

955917
/// If Base is known to point to the start of an array, return the length of
956918
/// that array. Return 0 if the length cannot be determined.
957-
static llvm::Value *getArrayIndexingBound(
958-
CodeGenFunction &CGF, const Expr *Base, QualType &IndexedType) {
919+
static llvm::Value *getArrayIndexingBound(CodeGenFunction &CGF,
920+
const Expr *Base,
921+
QualType &IndexedType,
922+
ASTContext &Context,
923+
int StrictFlexArraysLevel) {
959924
// For the vector indexing extension, the bound is the number of elements.
960925
if (const VectorType *VT = Base->getType()->getAs<VectorType>()) {
961926
IndexedType = Base->getType();
@@ -966,7 +931,8 @@ static llvm::Value *getArrayIndexingBound(
966931

967932
if (const auto *CE = dyn_cast<CastExpr>(Base)) {
968933
if (CE->getCastKind() == CK_ArrayToPointerDecay &&
969-
!isFlexibleArrayMemberExpr(CE->getSubExpr())) {
934+
!CE->getSubExpr()->isFlexibleArrayMember(Context,
935+
StrictFlexArraysLevel)) {
970936
IndexedType = CE->getSubExpr()->getType();
971937
const ArrayType *AT = IndexedType->castAsArrayTypeUnsafe();
972938
if (const auto *CAT = dyn_cast<ConstantArrayType>(AT))
@@ -994,7 +960,8 @@ void CodeGenFunction::EmitBoundsCheck(const Expr *E, const Expr *Base,
994960
SanitizerScope SanScope(this);
995961

996962
QualType IndexedType;
997-
llvm::Value *Bound = getArrayIndexingBound(*this, Base, IndexedType);
963+
llvm::Value *Bound = getArrayIndexingBound(
964+
*this, Base, IndexedType, getContext(), getLangOpts().StrictFlexArrays);
998965
if (!Bound)
999966
return;
1000967

clang/lib/Driver/ToolChains/Clang.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6218,6 +6218,8 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
62186218
Args.AddLastArg(CmdArgs, options::OPT_funroll_loops,
62196219
options::OPT_fno_unroll_loops);
62206220

6221+
Args.AddLastArg(CmdArgs, options::OPT_fstrict_flex_arrays_EQ);
6222+
62216223
Args.AddLastArg(CmdArgs, options::OPT_pthread);
62226224

62236225
if (Args.hasFlag(options::OPT_mspeculative_load_hardening,

clang/lib/Sema/SemaChecking.cpp

Lines changed: 3 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -15716,53 +15716,6 @@ void Sema::CheckCastAlign(Expr *Op, QualType T, SourceRange TRange) {
1571615716
<< TRange << Op->getSourceRange();
1571715717
}
1571815718

15719-
/// Check whether this array fits the idiom of a size-one tail padded
15720-
/// array member of a struct.
15721-
///
15722-
/// We avoid emitting out-of-bounds access warnings for such arrays as they are
15723-
/// commonly used to emulate flexible arrays in C89 code.
15724-
static bool IsTailPaddedMemberArray(Sema &S, const llvm::APInt &Size,
15725-
const NamedDecl *ND) {
15726-
if (Size != 1 || !ND) return false;
15727-
15728-
const FieldDecl *FD = dyn_cast<FieldDecl>(ND);
15729-
if (!FD) return false;
15730-
15731-
// Don't consider sizes resulting from macro expansions or template argument
15732-
// substitution to form C89 tail-padded arrays.
15733-
15734-
TypeSourceInfo *TInfo = FD->getTypeSourceInfo();
15735-
while (TInfo) {
15736-
TypeLoc TL = TInfo->getTypeLoc();
15737-
// Look through typedefs.
15738-
if (TypedefTypeLoc TTL = TL.getAs<TypedefTypeLoc>()) {
15739-
const TypedefNameDecl *TDL = TTL.getTypedefNameDecl();
15740-
TInfo = TDL->getTypeSourceInfo();
15741-
continue;
15742-
}
15743-
if (ConstantArrayTypeLoc CTL = TL.getAs<ConstantArrayTypeLoc>()) {
15744-
const Expr *SizeExpr = dyn_cast<IntegerLiteral>(CTL.getSizeExpr());
15745-
if (!SizeExpr || SizeExpr->getExprLoc().isMacroID())
15746-
return false;
15747-
}
15748-
break;
15749-
}
15750-
15751-
const RecordDecl *RD = dyn_cast<RecordDecl>(FD->getDeclContext());
15752-
if (!RD) return false;
15753-
if (RD->isUnion()) return false;
15754-
if (const CXXRecordDecl *CRD = dyn_cast<CXXRecordDecl>(RD)) {
15755-
if (!CRD->isStandardLayout()) return false;
15756-
}
15757-
15758-
// See if this is the last field decl in the record.
15759-
const Decl *D = FD;
15760-
while ((D = D->getNextDeclInContext()))
15761-
if (isa<FieldDecl>(D))
15762-
return false;
15763-
return true;
15764-
}
15765-
1576615719
void Sema::CheckArrayAccess(const Expr *BaseExpr, const Expr *IndexExpr,
1576715720
const ArraySubscriptExpr *ASE,
1576815721
bool AllowOnePastEnd, bool IndexNegated) {
@@ -15915,10 +15868,9 @@ void Sema::CheckArrayAccess(const Expr *BaseExpr, const Expr *IndexExpr,
1591515868
if (AllowOnePastEnd ? index.ule(size) : index.ult(size))
1591615869
return;
1591715870

15918-
// Also don't warn for arrays of size 1 which are members of some
15919-
// structure. These are often used to approximate flexible arrays in C89
15920-
// code.
15921-
if (IsTailPaddedMemberArray(*this, size, ND))
15871+
// Also don't warn for flexible array members.
15872+
if (BaseExpr->isFlexibleArrayMember(Context,
15873+
getLangOpts().StrictFlexArrays))
1592215874
return;
1592315875

1592415876
// Suppress the warning if the subscript expression (as identified by the

clang/lib/StaticAnalyzer/Core/MemRegion.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,9 @@ DefinedOrUnknownSVal MemRegionManager::getStaticSize(const MemRegion *MR,
789789
if (isa<IncompleteArrayType>(AT))
790790
return true;
791791

792+
if (getContext().getLangOpts().StrictFlexArrays)
793+
return false;
794+
792795
if (const auto *CAT = dyn_cast<ConstantArrayType>(AT)) {
793796
const llvm::APInt &Size = CAT->getSize();
794797
if (Size.isZero())

clang/test/CodeGen/bounds-checking.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,9 @@ union U { int a[0]; int b[1]; int c[2]; };
3535

3636
// CHECK-LABEL: define {{.*}} @f4
3737
int f4(union U *u, int i) {
38-
// a and b are treated as flexible array members.
39-
// CHECK-NOT: @llvm.ubsantrap
38+
// a and b bounds are treated as flexible array members, but they are inside a union
39+
// and that prevent them from being considered as flexible array members.
40+
// NONLOCAL: @llvm.ubsantrap
4041
return u->a[i] + u->b[i];
4142
// CHECK: }
4243
}

0 commit comments

Comments
 (0)