Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,16 @@ Potentially Breaking Changes
call the member ``operator delete`` instead of the expected global
delete operator. The old behavior is retained under ``-fclang-abi-compat=21``
flag.
- Trailing null statements in GNU statement expressions are no longer
ignored by Clang; they now result in a void type. Clang previously
matched GCC's behavior, which was recently clarified to be incorrect.

.. code-block:: c++

// The resulting type is 'void', not 'int'
void foo(void) {
return ({ 1;; });
}

C/C++ Language Potentially Breaking Changes
-------------------------------------------
Expand Down
20 changes: 0 additions & 20 deletions clang/include/clang/AST/Stmt.h
Original file line number Diff line number Diff line change
Expand Up @@ -1831,26 +1831,6 @@ class CompoundStmt final
return const_reverse_body_iterator(body_begin());
}

// Get the Stmt that StmtExpr would consider to be the result of this
// compound statement. This is used by StmtExpr to properly emulate the GCC
// compound expression extension, which ignores trailing NullStmts when
// getting the result of the expression.
// i.e. ({ 5;;; })
// ^^ ignored
// If we don't find something that isn't a NullStmt, just return the last
// Stmt.
Stmt *getStmtExprResult() {
for (auto *B : llvm::reverse(body())) {
if (!isa<NullStmt>(B))
return B;
}
return body_back();
}

const Stmt *getStmtExprResult() const {
return const_cast<CompoundStmt *>(this)->getStmtExprResult();
}

SourceLocation getBeginLoc() const { return LBraceLoc; }
SourceLocation getEndLoc() const { return RBraceLoc; }

Expand Down
2 changes: 1 addition & 1 deletion clang/lib/AST/ByteCode/Compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4157,7 +4157,7 @@ bool Compiler<Emitter>::VisitStmtExpr(const StmtExpr *E) {
StmtExprScope<Emitter> SS(this);

const CompoundStmt *CS = E->getSubStmt();
const Stmt *Result = CS->getStmtExprResult();
const Stmt *Result = CS->body_back();
for (const Stmt *S : CS->body()) {
if (S != Result) {
if (!this->visitStmt(S))
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/AST/ComputeDependence.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ ExprDependence clang::computeDependence(StmtExpr *E, unsigned TemplateDepth) {
auto D = toExprDependenceForImpliedType(E->getType()->getDependence());
// Propagate dependence of the result.
if (const auto *CompoundExprResult =
dyn_cast_or_null<ValueStmt>(E->getSubStmt()->getStmtExprResult()))
dyn_cast_or_null<ValueStmt>(E->getSubStmt()->body_back()))
if (const Expr *ResultExpr = CompoundExprResult->getExprStmt())
D |= ResultExpr->getDependence();
// Note: we treat a statement-expression in a dependent context as always
Expand Down
69 changes: 33 additions & 36 deletions clang/lib/CodeGen/CGStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -582,48 +582,45 @@ CodeGenFunction::EmitCompoundStmtWithoutScope(const CompoundStmt &S,
bool GetLast,
AggValueSlot AggSlot) {

const Stmt *ExprResult = S.getStmtExprResult();
assert((!GetLast || (GetLast && ExprResult)) &&
"If GetLast is true then the CompoundStmt must have a StmtExprResult");
for (CompoundStmt::const_body_iterator I = S.body_begin(),
E = S.body_end() - GetLast;
I != E; ++I)
EmitStmt(*I);

Address RetAlloca = Address::invalid();

for (auto *CurStmt : S.body()) {
if (GetLast && ExprResult == CurStmt) {
// We have to special case labels here. They are statements, but when put
// at the end of a statement expression, they yield the value of their
// subexpression. Handle this by walking through all labels we encounter,
// emitting them before we evaluate the subexpr.
// Similar issues arise for attributed statements.
while (!isa<Expr>(ExprResult)) {
if (const auto *LS = dyn_cast<LabelStmt>(ExprResult)) {
EmitLabel(LS->getDecl());
ExprResult = LS->getSubStmt();
} else if (const auto *AS = dyn_cast<AttributedStmt>(ExprResult)) {
// FIXME: Update this if we ever have attributes that affect the
// semantics of an expression.
ExprResult = AS->getSubStmt();
} else {
llvm_unreachable("unknown value statement");
}
if (GetLast) {
// We have to special case labels here. They are statements, but when put
// at the end of a statement expression, they yield the value of their
// subexpression. Handle this by walking through all labels we encounter,
// emitting them before we evaluate the subexpr.
// Similar issues arise for attributed statements.
const Stmt *LastStmt = S.body_back();
while (!isa<Expr>(LastStmt)) {
if (const auto *LS = dyn_cast<LabelStmt>(LastStmt)) {
EmitLabel(LS->getDecl());
LastStmt = LS->getSubStmt();
} else if (const auto *AS = dyn_cast<AttributedStmt>(LastStmt)) {
// FIXME: Update this if we ever have attributes that affect the
// semantics of an expression.
LastStmt = AS->getSubStmt();
} else {
llvm_unreachable("unknown value statement");
}
}

EnsureInsertPoint();
EnsureInsertPoint();

const Expr *E = cast<Expr>(ExprResult);
QualType ExprTy = E->getType();
if (hasAggregateEvaluationKind(ExprTy)) {
EmitAggExpr(E, AggSlot);
} else {
// We can't return an RValue here because there might be cleanups at
// the end of the StmtExpr. Because of that, we have to emit the result
// here into a temporary alloca.
RetAlloca = CreateMemTemp(ExprTy);
EmitAnyExprToMem(E, RetAlloca, Qualifiers(),
/*IsInit*/ false);
}
const Expr *E = cast<Expr>(LastStmt);
QualType ExprTy = E->getType();
if (hasAggregateEvaluationKind(ExprTy)) {
EmitAggExpr(E, AggSlot);
} else {
EmitStmt(CurStmt);
// We can't return an RValue here because there might be cleanups at
// the end of the StmtExpr. Because of that, we have to emit the result
// here into a temporary alloca.
RetAlloca = CreateMemTemp(ExprTy);
EmitAnyExprToMem(E, RetAlloca, Qualifiers(),
/*IsInit*/ false);
}
}

Expand Down
14 changes: 4 additions & 10 deletions clang/lib/Parse/ParseStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1079,16 +1079,10 @@ bool Parser::ConsumeNullStmt(StmtVector &Stmts) {
StmtResult Parser::handleExprStmt(ExprResult E, ParsedStmtContext StmtCtx) {
bool IsStmtExprResult = false;
if ((StmtCtx & ParsedStmtContext::InStmtExpr) != ParsedStmtContext()) {
// For GCC compatibility we skip past NullStmts.
unsigned LookAhead = 0;
while (GetLookAheadToken(LookAhead).is(tok::semi)) {
++LookAhead;
}
// Then look to see if the next two tokens close the statement expression;
// if so, this expression statement is the last statement in a statement
// expression.
IsStmtExprResult = GetLookAheadToken(LookAhead).is(tok::r_brace) &&
GetLookAheadToken(LookAhead + 1).is(tok::r_paren);
// Look ahead to see if the next two tokens close the statement expression;
// if so, this expression statement is the last statement in a
// statment expression.
IsStmtExprResult = Tok.is(tok::r_brace) && NextToken().is(tok::r_paren);
}

if (IsStmtExprResult)
Expand Down
4 changes: 1 addition & 3 deletions clang/lib/Sema/SemaExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16185,9 +16185,7 @@ ExprResult Sema::BuildStmtExpr(SourceLocation LPLoc, Stmt *SubStmt,
QualType Ty = Context.VoidTy;
bool StmtExprMayBindToTemp = false;
if (!Compound->body_empty()) {
// For GCC compatibility we get the last Stmt excluding trailing NullStmts.
if (const auto *LastStmt =
dyn_cast<ValueStmt>(Compound->getStmtExprResult())) {
if (const auto *LastStmt = dyn_cast<ValueStmt>(Compound->body_back())) {
if (const Expr *Value = LastStmt->getExprStmt()) {
StmtExprMayBindToTemp = true;
Ty = Value->getType();
Expand Down
5 changes: 2 additions & 3 deletions clang/lib/Sema/TreeTransform.h
Original file line number Diff line number Diff line change
Expand Up @@ -8080,14 +8080,13 @@ TreeTransform<Derived>::TransformCompoundStmt(CompoundStmt *S,
getSema().resetFPOptions(
S->getStoredFPFeatures().applyOverrides(getSema().getLangOpts()));

const Stmt *ExprResult = S->getStmtExprResult();
bool SubStmtInvalid = false;
bool SubStmtChanged = false;
SmallVector<Stmt*, 8> Statements;
for (auto *B : S->body()) {
StmtResult Result = getDerived().TransformStmt(
B, IsStmtExpr && B == ExprResult ? StmtDiscardKind::StmtExprResult
: StmtDiscardKind::Discarded);
B, IsStmtExpr && B == S->body_back() ? StmtDiscardKind::StmtExprResult
: StmtDiscardKind::Discarded);

if (Result.isInvalid()) {
// Immediately fail if this was a DeclStmt, since it's very
Expand Down
2 changes: 1 addition & 1 deletion clang/test/AST/ast-dump-stmt.c
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ void TestMiscStmts(void) {
// CHECK-NEXT: ImplicitCastExpr
// CHECK-NEXT: DeclRefExpr 0x{{[^ ]*}} <col:17> 'int' lvalue Var 0x{{[^ ]*}} 'a' 'int'
({int a = 10; a;;; });
// CHECK-NEXT: StmtExpr 0x{{[^ ]*}} <line:[[@LINE-1]]:3, col:23> 'int'
// CHECK-NEXT: StmtExpr 0x{{[^ ]*}} <line:[[@LINE-1]]:3, col:23> 'void'
// CHECK-NEXT: CompoundStmt
// CHECK-NEXT: DeclStmt
// CHECK-NEXT: VarDecl 0x{{[^ ]*}} <col:5, col:13> col:9 used a 'int' cinit
Expand Down
9 changes: 8 additions & 1 deletion clang/test/CodeGen/exprs.c
Original file line number Diff line number Diff line change
Expand Up @@ -196,10 +196,17 @@ void f18(void) {

// Ensure the right stmt is returned
int f19(void) {
return ({ 3;;4;; });
return ({ 3;;4; });
}
// CHECK-LABEL: define{{.*}} i32 @f19()
// CHECK: [[T:%.*]] = alloca i32
// CHECK: store i32 4, ptr [[T]]
// CHECK: [[L:%.*]] = load i32, ptr [[T]]
// CHECK: ret i32 [[L]]

// PR166036: The trailing NullStmt should result in a void.
void f20(void) {
return ({ 3;;4;; });
}
// CHECK-LABEL: define{{.*}} void @f20()
// CHECK: ret void
9 changes: 5 additions & 4 deletions clang/test/Sema/statements.c
Original file line number Diff line number Diff line change
Expand Up @@ -119,14 +119,15 @@ void test_pr22849(void) {
};
}

// GCC ignores empty statements at the end of compound expressions where the
// result type is concerned.
// Empty statements at the end of compound expressions have a result type 'void'.
void test13(void) {
int a;
a = ({ 1; });
a = ({1;; });
a = ({ 1; 2; }); // expected-warning {{expression result unused}}
a = ({ 1;; }); // expected-error {{assigning to 'int' from incompatible type 'void'}}
// expected-warning@-1 {{expression result unused}}
a = ({int x = 1; (void)x; }); // expected-error {{assigning to 'int' from incompatible type 'void'}}
a = ({int x = 1; (void)x;; }); // expected-error {{assigning to 'int' from incompatible type 'void'}}
a = ({int x = 1;; }); // expected-error {{assigning to 'int' from incompatible type 'void'}}
}

void test14(void) { return ({}); }
Expand Down
17 changes: 15 additions & 2 deletions clang/test/SemaCXX/statements.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,6 @@ T test7(T v) {
return ({ // expected-warning{{use of GNU statement expression extension}}
T a = v;
a;
;
;
});
}

Expand All @@ -53,6 +51,21 @@ void test8() {
double b = test7(2.0);
}

template <typename T>
T test9(T v) {
return ({ // expected-warning {{use of GNU statement expression extension}}
T a = v;
a; // expected-warning {{expression result unused}}
;
;
});
}

void test10() {
int a = test9(1); // expected-note {{in instantiation of function template specialization 'test9<int>' requested here}}
// expected-error@-10 {{cannot initialize return object of type 'int' with an rvalue of type 'void'}}
}

namespace GH48405 {
void foo() {
struct S {
Expand Down