Skip to content

Commit 7f7e41b

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 c3f4e5a commit 7f7e41b

File tree

1 file changed

+102
-0
lines changed

1 file changed

+102
-0
lines changed

lib/ClangImporter/SwiftDeclSynthesizer.cpp

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1247,7 +1247,49 @@ 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>, <value2>, ...:
1287+
/// self = Builtin.reinterpretCast(rawValue)
1288+
/// default:
1289+
/// return nil
1290+
/// }
1291+
/// }
1292+
/// This ensures that only declared raw values are accepted.
12511293
static std::pair<BraceStmt *, bool>
12521294
synthesizeEnumRawValueConstructorBody(AbstractFunctionDecl *afd,
12531295
void *context) {
@@ -1290,10 +1332,70 @@ synthesizeEnumRawValueConstructorBody(AbstractFunctionDecl *afd,
12901332
/*implicit*/ true);
12911333
assign->setType(TupleType::getEmpty(ctx));
12921334

1335+
// Check if the enum is frozen (closed). If so, we need to validate
1336+
// that the raw value is one of the declared cases.
1337+
bool isFrozen = enumDecl->getAttrs().hasAttribute<FrozenAttr>();
1338+
1339+
if (isFrozen) {
1340+
// For frozen enums, generate a switch statement to validate the raw value.
1341+
1342+
// Collect all case labels for valid enum values
1343+
SmallVector<CaseLabelItem, 8> validCaseLabels;
1344+
for (auto *elt : enumDecl->getAllElements()) {
1345+
// Get the raw value literal for this element
1346+
auto rawValueExpr = elt->getStructuralRawValueExpr();
1347+
if (!rawValueExpr)
1348+
continue;
1349+
1350+
// Clone the raw value expression for pattern matching
1351+
auto *litExpr = cloneRawLiteralExpr(ctx, cast<LiteralExpr>(rawValueExpr));
1352+
auto *litPat = ExprPattern::createImplicit(ctx, litExpr, ctorDecl);
1353+
1354+
// Add to the list of valid case labels
1355+
validCaseLabels.push_back(CaseLabelItem(litPat));
1356+
}
1357+
1358+
// Create a single case statement with all valid raw values
1359+
// All valid values perform the same action: reinterpret cast
1360+
auto *caseBody = BraceStmt::create(ctx, SourceLoc(), {assign},
1361+
SourceLoc(), /*implicit*/ true);
1362+
auto *validCase = CaseStmt::createImplicit(ctx, CaseParentKind::Switch,
1363+
validCaseLabels, caseBody);
1364+
1365+
// Create default case that returns nil
1366+
auto *defaultPattern = AnyPattern::createImplicit(ctx);
1367+
auto defaultLabelItem = CaseLabelItem(defaultPattern);
1368+
1369+
auto *failStmt = new (ctx) FailStmt(SourceLoc(), SourceLoc(), /*implicit*/ true);
1370+
auto *defaultBody = BraceStmt::create(ctx, SourceLoc(), {failStmt},
1371+
SourceLoc(), /*implicit*/ true);
1372+
1373+
auto *defaultCase = CaseStmt::createImplicit(ctx, CaseParentKind::Switch,
1374+
{defaultLabelItem}, defaultBody);
1375+
1376+
// Create the switch statement
1377+
auto *switchParamRef = new (ctx) DeclRefExpr(param, DeclNameLoc(),
1378+
/*implicit*/ true);
1379+
switchParamRef->setType(param->getTypeInContext());
1380+
1381+
auto *switchStmt = SwitchStmt::create(LabeledStmtInfo(), SourceLoc(),
1382+
switchParamRef, SourceLoc(),
1383+
{validCase, defaultCase}, SourceLoc(), SourceLoc(), ctx);
1384+
1385+
auto body = BraceStmt::create(ctx, SourceLoc(), {switchStmt}, SourceLoc(),
1386+
/*implicit*/ true);
1387+
// Return isTypeChecked=false because the switch statement contains patterns
1388+
// and expressions that need type inference and semantic analysis.
1389+
return {body, /*isTypeChecked=*/false};
1390+
}
1391+
1392+
// For non-frozen enums, use the simple reinterpret cast approach
12931393
auto *ret = ReturnStmt::createImplicit(ctx, /*expr*/ nullptr);
12941394

12951395
auto body = BraceStmt::create(ctx, SourceLoc(), {assign, ret}, SourceLoc(),
12961396
/*implicit*/ true);
1397+
// Return isTypeChecked=true because all types are explicitly set
1398+
// and no type inference is needed.
12971399
return {body, /*isTypeChecked=*/true};
12981400
}
12991401

0 commit comments

Comments
 (0)