Skip to content

Commit

Permalink
[Sema] Fold VLAs to constant arrays in a few more contexts
Browse files Browse the repository at this point in the history
552c6c2 removed support for promoting VLAs to constant arrays when the bounds
isn't an ICE, since this can result in miscompiling a conforming program that
assumes that the array is a VLA. Promoting VLAs for fields is still supported,
since clang doesn't support VLAs in fields, so no conforming program could have
a field VLA.

This change is really disruptive, so this commit carves out two more cases
where we promote VLAs which can't miscompile a conforming program:

 - When the VLA appears in an ivar -- this seems like a corollary to the field thing
 - When the VLA has an initializer -- VLAs can't have an initializer

Differential revision: https://reviews.llvm.org/D90871
  • Loading branch information
epilk committed Dec 4, 2020
1 parent c8ec685 commit 090dd64
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 35 deletions.
11 changes: 9 additions & 2 deletions clang/include/clang/Sema/DeclSpec.h
Expand Up @@ -1844,6 +1844,9 @@ class Declarator {
/// Indicates whether the InlineParams / InlineBindings storage has been used.
unsigned InlineStorageUsed : 1;

/// Indicates whether this declarator has an initializer.
unsigned HasInitializer : 1;

/// Attrs - Attributes.
ParsedAttributes Attrs;

Expand Down Expand Up @@ -1892,8 +1895,8 @@ class Declarator {
FunctionDefinitionKind::Declaration)),
Redeclaration(false), Extension(false), ObjCIvar(false),
ObjCWeakProperty(false), InlineStorageUsed(false),
Attrs(ds.getAttributePool().getFactory()), AsmLabel(nullptr),
TrailingRequiresClause(nullptr),
HasInitializer(false), Attrs(ds.getAttributePool().getFactory()),
AsmLabel(nullptr), TrailingRequiresClause(nullptr),
InventedTemplateParameterList(nullptr) {}

~Declarator() {
Expand Down Expand Up @@ -1976,6 +1979,7 @@ class Declarator {
Attrs.clear();
AsmLabel = nullptr;
InlineStorageUsed = false;
HasInitializer = false;
ObjCIvar = false;
ObjCWeakProperty = false;
CommaLoc = SourceLocation();
Expand Down Expand Up @@ -2574,6 +2578,9 @@ class Declarator {
return (FunctionDefinitionKind)FunctionDefinition;
}

void setHasInitializer(bool Val = true) { HasInitializer = Val; }
bool hasInitializer() const { return HasInitializer; }

/// Returns true if this declares a real member and not a friend.
bool isFirstDeclarationOfMember() {
return getContext() == DeclaratorContext::Member &&
Expand Down
37 changes: 29 additions & 8 deletions clang/lib/Parse/ParseDecl.cpp
Expand Up @@ -2192,7 +2192,22 @@ Decl *Parser::ParseDeclarationAfterDeclaratorAndAttributes(
}
};

// Inform the current actions module that we just parsed this declarator.
enum class InitKind { Uninitialized, Equal, CXXDirect, CXXBraced };
InitKind TheInitKind;
// If a '==' or '+=' is found, suggest a fixit to '='.
if (isTokenEqualOrEqualTypo())
TheInitKind = InitKind::Equal;
else if (Tok.is(tok::l_paren))
TheInitKind = InitKind::CXXDirect;
else if (getLangOpts().CPlusPlus11 && Tok.is(tok::l_brace) &&
(!CurParsedObjCImpl || !D.isFunctionDeclarator()))
TheInitKind = InitKind::CXXBraced;
else
TheInitKind = InitKind::Uninitialized;
if (TheInitKind != InitKind::Uninitialized)
D.setHasInitializer();

// Inform Sema that we just parsed this declarator.
Decl *ThisDecl = nullptr;
Decl *OuterDecl = nullptr;
switch (TemplateInfo.Kind) {
Expand Down Expand Up @@ -2254,9 +2269,9 @@ Decl *Parser::ParseDeclarationAfterDeclaratorAndAttributes(
}
}

switch (TheInitKind) {
// Parse declarator '=' initializer.
// If a '==' or '+=' is found, suggest a fixit to '='.
if (isTokenEqualOrEqualTypo()) {
case InitKind::Equal: {
SourceLocation EqualLoc = ConsumeToken();

if (Tok.is(tok::kw_delete)) {
Expand Down Expand Up @@ -2311,7 +2326,9 @@ Decl *Parser::ParseDeclarationAfterDeclaratorAndAttributes(
Actions.AddInitializerToDecl(ThisDecl, Init.get(),
/*DirectInit=*/false);
}
} else if (Tok.is(tok::l_paren)) {
break;
}
case InitKind::CXXDirect: {
// Parse C++ direct initializer: '(' expression-list ')'
BalancedDelimiterTracker T(*this, tok::l_paren);
T.consumeOpen();
Expand Down Expand Up @@ -2365,8 +2382,9 @@ Decl *Parser::ParseDeclarationAfterDeclaratorAndAttributes(
Actions.AddInitializerToDecl(ThisDecl, Initializer.get(),
/*DirectInit=*/true);
}
} else if (getLangOpts().CPlusPlus11 && Tok.is(tok::l_brace) &&
(!CurParsedObjCImpl || !D.isFunctionDeclarator())) {
break;
}
case InitKind::CXXBraced: {
// Parse C++0x braced-init-list.
Diag(Tok, diag::warn_cxx98_compat_generalized_initializer_lists);

Expand All @@ -2381,9 +2399,12 @@ Decl *Parser::ParseDeclarationAfterDeclaratorAndAttributes(
Actions.ActOnInitializerError(ThisDecl);
} else
Actions.AddInitializerToDecl(ThisDecl, Init.get(), /*DirectInit=*/true);

} else {
break;
}
case InitKind::Uninitialized: {
Actions.ActOnUninitializedDecl(ThisDecl);
break;
}
}

Actions.FinalizeDeclaration(ThisDecl);
Expand Down
58 changes: 36 additions & 22 deletions clang/lib/Sema/SemaDecl.cpp
Expand Up @@ -6023,6 +6023,31 @@ TryToFixInvalidVariablyModifiedTypeSourceInfo(TypeSourceInfo *TInfo,
return FixedTInfo;
}

/// Attempt to fold a variable-sized type to a constant-sized type, returning
/// true if we were successful.
static bool tryToFixVariablyModifiedVarType(Sema &S, TypeSourceInfo *&TInfo,
QualType &T, SourceLocation Loc,
unsigned FailedFoldDiagID) {
bool SizeIsNegative;
llvm::APSInt Oversized;
TypeSourceInfo *FixedTInfo = TryToFixInvalidVariablyModifiedTypeSourceInfo(
TInfo, S.Context, SizeIsNegative, Oversized);
if (FixedTInfo) {
S.Diag(Loc, diag::ext_vla_folded_to_constant);
TInfo = FixedTInfo;
T = FixedTInfo->getType();
return true;
}

if (SizeIsNegative)
S.Diag(Loc, diag::err_typecheck_negative_array_size);
else if (Oversized.getBoolValue())
S.Diag(Loc, diag::err_array_too_large) << Oversized.toString(10);
else if (FailedFoldDiagID)
S.Diag(Loc, FailedFoldDiagID);
return false;
}

/// Register the given locally-scoped extern "C" declaration so
/// that it can be found later for redeclarations. We include any extern "C"
/// declaration that is not visible in the translation unit here, not just
Expand Down Expand Up @@ -6861,6 +6886,12 @@ NamedDecl *Sema::ActOnVariableDeclarator(
}
}

// If this variable has a variable-modified type and an initializer, try to
// fold to a constant-sized type. This is otherwise invalid.
if (D.hasInitializer() && R->isVariablyModifiedType())
tryToFixVariablyModifiedVarType(*this, TInfo, R, D.getIdentifierLoc(),
/*DiagID=*/0);

bool IsMemberSpecialization = false;
bool IsVariableTemplateSpecialization = false;
bool IsPartialSpecialization = false;
Expand Down Expand Up @@ -16658,27 +16689,9 @@ FieldDecl *Sema::CheckFieldDecl(DeclarationName Name, QualType T,
// C99 6.7.2.1p8: A member of a structure or union may have any type other
// than a variably modified type.
if (!InvalidDecl && T->isVariablyModifiedType()) {
bool SizeIsNegative;
llvm::APSInt Oversized;

TypeSourceInfo *FixedTInfo =
TryToFixInvalidVariablyModifiedTypeSourceInfo(TInfo, Context,
SizeIsNegative,
Oversized);
if (FixedTInfo) {
Diag(Loc, diag::ext_vla_folded_to_constant);
TInfo = FixedTInfo;
T = FixedTInfo->getType();
} else {
if (SizeIsNegative)
Diag(Loc, diag::err_typecheck_negative_array_size);
else if (Oversized.getBoolValue())
Diag(Loc, diag::err_array_too_large)
<< Oversized.toString(10);
else
Diag(Loc, diag::err_typecheck_field_variable_size);
if (!tryToFixVariablyModifiedVarType(
*this, TInfo, T, Loc, diag::err_typecheck_field_variable_size))
InvalidDecl = true;
}
}

// Fields can not have abstract class types
Expand Down Expand Up @@ -16904,8 +16917,9 @@ Decl *Sema::ActOnIvar(Scope *S,
// C99 6.7.2.1p8: A member of a structure or union may have any type other
// than a variably modified type.
else if (T->isVariablyModifiedType()) {
Diag(Loc, diag::err_typecheck_ivar_variable_size);
D.setInvalidType();
if (!tryToFixVariablyModifiedVarType(
*this, TInfo, T, Loc, diag::err_typecheck_ivar_variable_size))
D.setInvalidType();
}

// Get the visibility (access control) for this ivar.
Expand Down
3 changes: 1 addition & 2 deletions clang/test/CXX/basic/basic.types/p10.cpp
Expand Up @@ -140,8 +140,7 @@ constexpr int arb(int n) {
int a[n]; // expected-error {{variable of non-literal type 'int [n]' cannot be defined in a constexpr function}}
}
// expected-warning@+1 {{variable length array folded to constant array as an extension}}
constexpr long Overflow[ // expected-error {{constexpr variable cannot have non-literal type 'long const[(1 << 30) << 2]'}}
(1 << 30) << 2]{}; // expected-warning {{requires 34 bits to represent}}
constexpr long Overflow[(1 << 30) << 2]{}; // expected-warning {{requires 34 bits to represent}}

namespace inherited_ctor {
struct A { constexpr A(int); };
Expand Down
2 changes: 1 addition & 1 deletion clang/test/Sema/decl-in-prototype.c
Expand Up @@ -49,7 +49,7 @@ void f(struct q *, struct __attribute__((aligned(4))) q *); // expected-warning
// function.
enum { BB = 0 };
void enum_in_fun_in_fun(void (*fp)(enum { AA, BB } e)) { // expected-warning {{will not be visible}}
SA(1, AA == 5); // expected-error {{variable-sized object may not be initialized}}
SA(1, AA == 5); // expected-warning{{variable length array folded to constant array as an extension}}
SA(2, BB == 0);
}

Expand Down
30 changes: 30 additions & 0 deletions clang/test/Sema/vla.c
Expand Up @@ -100,3 +100,33 @@ const int pr44406_a = 32;
typedef struct {
char c[pr44406_a]; // expected-warning {{folded to constant array as an extension}}
} pr44406_s;

void test_fold_to_constant_array() {
const int ksize = 4;

goto jump_over_a1; // expected-error{{cannot jump from this goto statement to its label}}
char a1[ksize]; // expected-note{{variable length array}}
jump_over_a1:;

goto jump_over_a2;
char a2[ksize] = "foo"; // expected-warning{{variable length array folded to constant array as an extension}}
jump_over_a2:;

goto jump_over_a3;
char a3[ksize] = {}; // expected-warning {{variable length array folded to constant array as an extension}} expected-warning{{use of GNU empty initializer}}
jump_over_a3:;

goto jump_over_a4; // expected-error{{cannot jump from this goto statement to its label}}
char a4[ksize][2]; // expected-note{{variable length array}}
jump_over_a4:;

char a5[ksize][2] = {}; // expected-warning {{variable length array folded to constant array as an extension}} expected-warning{{use of GNU empty initializer}}

int a6[ksize] = {1,2,3,4}; // expected-warning{{variable length array folded to constant array as an extension}}

// expected-warning@+1{{variable length array folded to constant array as an extension}}
int a7[ksize] __attribute__((annotate("foo"))) = {1,2,3,4};

// expected-warning@+1{{variable length array folded to constant array as an extension}}
char a8[2][ksize] = {{1,2,3,4},{4,3,2,1}};
}
6 changes: 6 additions & 0 deletions clang/test/SemaCXX/vla.cpp
Expand Up @@ -20,3 +20,9 @@ namespace PR18581 {

void pr23151(int (&)[*]) { // expected-error {{variable length array must be bound in function definition}}
}

void test_fold() {
char a1[(unsigned long)(int *)0+1]{}; // expected-warning{{variable length array folded to constant array as an extension}}
char a2[(unsigned long)(int *)0+1] = {}; // expected-warning{{variable length array folded to constant array as an extension}}
char a3[(unsigned long)(int *)0+1];
}
12 changes: 12 additions & 0 deletions clang/test/SemaObjC/variable-size-ivar.m
@@ -0,0 +1,12 @@
// RUN: %clang_cc1 -fsyntax-only %s -verify

const int ksize = 42;
int size = 42;

@interface X
{
int arr1[ksize]; // expected-warning{{variable length array folded to constant array}}
int arr2[size]; // expected-error{{instance variables must have a constant size}}
int arr3[ksize-43]; // expected-error{{array size is negative}}
}
@end

0 comments on commit 090dd64

Please sign in to comment.