Skip to content

Commit 429128c

Browse files
committed
[ClangImporter] Fix import of aliased enum cases
When a C enum has multiple constants with the same value, ClangImporter selects one of them to import as an `EnumElementDecl` and imports the others as `VarDecl` “aliases” of that one. This helps preserve the invariant that each case of an enum has a unique raw value and is distinct for exhaustiveness checking. However, a bug in that logic could sometimes cause the canonical case to be imported *twice*—once as an `EnumElementDecl` and again as a `VarDecl` alias. In this situation, the `EnumElementDecl` was not added to the enum’s member list, resulting in a malformed AST. This bug has apparently been present since early 2017 (!), but it seems to have been harmless until recently, when the `ComputeSideEffects` SIL pass recently became sensitive to it (probably because of either swiftlang#79872 or swiftlang#80263). Correct this problem by modifying the memoization logic to retrieve the canonical case’s clang-side constant so the subsequent check will succeed. Additionally change a variable name and add comments to help clarify this code for future maintainers. In lieu of adding new unit tests, this commit adds a (slightly expensive) conditional assertion to catch this kind of AST malformation. There are actually about twenty tests that will fail with just the assertion and not the fix, but I’ve updated one of them to enable the assertion even in release mode. Fixes rdar://148213237. Followup to swiftlang#80487, which added related assertions to the SIL layer.
1 parent c8609c7 commit 429128c

File tree

2 files changed

+48
-8
lines changed

2 files changed

+48
-8
lines changed

lib/ClangImporter/ImportDecl.cpp

+45-5
Original file line numberDiff line numberDiff line change
@@ -1864,10 +1864,28 @@ namespace {
18641864
break;
18651865
}
18661866

1867+
/// A table mapping each raw value used in this enum to the clang or
1868+
/// Swift decl for the "canonical" constant corresponding to that raw
1869+
/// value. The clang decls represent cases that haven't yet been imported;
1870+
/// the Swift decls represent cases that have been imported before.
1871+
///
1872+
/// The problem we are trying to solve here is that C allows several
1873+
/// constants in the same enum to have the same raw value, but Swift does
1874+
/// not. We must therefore resolve collisions by selecting one case to be
1875+
/// the "canonical" one that will be imported as an \c EnumElementDecl
1876+
/// and importing the others as static \c VarDecl aliases of it. This
1877+
/// map knows which constants are canonical and can map a constant's raw
1878+
/// value to its corresponding canonical constant.
1879+
///
1880+
/// Note that unavailable constants don't get inserted into this table,
1881+
/// so if an unavailable constant has no available alias, it simply won't
1882+
/// be present here. (Potential raw value conflicts doesn't really matter
1883+
/// for them because they will be imported as unavailable anyway.)
18671884
llvm::SmallDenseMap<llvm::APSInt,
18681885
PointerUnion<const clang::EnumConstantDecl *,
18691886
EnumElementDecl *>, 8> canonicalEnumConstants;
18701887

1888+
// Fill in `canonicalEnumConstants` if it will be used.
18711889
if (enumKind == EnumKind::NonFrozenEnum ||
18721890
enumKind == EnumKind::FrozenEnum) {
18731891
for (auto constant : decl->enumerators()) {
@@ -1942,24 +1960,32 @@ namespace {
19421960
SwiftDeclConverter(Impl, getActiveSwiftVersion())
19431961
.importEnumCase(constant, decl, cast<EnumDecl>(result));
19441962
} else {
1945-
const clang::EnumConstantDecl *unimported =
1963+
// Will initially be nullptr if `canonicalCaseIter` points to a
1964+
// memoized result.
1965+
const clang::EnumConstantDecl *canonConstant =
19461966
canonicalCaseIter->
19471967
second.dyn_cast<const clang::EnumConstantDecl *>();
19481968

1949-
// Import the canonical enumerator for this case first.
1950-
if (unimported) {
1969+
// First, either import the canonical constant for this case,
1970+
// or extract the memoized result of a previous import (and use it
1971+
// to populate `canonConstant`).
1972+
if (canonConstant) {
19511973
enumeratorDecl = SwiftDeclConverter(Impl, getActiveSwiftVersion())
1952-
.importEnumCase(unimported, decl, cast<EnumDecl>(result));
1974+
.importEnumCase(canonConstant, decl, cast<EnumDecl>(result));
19531975
if (enumeratorDecl) {
1976+
// Memoize so we end up in the `else` branch next time.
19541977
canonicalCaseIter->getSecond() =
19551978
cast<EnumElementDecl>(enumeratorDecl);
19561979
}
19571980
} else {
19581981
enumeratorDecl =
19591982
canonicalCaseIter->second.get<EnumElementDecl *>();
1983+
canonConstant =
1984+
cast<clang::EnumConstantDecl>(enumeratorDecl->getClangDecl());
19601985
}
19611986

1962-
if (unimported != constant && enumeratorDecl) {
1987+
// If `constant` wasn't the `canonConstant`, import it as an alias.
1988+
if (canonConstant != constant && enumeratorDecl) {
19631989
ImportedName importedName =
19641990
Impl.importFullName(constant, getActiveSwiftVersion());
19651991
Identifier name = importedName.getBaseIdentifier(Impl.SwiftContext);
@@ -1975,6 +2001,7 @@ namespace {
19752001
}
19762002
}
19772003

2004+
// Now import each of the constant's alternate names.
19782005
Impl.forEachDistinctName(constant,
19792006
[&](ImportedName newName,
19802007
ImportNameVersion nameVersion) -> bool {
@@ -2025,6 +2052,19 @@ namespace {
20252052
}
20262053
}
20272054

2055+
// We don't always add an imported canonical constant to the enum's
2056+
// members right away, but we should have by the time we leave the loop.
2057+
// Verify that they are all in the enum's member list. (rdar://148213237)
2058+
if (CONDITIONAL_ASSERT_enabled()) {
2059+
for (const auto &entry : canonicalEnumConstants) {
2060+
auto importedCase = entry.second.dyn_cast<EnumElementDecl *>();
2061+
if (!importedCase)
2062+
continue;
2063+
2064+
ASSERT(llvm::is_contained(result->getCurrentMembers(), importedCase));
2065+
}
2066+
}
2067+
20282068
return result;
20292069
}
20302070

test/ClangImporter/enum.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck %s -verify
2-
// RUN: not %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck %s 2>&1 | %FileCheck %s
1+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck %s -verify -compiler-assertions
2+
// RUN: not %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck %s -compiler-assertions 2>&1 | %FileCheck %s
33
// -- Check that we can successfully round-trip.
4-
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -D IRGEN -emit-ir -primary-file %s | %FileCheck -check-prefix=CHECK-IR %s
4+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -D IRGEN -emit-ir -primary-file %s -compiler-assertions | %FileCheck -check-prefix=CHECK-IR %s
55

66
// REQUIRES: objc_interop
77

0 commit comments

Comments
 (0)