Skip to content

Commit 4023199

Browse files
authored
[Sema] Diagnose unsound pointer conversions (#27695)
[Sema] Diagnose unsound pointer conversions
2 parents dad642c + 65dda6d commit 4023199

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+2363
-76
lines changed

include/swift/AST/Attr.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,10 @@ DECL_ATTR(_projectedValueProperty, ProjectedValueProperty,
496496
OnVar | UserInaccessible |
497497
ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove,
498498
89)
499+
SIMPLE_DECL_ATTR(_nonEphemeral, NonEphemeral,
500+
OnParam | UserInaccessible |
501+
ABIStableToAdd | ABIStableToRemove | APIBreakingToAdd | APIStableToRemove,
502+
90)
499503

500504
SIMPLE_DECL_ATTR(IBSegueAction, IBSegueAction,
501505
OnFunc |

include/swift/AST/Decl.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5337,6 +5337,37 @@ class ParamDecl : public VarDecl {
53375337
: flags - Flags::IsAutoClosure);
53385338
}
53395339

5340+
/// Does this parameter reject temporary pointer conversions?
5341+
bool isNonEphemeral() const {
5342+
if (getAttrs().hasAttribute<NonEphemeralAttr>())
5343+
return true;
5344+
5345+
// Only pointer parameters can be non-ephemeral.
5346+
auto ty = getInterfaceType();
5347+
if (!ty->lookThroughSingleOptionalType()->getAnyPointerElementType())
5348+
return false;
5349+
5350+
// Enum element pointer parameters are always non-ephemeral.
5351+
auto *parentDecl = getDeclContext()->getAsDecl();
5352+
if (parentDecl && isa<EnumElementDecl>(parentDecl))
5353+
return true;
5354+
5355+
return false;
5356+
}
5357+
5358+
/// Attempt to apply an implicit `@_nonEphemeral` attribute to this parameter.
5359+
void setNonEphemeralIfPossible() {
5360+
// Don't apply the attribute if this isn't a pointer param.
5361+
auto type = getInterfaceType();
5362+
if (!type->lookThroughSingleOptionalType()->getAnyPointerElementType())
5363+
return;
5364+
5365+
if (!getAttrs().hasAttribute<NonEphemeralAttr>()) {
5366+
auto &ctx = getASTContext();
5367+
getAttrs().add(new (ctx) NonEphemeralAttr(/*IsImplicit*/ true));
5368+
}
5369+
}
5370+
53405371
/// Remove the type of this varargs element designator, without the array
53415372
/// type wrapping it. A parameter like "Int..." will have formal parameter
53425373
/// type of "[Int]" and this returns "Int".

include/swift/AST/DiagnosticsSema.def

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,45 @@ ERROR(cannot_convert_argument_value_generic,none,
397397
"cannot convert value of type %0 (%1) to expected argument type %2 (%3)",
398398
(Type, StringRef, Type, StringRef))
399399

400+
// @_nonEphemeral conversion diagnostics
401+
ERROR(cannot_pass_type_to_non_ephemeral,none,
402+
"cannot pass %0 to parameter; argument #%1 must be a pointer that "
403+
"outlives the call%select{| to %3}2", (Type, unsigned, bool, DeclName))
404+
WARNING(cannot_pass_type_to_non_ephemeral_warning,none,
405+
"passing %0 to parameter, but argument #%1 should be a pointer that "
406+
"outlives the call%select{| to %3}2", (Type, unsigned, bool, DeclName))
407+
ERROR(cannot_use_inout_non_ephemeral,none,
408+
"cannot use inout expression here; argument #%0 must be a pointer that "
409+
"outlives the call%select{| to %2}1", (unsigned, bool, DeclName))
410+
WARNING(cannot_use_inout_non_ephemeral_warning,none,
411+
"inout expression creates a temporary pointer, but argument #%0 should "
412+
"be a pointer that outlives the call%select{| to %2}1",
413+
(unsigned, bool, DeclName))
414+
ERROR(cannot_construct_dangling_pointer,none,
415+
"initialization of %0 results in a dangling %select{|buffer }1pointer",
416+
(Type, unsigned))
417+
WARNING(cannot_construct_dangling_pointer_warning,none,
418+
"initialization of %0 results in a dangling %select{|buffer }1pointer",
419+
(Type, unsigned))
420+
NOTE(ephemeral_pointer_argument_conversion_note,none,
421+
"implicit argument conversion from %0 to %1 produces a pointer valid only "
422+
"for the duration of the call%select{| to %3}2",
423+
(Type, Type, bool, DeclName))
424+
NOTE(ephemeral_use_with_unsafe_pointer,none,
425+
"use 'withUnsafe%select{Bytes|MutableBytes|Pointer|MutablePointer}0' in "
426+
"order to explicitly convert argument to %select{buffer |buffer ||}0"
427+
"pointer valid for a defined scope", (unsigned))
428+
NOTE(ephemeral_use_string_with_c_string,none,
429+
"use the 'withCString' method on String in order to explicitly "
430+
"convert argument to pointer valid for a defined scope", ())
431+
NOTE(ephemeral_use_array_with_unsafe_buffer,none,
432+
"use the 'withUnsafe%select{Bytes|MutableBytes|BufferPointer|"
433+
"MutableBufferPointer}0' method on Array in order to explicitly convert "
434+
"argument to buffer pointer valid for a defined scope", (unsigned))
435+
NOTE(candidate_performs_illegal_ephemeral_conv,none,
436+
"candidate expects pointer that outlives the call for parameter #%0",
437+
(unsigned))
438+
400439
ERROR(cannot_convert_argument_value_protocol,none,
401440
"argument type %0 does not conform to expected type %1", (Type, Type))
402441
ERROR(cannot_convert_partial_argument_value_protocol,none,
@@ -2755,6 +2794,10 @@ ERROR(escaping_non_function_parameter,none,
27552794
NOTE(escaping_optional_type_argument, none,
27562795
"closure is already escaping in optional type argument", ())
27572796

2797+
// @_nonEphemeral attribute
2798+
ERROR(non_ephemeral_non_pointer_type,none,
2799+
"@_nonEphemeral attribute only applies to pointer types", ())
2800+
27582801
// NSManaged attribute
27592802
ERROR(attr_NSManaged_not_instance_member,none,
27602803
"@NSManaged only allowed on an instance property or method", ())

include/swift/AST/Types.h

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1765,13 +1765,14 @@ class TypeAliasType final
17651765
/// escaping.
17661766
class ParameterTypeFlags {
17671767
enum ParameterFlags : uint8_t {
1768-
None = 0,
1769-
Variadic = 1 << 0,
1770-
AutoClosure = 1 << 1,
1771-
OwnershipShift = 2,
1772-
Ownership = 7 << OwnershipShift,
1773-
1774-
NumBits = 5
1768+
None = 0,
1769+
Variadic = 1 << 0,
1770+
AutoClosure = 1 << 1,
1771+
NonEphemeral = 1 << 2,
1772+
OwnershipShift = 3,
1773+
Ownership = 7 << OwnershipShift,
1774+
1775+
NumBits = 6
17751776
};
17761777
OptionSet<ParameterFlags> value;
17771778
static_assert(NumBits < 8*sizeof(OptionSet<ParameterFlags>), "overflowed");
@@ -1784,19 +1785,21 @@ class ParameterTypeFlags {
17841785
return ParameterTypeFlags(OptionSet<ParameterFlags>(raw));
17851786
}
17861787

1787-
ParameterTypeFlags(bool variadic, bool autoclosure,
1788+
ParameterTypeFlags(bool variadic, bool autoclosure, bool nonEphemeral,
17881789
ValueOwnership ownership)
17891790
: value((variadic ? Variadic : 0) | (autoclosure ? AutoClosure : 0) |
1791+
(nonEphemeral ? NonEphemeral : 0) |
17901792
uint8_t(ownership) << OwnershipShift) {}
17911793

17921794
/// Create one from what's present in the parameter type
17931795
inline static ParameterTypeFlags
17941796
fromParameterType(Type paramTy, bool isVariadic, bool isAutoClosure,
1795-
ValueOwnership ownership);
1797+
bool isNonEphemeral, ValueOwnership ownership);
17961798

17971799
bool isNone() const { return !value; }
17981800
bool isVariadic() const { return value.contains(Variadic); }
17991801
bool isAutoClosure() const { return value.contains(AutoClosure); }
1802+
bool isNonEphemeral() const { return value.contains(NonEphemeral); }
18001803
bool isInOut() const { return getValueOwnership() == ValueOwnership::InOut; }
18011804
bool isShared() const { return getValueOwnership() == ValueOwnership::Shared;}
18021805
bool isOwned() const { return getValueOwnership() == ValueOwnership::Owned; }
@@ -1836,6 +1839,12 @@ class ParameterTypeFlags {
18361839
: value - ParameterTypeFlags::AutoClosure);
18371840
}
18381841

1842+
ParameterTypeFlags withNonEphemeral(bool isNonEphemeral) const {
1843+
return ParameterTypeFlags(isNonEphemeral
1844+
? value | ParameterTypeFlags::NonEphemeral
1845+
: value - ParameterTypeFlags::NonEphemeral);
1846+
}
1847+
18391848
bool operator ==(const ParameterTypeFlags &other) const {
18401849
return value.toRaw() == other.value.toRaw();
18411850
}
@@ -1902,6 +1911,7 @@ class YieldTypeFlags {
19021911
ParameterTypeFlags asParamFlags() const {
19031912
return ParameterTypeFlags(/*variadic*/ false,
19041913
/*autoclosure*/ false,
1914+
/*nonEphemeral*/ false,
19051915
getValueOwnership());
19061916
}
19071917

@@ -2771,6 +2781,9 @@ class AnyFunctionType : public TypeBase {
27712781
/// Whether the parameter is marked 'owned'
27722782
bool isOwned() const { return Flags.isOwned(); }
27732783

2784+
/// Whether the parameter is marked '@_nonEphemeral'
2785+
bool isNonEphemeral() const { return Flags.isNonEphemeral(); }
2786+
27742787
ValueOwnership getValueOwnership() const {
27752788
return Flags.getValueOwnership();
27762789
}
@@ -5610,7 +5623,7 @@ inline TupleTypeElt TupleTypeElt::getWithType(Type T) const {
56105623
/// Create one from what's present in the parameter decl and type
56115624
inline ParameterTypeFlags
56125625
ParameterTypeFlags::fromParameterType(Type paramTy, bool isVariadic,
5613-
bool isAutoClosure,
5626+
bool isAutoClosure, bool isNonEphemeral,
56145627
ValueOwnership ownership) {
56155628
// FIXME(Remove InOut): The last caller that needs this is argument
56165629
// decomposition. Start by enabling the assertion there and fixing up those
@@ -5621,7 +5634,7 @@ ParameterTypeFlags::fromParameterType(Type paramTy, bool isVariadic,
56215634
ownership == ValueOwnership::InOut);
56225635
ownership = ValueOwnership::InOut;
56235636
}
5624-
return {isVariadic, isAutoClosure, ownership};
5637+
return {isVariadic, isAutoClosure, isNonEphemeral, ownership};
56255638
}
56265639

56275640
inline const Type *BoundGenericType::getTrailingObjectsPointer() const {

include/swift/Basic/LangOptions.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,10 @@ namespace swift {
215215
/// operator protocol designator feature.
216216
bool SolverEnableOperatorDesignatedTypes = false;
217217

218+
/// Whether to diagnose an ephemeral to non-ephemeral conversion as an
219+
/// error.
220+
bool DiagnoseInvalidEphemeralnessAsError = false;
221+
218222
/// The maximum depth to which to test decl circularity.
219223
unsigned MaxCircularityDepth = 500;
220224

include/swift/Option/FrontendOptions.td

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,14 @@ def solver_enable_operator_designated_types :
431431
Flag<["-"], "solver-enable-operator-designated-types">,
432432
HelpText<"Enable operator designated types in constraint solver">;
433433

434+
def enable_invalid_ephemeralness_as_error :
435+
Flag<["-"], "enable-invalid-ephemeralness-as-error">,
436+
HelpText<"Diagnose invalid ephemeral to non-ephemeral conversions as errors">;
437+
438+
def disable_invalid_ephemeralness_as_error :
439+
Flag<["-"], "disable-invalid-ephemeralness-as-error">,
440+
HelpText<"Diagnose invalid ephemeral to non-ephemeral conversions as warnings">;
441+
434442
def switch_checking_invocation_threshold_EQ : Joined<["-"],
435443
"switch-checking-invocation-threshold=">;
436444

lib/AST/ASTContext.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2915,7 +2915,7 @@ void AnyFunctionType::decomposeInput(
29152915
default:
29162916
result.emplace_back(type->getInOutObjectType(), Identifier(),
29172917
ParameterTypeFlags::fromParameterType(
2918-
type, false, false, ValueOwnership::Default));
2918+
type, false, false, false, ValueOwnership::Default));
29192919
return;
29202920
}
29212921
}

lib/AST/ASTDumper.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -988,6 +988,9 @@ namespace {
988988
if (P->isAutoClosure())
989989
OS << " autoclosure";
990990

991+
if (P->isNonEphemeral())
992+
OS << " nonEphemeral";
993+
991994
if (P->getDefaultArgumentKind() != DefaultArgumentKind::None) {
992995
printField("default_arg",
993996
getDefaultArgumentKindString(P->getDefaultArgumentKind()));
@@ -3306,6 +3309,7 @@ namespace {
33063309
void dumpParameterFlags(ParameterTypeFlags paramFlags) {
33073310
printFlag(paramFlags.isVariadic(), "vararg");
33083311
printFlag(paramFlags.isAutoClosure(), "autoclosure");
3312+
printFlag(paramFlags.isNonEphemeral(), "nonEphemeral");
33093313
switch (paramFlags.getValueOwnership()) {
33103314
case ValueOwnership::Default: break;
33113315
case ValueOwnership::Owned: printFlag("owned"); break;

lib/AST/Decl.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2608,7 +2608,9 @@ static Type mapSignatureFunctionType(ASTContext &ctx, Type type,
26082608
SmallVector<AnyFunctionType::Param, 4> newParams;
26092609
for (const auto &param : funcTy->getParams()) {
26102610
auto newParamType = mapSignatureParamType(ctx, param.getPlainType());
2611-
ParameterTypeFlags newFlags = param.getParameterFlags();
2611+
2612+
// Don't allow overloading by @_nonEphemeral.
2613+
auto newFlags = param.getParameterFlags().withNonEphemeral(false);
26122614

26132615
// For the 'self' of a method, strip off 'inout'.
26142616
if (isMethod) {
@@ -5982,6 +5984,7 @@ AnyFunctionType::Param ParamDecl::toFunctionParam(Type type) const {
59825984
auto flags = ParameterTypeFlags::fromParameterType(type,
59835985
isVariadic(),
59845986
isAutoClosure(),
5987+
isNonEphemeral(),
59855988
getValueOwnership());
59865989
return AnyFunctionType::Param(type, label, flags);
59875990
}

lib/ClangImporter/ImportDecl.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1411,6 +1411,10 @@ createValueConstructor(ClangImporter::Implementation &Impl,
14111411
param->setSpecifier(ParamSpecifier::Default);
14121412
param->setInterfaceType(var->getInterfaceType());
14131413
Impl.recordImplicitUnwrapForDecl(param, var->isImplicitlyUnwrappedOptional());
1414+
1415+
// Don't allow the parameter to accept temporary pointer conversions.
1416+
param->setNonEphemeralIfPossible();
1417+
14141418
valueParameters.push_back(param);
14151419
}
14161420

lib/Frontend/CompilerInvocation.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,10 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
276276

277277
Opts.EnableOperatorDesignatedTypes |=
278278
Args.hasArg(OPT_enable_operator_designated_types);
279-
279+
280+
Opts.DiagnoseInvalidEphemeralnessAsError |=
281+
Args.hasArg(OPT_enable_invalid_ephemeralness_as_error);
282+
280283
// Always enable operator designated types for the standard library.
281284
Opts.EnableOperatorDesignatedTypes |= FrontendOpts.ParseStdlib;
282285

0 commit comments

Comments
 (0)