Skip to content

Fix the evil fallthrough bug #23687

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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
25 changes: 25 additions & 0 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#include "swift/Basic/ArrayRefView.h"
#include "swift/Basic/Compiler.h"
#include "swift/Basic/InlineBitfield.h"
#include "swift/Basic/NullablePtr.h"
#include "swift/Basic/OptionalEnum.h"
#include "swift/Basic/Range.h"
#include "llvm/ADT/DenseMap.h"
Expand Down Expand Up @@ -4747,8 +4748,32 @@ class VarDecl : public AbstractStorageDecl {
/// return this. Otherwise, this VarDecl must belong to a CaseStmt's
/// CaseLabelItem. In that case, return the first case label item of the first
/// case stmt in a sequence of case stmts that fallthrough into each other.
///
/// NOTE: During type checking, we emit an error if we have a single case
/// label item with a pattern that has multiple var decls of the same
/// name. This means that during type checking and before type checking, we
/// may have a _malformed_ switch stmt var decl linked list since var decls in
/// the same case label item that have the same name will point at the same
/// canonical var decl, namely the first var decl with the name in the
/// canonical case label item's var decl list. This is ok, since we are going
/// to emit the error, but it requires us to be more careful/cautious before
/// type checking has been complete when relying on canonical var decls
/// matching up.
VarDecl *getCanonicalVarDecl() const;

/// If this is a case stmt var decl, return the var decl that corresponds to
/// this var decl in the first case label item of the case stmt. Returns
/// nullptr if this isn't a VarDecl that is part of a case stmt.
NullablePtr<VarDecl> getCorrespondingFirstCaseLabelItemVarDecl() const;

/// If this is a case stmt var decl, return the case body var decl that this
/// var decl maps to.
NullablePtr<VarDecl> getCorrespondingCaseBodyVariable() const;

/// Return true if this var decl is an implicit var decl belonging to a case
/// stmt's body.
bool isCaseBodyVariable() const;

/// True if the global stored property requires lazy initialization.
bool isLazilyInitializedGlobal() const;

Expand Down
7 changes: 7 additions & 0 deletions include/swift/AST/Pattern.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,13 @@ class alignas(8) Pattern {
/// pattern.
void forEachVariable(llvm::function_ref<void(VarDecl *)> f) const;

/// Returns true if \p vd is in the pattern.
bool containsVarDecl(const VarDecl *inputVD) const {
bool result = false;
forEachVariable([&](VarDecl *vd) { result |= inputVD == vd; });
return result;
}

/// apply the specified function to all pattern nodes recursively in
/// this pattern. This is a pre-order traversal.
void forEachNode(llvm::function_ref<void(Pattern *)> f);
Expand Down
8 changes: 6 additions & 2 deletions include/swift/Basic/NullablePtr.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,13 @@ class NullablePtr {

explicit operator bool() const { return Ptr; }

bool operator==(NullablePtr<T> &&other) const { return other.Ptr == Ptr; }
bool operator==(const NullablePtr<T> &other) const {
return other.Ptr == Ptr;
}

bool operator!=(NullablePtr<T> &&other) const { return !(*this == other); }
bool operator!=(const NullablePtr<T> &other) const {
return !(*this == other);
}

bool operator==(const T *other) const { return other == Ptr; }

Expand Down
4 changes: 2 additions & 2 deletions include/swift/Parse/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -689,11 +689,11 @@ class Parser {
swift::ScopeInfo &getScopeInfo() { return State->getScopeInfo(); }

/// Add the given Decl to the current scope.
void addToScope(ValueDecl *D) {
void addToScope(ValueDecl *D, bool diagnoseRedefinitions = true) {
if (Context.LangOpts.EnableASTScopeLookup)
return;

getScopeInfo().addToScope(D, *this);
getScopeInfo().addToScope(D, *this, diagnoseRedefinitions);
}

ValueDecl *lookupInScope(DeclName Name) {
Expand Down
3 changes: 2 additions & 1 deletion include/swift/Parse/Scope.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ class ScopeInfo {

/// addToScope - Register the specified decl as being in the current lexical
/// scope.
void addToScope(ValueDecl *D, Parser &TheParser);
void addToScope(ValueDecl *D, Parser &TheParser,
bool diagnoseRedefinitions = true);

bool isInactiveConfigBlock() const;

Expand Down
14 changes: 14 additions & 0 deletions lib/AST/ASTVerifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2500,6 +2500,20 @@ class Verifier : public ASTWalker {
}
}

if (auto *caseStmt =
dyn_cast_or_null<CaseStmt>(var->getRecursiveParentPatternStmt())) {
// In a type checked AST, a case stmt that is a recursive parent pattern
// stmt of a var decl, must have bound decls. This is because we
// guarantee that all case label items bind corresponding patterns and
// the case body var decls of a case stmt are created from the var decls
// of the first case label items.
if (!caseStmt->hasBoundDecls()) {
Out << "parent CaseStmt of VarDecl does not have any case body "
"decls?!\n";
abort();
}
}

verifyCheckedBase(var);
}

Expand Down
73 changes: 54 additions & 19 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1294,27 +1294,19 @@ VarDecl *PatternBindingInitializer::getInitializedLazyVar() const {
return nullptr;
}

static bool patternContainsVarDeclBinding(const Pattern *P, const VarDecl *VD) {
bool Result = false;
P->forEachVariable([&](VarDecl *FoundVD) {
Result |= FoundVD == VD;
});
return Result;
}

unsigned PatternBindingDecl::getPatternEntryIndexForVarDecl(const VarDecl *VD) const {
assert(VD && "Cannot find a null VarDecl");

auto List = getPatternList();
if (List.size() == 1) {
assert(patternContainsVarDeclBinding(List[0].getPattern(), VD) &&
assert(List[0].getPattern()->containsVarDecl(VD) &&
"Single entry PatternBindingDecl is set up wrong");
return 0;
}

unsigned Result = 0;
for (auto entry : List) {
if (patternContainsVarDeclBinding(entry.getPattern(), VD))
if (entry.getPattern()->containsVarDecl(VD))
return Result;
++Result;
}
Expand Down Expand Up @@ -4927,12 +4919,6 @@ SourceRange VarDecl::getTypeSourceRangeForDiagnostics() const {
return SourceRange();
}

static bool isVarInPattern(const VarDecl *vd, Pattern *p) {
bool foundIt = false;
p->forEachVariable([&](VarDecl *foundFD) { foundIt |= foundFD == vd; });
return foundIt;
}

static Optional<std::pair<CaseStmt *, Pattern *>>
findParentPatternCaseStmtAndPattern(const VarDecl *inputVD) {
auto getMatchingPattern = [&](CaseStmt *cs) -> Pattern * {
Expand All @@ -4946,7 +4932,7 @@ findParentPatternCaseStmtAndPattern(const VarDecl *inputVD) {

// Then check the rest of our case label items.
for (auto &item : cs->getMutableCaseLabelItems()) {
if (isVarInPattern(inputVD, item.getPattern())) {
if (item.getPattern()->containsVarDecl(inputVD)) {
return item.getPattern();
}
}
Expand Down Expand Up @@ -5039,15 +5025,15 @@ Pattern *VarDecl::getParentPattern() const {
// In a case statement, search for the pattern that contains it. This is
// a bit silly, because you can't have something like "case x, y:" anyway.
for (auto items : cs->getCaseLabelItems()) {
if (isVarInPattern(this, items.getPattern()))
if (items.getPattern()->containsVarDecl(this))
return items.getPattern();
}
}

if (auto *LCS = dyn_cast<LabeledConditionalStmt>(stmt)) {
for (auto &elt : LCS->getCond())
if (auto pat = elt.getPatternOrNull())
if (isVarInPattern(this, pat))
if (pat->containsVarDecl(this))
return pat;
}

Expand All @@ -5066,6 +5052,55 @@ Pattern *VarDecl::getParentPattern() const {
return nullptr;
}

NullablePtr<VarDecl>
VarDecl::getCorrespondingFirstCaseLabelItemVarDecl() const {
if (!hasName())
return nullptr;

auto *caseStmt = dyn_cast_or_null<CaseStmt>(getRecursiveParentPatternStmt());
if (!caseStmt)
return nullptr;

auto *pattern = caseStmt->getCaseLabelItems().front().getPattern();
SmallVector<VarDecl *, 8> vars;
pattern->collectVariables(vars);
for (auto *vd : vars) {
if (vd->hasName() && vd->getName() == getName())
return vd;
}
return nullptr;
}

bool VarDecl::isCaseBodyVariable() const {
auto *caseStmt = dyn_cast_or_null<CaseStmt>(getRecursiveParentPatternStmt());
if (!caseStmt)
return false;
return llvm::any_of(caseStmt->getCaseBodyVariablesOrEmptyArray(),
[&](VarDecl *vd) { return vd == this; });
}

NullablePtr<VarDecl> VarDecl::getCorrespondingCaseBodyVariable() const {
// Only var decls associated with case statements can have child var decls.
auto *caseStmt = dyn_cast_or_null<CaseStmt>(getRecursiveParentPatternStmt());
if (!caseStmt)
return nullptr;

// If this var decl doesn't have a name, it can not have a corresponding case
// body variable.
if (!hasName())
return nullptr;

auto name = getName();

// A var decl associated with a case stmt implies that the case stmt has body
// var decls. So we can access the optional value here without worry.
auto caseBodyVars = *caseStmt->getCaseBodyVariables();
auto result = llvm::find_if(caseBodyVars, [&](VarDecl *caseBodyVar) {
return caseBodyVar->getName() == name;
});
return (result != caseBodyVars.end()) ? *result : nullptr;
}

bool VarDecl::isSelfParameter() const {
if (isa<ParamDecl>(this)) {
if (auto *AFD = dyn_cast<AbstractFunctionDecl>(getDeclContext()))
Expand Down
10 changes: 8 additions & 2 deletions lib/AST/NameLookup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2347,8 +2347,14 @@ void FindLocalVal::visitCaseStmt(CaseStmt *S) {
}
}
}
if (!inPatterns && !items.empty())
checkPattern(items[0].getPattern(), DeclVisibilityKind::LocalVariable);

if (!inPatterns && !items.empty()) {
if (auto caseBodyVars = S->getCaseBodyVariables()) {
for (auto *vd : *caseBodyVars) {
checkValueDecl(vd, DeclVisibilityKind::LocalVariable);
}
}
}
visit(S->getBody());
}

Expand Down
14 changes: 14 additions & 0 deletions lib/Parse/ParseStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2476,6 +2476,20 @@ ParserResult<CaseStmt> Parser::parseStmtCase(bool IsActive) {

assert(!CaseLabelItems.empty() && "did not parse any labels?!");

// Add a scope so that the parser can find our body bound decls if it emits
// optimized accesses.
Optional<Scope> BodyScope;
if (CaseBodyDecls) {
BodyScope.emplace(this, ScopeKind::CaseVars);
for (auto *v : *CaseBodyDecls) {
setLocalDiscriminator(v);
// If we had any bad redefinitions, we already diagnosed them against the
// first case label item.
if (v->hasName())
addToScope(v, false /*diagnoseRedefinitions*/);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this always going to be a redefinition?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a new entry point, like addRedefinitionToScope(), that changes the logic around and asserts if its not a redefinition.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SGTM

Copy link
Contributor Author

@gottesmm gottesmm Apr 3, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually that is not what we want. We actually just do not want
to emit the error here. The reason why is that we are not always
going to have a redefinition But in the case where we do have the
redefinition, we only want to emit a single error (the one from
the case label). That is why for the case body decls I have wired
through the flag to ignore the error /if/ it occurs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@slavapestov ping?

}
}

SmallVector<ASTNode, 8> BodyItems;

SourceLoc StartOfBody = Tok.getLoc();
Expand Down
13 changes: 9 additions & 4 deletions lib/Parse/Scope.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ static bool checkValidOverload(const ValueDecl *D1, const ValueDecl *D2,

/// addToScope - Register the specified decl as being in the current lexical
/// scope.
void ScopeInfo::addToScope(ValueDecl *D, Parser &TheParser) {
void ScopeInfo::addToScope(ValueDecl *D, Parser &TheParser,
bool diagnoseRedefinitions) {
if (!CurScope->isResolvable())
return;

Expand All @@ -121,9 +122,13 @@ void ScopeInfo::addToScope(ValueDecl *D, Parser &TheParser) {

// If this is in a resolvable scope, diagnose redefinitions. Later
// phases will handle scopes like module-scope, etc.
if (CurScope->getDepth() >= ResolvableDepth)
return TheParser.diagnoseRedefinition(PrevDecl, D);

if (CurScope->getDepth() >= ResolvableDepth) {
if (diagnoseRedefinitions) {
return TheParser.diagnoseRedefinition(PrevDecl, D);
}
return;
}

// If this is at top-level scope, validate that the members of the overload
// set all agree.

Expand Down
Loading