Skip to content

Commit a093a68

Browse files
committed
[ClangImporter] Fix NS_CLOSED_ENUM to validate raw values
Generate switch-based validation for @Frozen enum init?(rawValue:) instead of using unchecked reinterpretCast. This ensures only declared enum cases are accepted, returning nil for invalid raw values. For non-frozen enums (NS_ENUM), preserve existing reinterpretCast behavior for C compatibility. Fixes #85701
1 parent 1af4e51 commit a093a68

File tree

1 file changed

+130
-4
lines changed

1 file changed

+130
-4
lines changed

lib/ClangImporter/SwiftDeclSynthesizer.cpp

Lines changed: 130 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1247,7 +1247,52 @@ SwiftDeclSynthesizer::makeIndirectFieldAccessors(
12471247

12481248
// MARK: Enum RawValue initializers
12491249

1250+
/// Clone a literal expression for use in pattern matching.
1251+
/// Based on cloneRawLiteralExpr from DerivedConformanceRawRepresentable.cpp
1252+
static LiteralExpr *cloneRawLiteralExpr(ASTContext &C, LiteralExpr *expr) {
1253+
LiteralExpr *clone;
1254+
if (auto intLit = dyn_cast<IntegerLiteralExpr>(expr)) {
1255+
clone = new (C) IntegerLiteralExpr(intLit->getDigitsText(), expr->getLoc(),
1256+
/*implicit*/ true);
1257+
if (intLit->isNegative())
1258+
cast<IntegerLiteralExpr>(clone)->setNegative(expr->getLoc());
1259+
} else if (isa<NilLiteralExpr>(expr)) {
1260+
clone = new (C) NilLiteralExpr(expr->getLoc());
1261+
} else if (auto stringLit = dyn_cast<StringLiteralExpr>(expr)) {
1262+
clone = new (C) StringLiteralExpr(stringLit->getValue(), expr->getLoc());
1263+
} else if (auto floatLit = dyn_cast<FloatLiteralExpr>(expr)) {
1264+
clone = new (C) FloatLiteralExpr(floatLit->getDigitsText(), expr->getLoc(),
1265+
/*implicit*/ true);
1266+
if (floatLit->isNegative())
1267+
cast<FloatLiteralExpr>(clone)->setNegative(expr->getLoc());
1268+
} else {
1269+
llvm_unreachable("invalid raw literal expr");
1270+
}
1271+
clone->setImplicit();
1272+
return clone;
1273+
}
1274+
12501275
/// Synthesize the body of \c init?(rawValue:RawType) for an imported enum.
1276+
///
1277+
/// For non-frozen (open) enums, this generates:
1278+
/// init?(rawValue: RawType) {
1279+
/// self = Builtin.reinterpretCast(rawValue)
1280+
/// }
1281+
/// This allows arbitrary raw values for C compatibility.
1282+
///
1283+
/// For frozen (closed) enums, this generates:
1284+
/// init?(rawValue: RawType) {
1285+
/// switch rawValue {
1286+
/// case <value1>:
1287+
/// self = Builtin.reinterpretCast(rawValue)
1288+
/// case <value2>:
1289+
/// self = Builtin.reinterpretCast(rawValue)
1290+
/// ...
1291+
/// default:
1292+
/// return nil
1293+
/// }
1294+
/// }
1295+
/// This ensures that only declared raw values are accepted.
12511296
static std::pair<BraceStmt *, bool>
12521297
synthesizeEnumRawValueConstructorBody(AbstractFunctionDecl *afd,
12531298
void *context) {
@@ -1290,11 +1335,92 @@ synthesizeEnumRawValueConstructorBody(AbstractFunctionDecl *afd,
12901335
/*implicit*/ true);
12911336
assign->setType(TupleType::getEmpty(ctx));
12921337

1293-
auto *ret = ReturnStmt::createImplicit(ctx, /*expr*/ nullptr);
1338+
// Check if the enum is frozen (closed). If so, we need to validate
1339+
// that the raw value is one of the declared cases.
1340+
bool isFrozen = enumDecl->getAttrs().hasAttribute<FrozenAttr>();
12941341

1295-
auto body = BraceStmt::create(ctx, SourceLoc(), {assign, ret}, SourceLoc(),
1296-
/*implicit*/ true);
1297-
return {body, /*isTypeChecked=*/true};
1342+
if (isFrozen) {
1343+
// For frozen enums, generate a switch statement to validate the raw value
1344+
SmallVector<CaseStmt *, 8> cases;
1345+
1346+
// Build cases for each enum element
1347+
for (auto *elt : enumDecl->getAllElements()) {
1348+
// Get the raw value literal for this element
1349+
auto rawValueExpr = elt->getStructuralRawValueExpr();
1350+
if (!rawValueExpr)
1351+
continue;
1352+
1353+
// Clone the raw value expression for pattern matching
1354+
auto *litExpr = cloneRawLiteralExpr(ctx, cast<LiteralExpr>(rawValueExpr));
1355+
auto *litPat = ExprPattern::createImplicit(ctx, litExpr, ctorDecl);
1356+
1357+
// Create case label
1358+
auto labelItem = CaseLabelItem(litPat);
1359+
1360+
// Create the assignment statement for this case
1361+
auto *caseBodySelfRef = new (ctx) DeclRefExpr(selfDecl, DeclNameLoc(),
1362+
/*implicit*/ true);
1363+
caseBodySelfRef->setType(LValueType::get(selfDecl->getTypeInContext()));
1364+
1365+
auto *caseParamRef = new (ctx) DeclRefExpr(param, DeclNameLoc(),
1366+
/*implicit*/ true);
1367+
caseParamRef->setType(param->getTypeInContext());
1368+
1369+
auto *caseArgList = ArgumentList::forImplicitUnlabeled(ctx, {caseParamRef});
1370+
auto *caseReinterpretCastRef =
1371+
new (ctx) DeclRefExpr(concreteDeclRef, DeclNameLoc(), /*implicit*/ true);
1372+
caseReinterpretCastRef->setType(
1373+
FunctionType::get({FunctionType::Param(rawTy)}, enumTy, info));
1374+
1375+
auto caseReinterpreted =
1376+
CallExpr::createImplicit(ctx, caseReinterpretCastRef, caseArgList);
1377+
caseReinterpreted->setType(enumTy);
1378+
caseReinterpreted->setThrows(nullptr);
1379+
1380+
auto *caseAssign = new (ctx) AssignExpr(caseBodySelfRef, SourceLoc(),
1381+
caseReinterpreted, /*implicit*/ true);
1382+
caseAssign->setType(TupleType::getEmpty(ctx));
1383+
1384+
auto *caseBody = BraceStmt::create(ctx, SourceLoc(), {caseAssign},
1385+
SourceLoc(), /*implicit*/ true);
1386+
1387+
auto *caseStmt = CaseStmt::createImplicit(ctx, CaseParentKind::Switch,
1388+
{labelItem}, caseBody);
1389+
cases.push_back(caseStmt);
1390+
}
1391+
1392+
// Add default case that returns nil
1393+
auto *defaultPattern = AnyPattern::createImplicit(ctx);
1394+
auto defaultLabelItem = CaseLabelItem(defaultPattern);
1395+
1396+
auto *failStmt = new (ctx) FailStmt(SourceLoc(), SourceLoc(), /*implicit*/ true);
1397+
auto *defaultBody = BraceStmt::create(ctx, SourceLoc(), {failStmt},
1398+
SourceLoc(), /*implicit*/ true);
1399+
1400+
auto *defaultCase = CaseStmt::createImplicit(ctx, CaseParentKind::Switch,
1401+
{defaultLabelItem}, defaultBody);
1402+
cases.push_back(defaultCase);
1403+
1404+
// Create the switch statement
1405+
auto *switchParamRef = new (ctx) DeclRefExpr(param, DeclNameLoc(),
1406+
/*implicit*/ true);
1407+
switchParamRef->setType(param->getTypeInContext());
1408+
1409+
auto *switchStmt = SwitchStmt::create(LabeledStmtInfo(), SourceLoc(),
1410+
switchParamRef, SourceLoc(),
1411+
cases, SourceLoc(), SourceLoc(), ctx);
1412+
1413+
auto body = BraceStmt::create(ctx, SourceLoc(), {switchStmt}, SourceLoc(),
1414+
/*implicit*/ true);
1415+
return {body, /*isTypeChecked=*/false};
1416+
} else {
1417+
// For non-frozen enums, use the simple reinterpret cast approach
1418+
auto *ret = ReturnStmt::createImplicit(ctx, /*expr*/ nullptr);
1419+
1420+
auto body = BraceStmt::create(ctx, SourceLoc(), {assign, ret}, SourceLoc(),
1421+
/*implicit*/ true);
1422+
return {body, /*isTypeChecked=*/true};
1423+
}
12981424
}
12991425

13001426
ConstructorDecl *

0 commit comments

Comments
 (0)