Skip to content

Commit 3df40aa

Browse files
committed
[Sema] Diagnose unsound pointer conversions
Diagnose ephemeral conversions that are passed to @_nonEphemeral parameters. Currently, this defaults to a warning with a frontend flag to upgrade to an error. Hopefully this will become an error by default in a future language version.
1 parent e809460 commit 3df40aa

19 files changed

+1447
-4
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,35 @@ 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+
NOTE(ephemeral_pointer_argument_conversion_note,none,
415+
"implicit argument conversion from %0 to %1 produces a pointer valid only "
416+
"for the duration of the call%select{| to %3}2",
417+
(Type, Type, bool, DeclName))
418+
NOTE(ephemeral_use_with_unsafe_pointer,none,
419+
"use 'withUnsafe%select{Bytes|MutableBytes|Pointer|MutablePointer}0' in "
420+
"order to explicitly convert argument to %select{buffer |buffer ||}0"
421+
"pointer valid for a defined scope", (unsigned))
422+
NOTE(ephemeral_use_string_with_c_string,none,
423+
"use the 'withCString' method on String in order to explicitly "
424+
"convert argument to pointer valid for a defined scope", ())
425+
NOTE(ephemeral_use_array_with_unsafe_buffer,none,
426+
"use the 'withUnsafe%select{Bytes|MutableBytes|BufferPointer|"
427+
"MutableBufferPointer}0' method on Array in order to explicitly convert "
428+
"argument to buffer pointer valid for a defined scope", (unsigned))
400429
ERROR(cannot_convert_argument_value_protocol,none,
401430
"argument type %0 does not conform to expected type %1", (Type, Type))
402431
ERROR(cannot_convert_partial_argument_value_protocol,none,

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: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,10 @@ 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 diagnose_invalid_ephemeralness_as_error :
435+
Flag<["-"], "diagnose-invalid-ephemeralness-as-error">,
436+
HelpText<"Diagnose invalid ephemeral to non-ephemeral conversions as errors">;
437+
434438
def switch_checking_invocation_threshold_EQ : Joined<["-"],
435439
"switch-checking-invocation-threshold=">;
436440

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_diagnose_invalid_ephemeralness_as_error);
282+
280283
// Always enable operator designated types for the standard library.
281284
Opts.EnableOperatorDesignatedTypes |= FrontendOpts.ParseStdlib;
282285

lib/Sema/CSDiagnostics.cpp

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5426,3 +5426,134 @@ bool ExpandArrayIntoVarargsFailure::diagnoseAsNote() {
54265426
}
54275427
return false;
54285428
}
5429+
5430+
void NonEphemeralConversionFailure::emitSuggestionNotes() const {
5431+
auto getPointerKind = [](Type ty) -> PointerTypeKind {
5432+
PointerTypeKind pointerKind;
5433+
auto pointeeType = ty->lookThroughSingleOptionalType()
5434+
->getAnyPointerElementType(pointerKind);
5435+
assert(pointeeType && "Expected a pointer!");
5436+
(void)pointeeType;
5437+
5438+
return pointerKind;
5439+
};
5440+
5441+
// This must stay in sync with diag::ephemeral_use_array_with_unsafe_buffer
5442+
// and diag::ephemeral_use_with_unsafe_pointer.
5443+
enum AlternativeKind {
5444+
AK_Raw = 0,
5445+
AK_MutableRaw,
5446+
AK_Typed,
5447+
AK_MutableTyped,
5448+
};
5449+
5450+
auto getAlternativeKind = [&]() -> Optional<AlternativeKind> {
5451+
switch (getPointerKind(getParamType())) {
5452+
case PTK_UnsafeRawPointer:
5453+
return AK_Raw;
5454+
case PTK_UnsafeMutableRawPointer:
5455+
return AK_MutableRaw;
5456+
case PTK_UnsafePointer:
5457+
return AK_Typed;
5458+
case PTK_UnsafeMutablePointer:
5459+
return AK_MutableTyped;
5460+
case PTK_AutoreleasingUnsafeMutablePointer:
5461+
return None;
5462+
}
5463+
};
5464+
5465+
// First emit a note about the implicit conversion only lasting for the
5466+
// duration of the call.
5467+
auto *argExpr = getArgExpr();
5468+
emitDiagnostic(argExpr->getLoc(),
5469+
diag::ephemeral_pointer_argument_conversion_note,
5470+
getArgType(), getParamType(), getCallee(), getCalleeFullName())
5471+
.highlight(argExpr->getSourceRange());
5472+
5473+
// Then try to find a suitable alternative.
5474+
switch (ConversionKind) {
5475+
case ConversionRestrictionKind::ArrayToPointer: {
5476+
// Don't suggest anything for optional arrays, as there's currently no
5477+
// direct alternative.
5478+
if (getArgType()->getOptionalObjectType())
5479+
break;
5480+
5481+
// We can suggest using withUnsafe[Mutable][Bytes/BufferPointer].
5482+
if (auto alternative = getAlternativeKind())
5483+
emitDiagnostic(argExpr->getLoc(),
5484+
diag::ephemeral_use_array_with_unsafe_buffer,
5485+
*alternative);
5486+
break;
5487+
}
5488+
case ConversionRestrictionKind::StringToPointer: {
5489+
// Don't suggest anything for optional strings, as there's currently no
5490+
// direct alternative.
5491+
if (getArgType()->getOptionalObjectType())
5492+
break;
5493+
5494+
// We can suggest withCString as long as the resulting pointer is
5495+
// immutable.
5496+
switch (getPointerKind(getParamType())) {
5497+
case PTK_UnsafePointer:
5498+
case PTK_UnsafeRawPointer:
5499+
emitDiagnostic(argExpr->getLoc(),
5500+
diag::ephemeral_use_string_with_c_string);
5501+
break;
5502+
case PTK_UnsafeMutableRawPointer:
5503+
case PTK_UnsafeMutablePointer:
5504+
case PTK_AutoreleasingUnsafeMutablePointer:
5505+
// There's nothing really sensible we can suggest for a mutable pointer.
5506+
break;
5507+
}
5508+
break;
5509+
}
5510+
case ConversionRestrictionKind::InoutToPointer:
5511+
// For an arbitrary inout-to-pointer, we can suggest
5512+
// withUnsafe[Mutable][Bytes/Pointer].
5513+
if (auto alternative = getAlternativeKind())
5514+
emitDiagnostic(argExpr->getLoc(), diag::ephemeral_use_with_unsafe_pointer,
5515+
*alternative);
5516+
break;
5517+
case ConversionRestrictionKind::DeepEquality:
5518+
case ConversionRestrictionKind::Superclass:
5519+
case ConversionRestrictionKind::Existential:
5520+
case ConversionRestrictionKind::MetatypeToExistentialMetatype:
5521+
case ConversionRestrictionKind::ExistentialMetatypeToMetatype:
5522+
case ConversionRestrictionKind::ValueToOptional:
5523+
case ConversionRestrictionKind::OptionalToOptional:
5524+
case ConversionRestrictionKind::ClassMetatypeToAnyObject:
5525+
case ConversionRestrictionKind::ExistentialMetatypeToAnyObject:
5526+
case ConversionRestrictionKind::ProtocolMetatypeToProtocolClass:
5527+
case ConversionRestrictionKind::PointerToPointer:
5528+
case ConversionRestrictionKind::ArrayUpcast:
5529+
case ConversionRestrictionKind::DictionaryUpcast:
5530+
case ConversionRestrictionKind::SetUpcast:
5531+
case ConversionRestrictionKind::HashableToAnyHashable:
5532+
case ConversionRestrictionKind::CFTollFreeBridgeToObjC:
5533+
case ConversionRestrictionKind::ObjCTollFreeBridgeToCF:
5534+
llvm_unreachable("Expected an ephemeral conversion!");
5535+
}
5536+
}
5537+
5538+
bool NonEphemeralConversionFailure::diagnoseAsError() {
5539+
auto *argExpr = getArgExpr();
5540+
if (isa<InOutExpr>(argExpr)) {
5541+
auto diagID = DowngradeToWarning
5542+
? diag::cannot_use_inout_non_ephemeral_warning
5543+
: diag::cannot_use_inout_non_ephemeral;
5544+
5545+
emitDiagnostic(argExpr->getLoc(), diagID, getArgPosition(), getCallee(),
5546+
getCalleeFullName())
5547+
.highlight(argExpr->getSourceRange());
5548+
} else {
5549+
auto diagID = DowngradeToWarning
5550+
? diag::cannot_pass_type_to_non_ephemeral_warning
5551+
: diag::cannot_pass_type_to_non_ephemeral;
5552+
5553+
emitDiagnostic(argExpr->getLoc(), diagID, getArgType(), getArgPosition(),
5554+
getCallee(), getCalleeFullName())
5555+
.highlight(argExpr->getSourceRange());
5556+
}
5557+
emitSuggestionNotes();
5558+
return true;
5559+
}

lib/Sema/CSDiagnostics.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1910,6 +1910,36 @@ class MissingForcedDowncastFailure final : public ContextualFailure {
19101910
bool diagnoseAsError() override;
19111911
};
19121912

1913+
/// Diagnose the invalid conversion of a temporary pointer argument generated
1914+
/// from an X-to-pointer conversion to an @_nonEphemeral parameter.
1915+
///
1916+
/// ```swift
1917+
/// func foo(@_nonEphemeral _ ptr: UnsafePointer<Int>) {}
1918+
///
1919+
/// foo([1, 2, 3])
1920+
/// ```
1921+
class NonEphemeralConversionFailure final : public ArgumentMismatchFailure {
1922+
ConversionRestrictionKind ConversionKind;
1923+
bool DowngradeToWarning;
1924+
1925+
public:
1926+
NonEphemeralConversionFailure(Expr *expr, ConstraintSystem &cs,
1927+
ConstraintLocator *locator,
1928+
Type fromType, Type toType,
1929+
ConversionRestrictionKind conversionKind,
1930+
bool downgradeToWarning)
1931+
: ArgumentMismatchFailure(expr, cs, fromType, toType, locator),
1932+
ConversionKind(conversionKind), DowngradeToWarning(downgradeToWarning) {
1933+
}
1934+
1935+
bool diagnoseAsError() override;
1936+
1937+
private:
1938+
/// Emits a note explaining to the user that an ephemeral conversion is only
1939+
/// valid for the duration of the call, and suggests an alternative to use.
1940+
void emitSuggestionNotes() const;
1941+
};
1942+
19131943
} // end namespace constraints
19141944
} // end namespace swift
19151945

lib/Sema/CSFix.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -984,3 +984,25 @@ AllowArgumentMismatch::create(ConstraintSystem &cs, Type argType,
984984
return new (cs.getAllocator())
985985
AllowArgumentMismatch(cs, argType, paramType, locator);
986986
}
987+
988+
bool TreatEphemeralAsNonEphemeral::diagnose(Expr *root, bool asNote) const {
989+
NonEphemeralConversionFailure failure(
990+
root, getConstraintSystem(), getLocator(), getFromType(), getToType(),
991+
ConversionKind, isWarning());
992+
return failure.diagnose(asNote);
993+
}
994+
995+
TreatEphemeralAsNonEphemeral *TreatEphemeralAsNonEphemeral::create(
996+
ConstraintSystem &cs, ConstraintLocator *locator, Type srcType,
997+
Type dstType, ConversionRestrictionKind conversionKind,
998+
bool downgradeToWarning) {
999+
return new (cs.getAllocator()) TreatEphemeralAsNonEphemeral(
1000+
cs, locator, srcType, dstType, conversionKind, downgradeToWarning);
1001+
}
1002+
1003+
std::string TreatEphemeralAsNonEphemeral::getName() const {
1004+
llvm::SmallString<32> name;
1005+
name += "treat ephemeral as non-ephemeral for ";
1006+
name += ::getName(ConversionKind);
1007+
return name.c_str();
1008+
}

lib/Sema/CSFix.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class OverloadChoice;
4141
class ConstraintSystem;
4242
class ConstraintLocator;
4343
class ConstraintLocatorBuilder;
44+
enum class ConversionRestrictionKind;
4445
class Solution;
4546

4647
/// Describes the kind of fix to apply to the given constraint before
@@ -211,6 +212,10 @@ enum class FixKind : uint8_t {
211212
/// If an array was passed to a variadic argument, give a specific diagnostic
212213
/// and offer to drop the brackets if it's a literal.
213214
ExpandArrayIntoVarargs,
215+
216+
/// Allow an ephemeral argument conversion for a parameter marked as being
217+
/// non-ephemeral.
218+
TreatEphemeralAsNonEphemeral,
214219
};
215220

216221
class ConstraintFix {
@@ -1469,6 +1474,29 @@ class CoerceToCheckedCast final : public ContextualMismatch {
14691474
Type toType, ConstraintLocator *locator);
14701475
};
14711476

1477+
class TreatEphemeralAsNonEphemeral final : public AllowArgumentMismatch {
1478+
ConversionRestrictionKind ConversionKind;
1479+
1480+
TreatEphemeralAsNonEphemeral(ConstraintSystem &cs, ConstraintLocator *locator,
1481+
Type srcType, Type dstType,
1482+
ConversionRestrictionKind conversionKind,
1483+
bool downgradeToWarning)
1484+
: AllowArgumentMismatch(cs, FixKind::TreatEphemeralAsNonEphemeral,
1485+
srcType, dstType, locator, downgradeToWarning),
1486+
ConversionKind(conversionKind) {}
1487+
1488+
public:
1489+
ConversionRestrictionKind getConversionKind() const { return ConversionKind; }
1490+
std::string getName() const override;
1491+
1492+
bool diagnose(Expr *root, bool asNote = false) const override;
1493+
1494+
static TreatEphemeralAsNonEphemeral *
1495+
create(ConstraintSystem &cs, ConstraintLocator *locator, Type srcType,
1496+
Type dstType, ConversionRestrictionKind conversionKind,
1497+
bool downgradeToWarning);
1498+
};
1499+
14721500
} // end namespace constraints
14731501
} // end namespace swift
14741502

0 commit comments

Comments
 (0)