Skip to content

Commit fc23f34

Browse files
beccadaxCodaFi
authored andcommitted
Reject bad string interpolations (swiftlang#17074)
* Reject bad string interpolations String interpolations with multiple comma-separate expressions or argument labels were being incorrectly accepted. * Tweak error name to match message * Diagnose empty interpolations more clearly * Don’t double-diagnose parse errors Fixes a test at Parse/recovery.swift:799 which the previous commit broke. * Fix incorrect test RUN: line A previous version of this test used FileCheck instead of -verify, and the run line wasn’t properly corrected to use -verify. * Update comment * Add more argument label tests Ensures that we don’t get different results from an initializer that doesn’t exist or doesn’t take a String. * Resolve the SR-7958 crasher test We now diagnose the error and remove the label before it has an opportunity to crash.
1 parent 87e9b85 commit fc23f34

File tree

5 files changed

+90
-2
lines changed

5 files changed

+90
-2
lines changed

include/swift/AST/DiagnosticsParse.def

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1224,6 +1224,16 @@ ERROR(expected_type_after_as,none,
12241224
ERROR(string_interpolation_extra,none,
12251225
"extra tokens after interpolated string expression", ())
12261226

1227+
// Interpolated value accidentally forms a tuple.
1228+
ERROR(string_interpolation_single_expr,none,
1229+
"interpolations should be a single expression", ())
1230+
NOTE(string_interpolation_form_tuple,none,
1231+
"insert parentheses to form a tuple", ())
1232+
NOTE(string_interpolation_delete_empty,none,
1233+
"delete empty interpolation", ())
1234+
ERROR(string_interpolation_keyword_argument,PointsToFirstBadToken,
1235+
"interpolations cannot start with a keyword argument", ())
1236+
12271237
// Keypath expressions.
12281238
ERROR(expr_keypath_expected_lparen,PointsToFirstBadToken,
12291239
"expected '(' following '#keyPath'", ())

lib/Parse/ParseExpr.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1907,6 +1907,50 @@ parseStringSegments(SmallVectorImpl<Lexer::StringSegment> &Segments,
19071907
Status |= E;
19081908
if (E.isNonNull()) {
19091909
Exprs.push_back(E.get());
1910+
1911+
if (auto tuple = dyn_cast<TupleExpr>(Exprs.back())) {
1912+
// If parseExprList() returns a TupleExpr instead of a ParenExpr,
1913+
// the interpolation must have had an argument label, or the wrong
1914+
// number of elements, or both. Reject these.
1915+
if (tuple->getNumElements() > 1) {
1916+
SourceLoc StartLoc = tuple->getStartLoc();
1917+
SourceLoc SecondExprLoc = tuple->getElement(1)->getStartLoc();
1918+
SourceLoc EndLoc = tuple->getEndLoc();
1919+
1920+
diagnose(SecondExprLoc, diag::string_interpolation_single_expr);
1921+
diagnose(StartLoc, diag::string_interpolation_form_tuple)
1922+
.fixItInsert(StartLoc, "(")
1923+
.fixItInsertAfter(EndLoc, ")");
1924+
1925+
Exprs.back() =
1926+
new (Context) ParenExpr(SourceLoc(), tuple, SourceLoc(),
1927+
/*hasTrailingClosure=*/false);
1928+
} else if (tuple->getNumElements() == 1 &&
1929+
!tuple->getElementName(0).empty()) {
1930+
SourceLoc NameStart = tuple->getElementNameLoc(0);
1931+
SourceLoc ArgStart = tuple->getElement(0)->getStartLoc();
1932+
1933+
diagnose(NameStart, diag::string_interpolation_keyword_argument)
1934+
.fixItRemoveChars(NameStart, ArgStart);
1935+
1936+
Exprs.back() =
1937+
new (Context) ParenExpr(tuple->getLParenLoc(), tuple->getElement(0),
1938+
tuple->getRParenLoc(), /*hasTrailingClosure=*/false);
1939+
} else if (tuple->getNumElements() == 0 && !Status.isError()) {
1940+
SourceLoc StartLoc = tuple->getStartLoc();
1941+
SourceLoc EndLoc = tuple->getEndLoc();
1942+
SourceLoc SlashLoc = StartLoc.getAdvancedLocOrInvalid(-1);
1943+
1944+
diagnose(EndLoc, diag::string_interpolation_single_expr);
1945+
diagnose(SlashLoc, diag::string_interpolation_delete_empty)
1946+
.fixItRemoveChars(SlashLoc, EndLoc.getAdvancedLocOrInvalid(1));
1947+
1948+
auto Error = new (Context) ErrorExpr(SourceRange(EndLoc, EndLoc));
1949+
Exprs.back() =
1950+
new (Context) ParenExpr(SourceLoc(), Error, SourceLoc(),
1951+
/*hasTrailingClosure=*/false);
1952+
}
1953+
}
19101954

19111955
if (!Tok.is(tok::eof)) {
19121956
diagnose(Tok, diag::string_interpolation_extra);

lib/Parse/Parser.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -902,6 +902,10 @@ ParserStatus
902902
Parser::parseList(tok RightK, SourceLoc LeftLoc, SourceLoc &RightLoc,
903903
bool AllowSepAfterLast, Diag<> ErrorDiag, SyntaxKind Kind,
904904
llvm::function_ref<ParserStatus()> callback) {
905+
auto TokIsStringInterpolationEOF = [&]() -> bool {
906+
return Tok.is(tok::eof) && Tok.getText() == ")" && RightK == tok::r_paren;
907+
};
908+
905909
llvm::Optional<SyntaxParsingContext> ListContext;
906910
ListContext.emplace(SyntaxContext, Kind);
907911
if (Kind == SyntaxKind::Unknown)
@@ -914,6 +918,10 @@ Parser::parseList(tok RightK, SourceLoc LeftLoc, SourceLoc &RightLoc,
914918
RightLoc = consumeToken(RightK);
915919
return makeParserSuccess();
916920
}
921+
if (TokIsStringInterpolationEOF()) {
922+
RightLoc = Tok.getLoc();
923+
return makeParserSuccess();
924+
}
917925

918926
ParserStatus Status;
919927
while (true) {
@@ -933,7 +941,7 @@ Parser::parseList(tok RightK, SourceLoc LeftLoc, SourceLoc &RightLoc,
933941
// If the lexer stopped with an EOF token whose spelling is ")", then this
934942
// is actually the tuple that is a string literal interpolation context.
935943
// Just accept the ")" and build the tuple as we usually do.
936-
if (Tok.is(tok::eof) && Tok.getText() == ")" && RightK == tok::r_paren) {
944+
if (TokIsStringInterpolationEOF()) {
937945
RightLoc = Tok.getLoc();
938946
return Status;
939947
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// RUN: %target-typecheck-verify-swift
2+
3+
let str = "a"
4+
5+
// Test with too many interpolated values.
6+
func papaBear() {
7+
_ = "\(str, str)" // expected-error{{interpolations should be a single expression}} expected-note {{insert parentheses to form a tuple}} {{9-9=(}} {{19-19=)}}
8+
}
9+
10+
// Test with too few interpolated values.
11+
func mamaBear() {
12+
_ = "\()" // expected-error{{interpolations should be a single expression}} expected-note {{delete empty interpolation}} {{8-11=}}
13+
}
14+
15+
// Test with the correct number of interpolated values.
16+
func babyBear() {
17+
_ = "\(str)"
18+
}
19+
20+
// Test with an argument label
21+
func funkyBear() {
22+
_ = "\(describing: str)" // expected-error{{interpolations cannot start with a keyword argument}} {{10-22=}}
23+
_ = "\(format: str)" // expected-error{{interpolations cannot start with a keyword argument}} {{10-18=}}
24+
_ = "\(validatingUTF8: str)" // expected-error{{interpolations cannot start with a keyword argument}} {{10-26=}}
25+
_ = "\(fnord: str)" // expected-error{{interpolations cannot start with a keyword argument}} {{10-17=}}
26+
}

validation-test/compiler_crashers_2/0160-sr7958.swift renamed to validation-test/compiler_crashers_2_fixed/0160-sr7958.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: not --crash %target-swift-frontend %s -emit-ir
1+
// RUN: not %target-swift-frontend %s -emit-ir
22

33
func foo<U>(_ x: U?) {
44
_ = "\(anyLabelHere: x)"

0 commit comments

Comments
 (0)