Previously, we skipped checking the return type of a function for safety
as we expected to warn at the use of the returned value:
let x = returnsUnsafe()
usesUnsafe(x) // warn here
Unfortunately, this resulted in missing some unsafe constructs that can
introduce memory safety issues when the use of the return value had a
different shape resulting in false negatives for cases like:
return returnsUnsafe()
or
usesUnsafe(returnsUnsafe())
This PR changes the analysis to always take return types of function
calls into account.
rdar://157237301
Similar to what we do for 'throws' checking, perform argument-specific
checking for unsafe call arguments. This provides more detailed failures:
```
example.swift:18:3: warning: expression uses unsafe constructs but is not
marked with 'unsafe' [#StrictMemorySafety]
16 | x.f(a: 0, b: 17, c: nil)
17 |
18 | x.f(a: 0, b: 17, c: &i)
| | `- note: argument 'c' in call to instance
method 'f' has unsafe type 'UnsafePointer<Int>?'
| `- warning: expression uses unsafe constructs but is not marked
with 'unsafe' [#StrictMemorySafety]
19 | unsafeF()
20 | }
```
It also means that we won't complain for `nil` or `Optional.none`
arguments passed to unsafe types, which eliminates some false
positives, and won't complain about unsafe result types when there is
a call---because we'd still get complaints later about the
actually-unsafe bit, which is using those results.
Fixes rdar://149629670.
Print diagnostic groups as part of the LLVM printer in the same manner as the
Swift one does, always. Make `-print-diagnostic-groups` an inert option, since we
always print diagnostic group names with the `[#GroupName]` syntax.
As part of this, we no longer render the diagnostic group name as part
of the diagnostic *text*, instead leaving it up to the diagnostic
renderer to handle the category appropriately. Update all of the tests
that were depending on `-print-diagnostic-groups` putting it into the
text to instead use the `{{documentation-file=<file name>}}`
diagnostic verification syntax.
With the acceptance of SE-0458, allow the use of unsafe expressions, the
@safe and @unsafe attributes, and the `unsafe` effect on the for..in loop
in all Swift code.
Introduce the `-strict-memory-safety` flag detailed in the proposal to
enable strict memory safety checking. This enables a new class of
feature, an optional feature (that is *not* upcoming or experimental),
and which can be detected via `hasFeature(StrictMemorySafety)`.
Since we infer unsafety from a use of a declaration that involves unsafe types
in its signature, there isn't a reason to require @unsafe on declaration to
restate it. This matches recent revisions of SE-0458.
The implicit reference to the initializer was tripping up the logic
for finding the appropriate place to insert "unsafe", and it was ending
up in the middle of the expression.
Introduce an `unsafe` expression akin to `try` and `await` that notes
that there are unsafe constructs in the expression to the right-hand
side. Extend the effects checker to also check for unsafety along with
throwing and async operations. This will result in diagnostics like
the following:
10 | func sum() -> Int {
11 | withUnsafeBufferPointer { buffer in
12 | let value = buffer[0]
| | `- note: reference to unsafe subscript 'subscript(_:)'
| |- warning: expression uses unsafe constructs but is not marked with 'unsafe'
| `- note: reference to parameter 'buffer' involves unsafe type 'UnsafeBufferPointer<Int>'
13 | tryWithP(X())
14 | return fastAdd(buffer.baseAddress, buffer.count)
These will come with a Fix-It that inserts `unsafe` into the proper
place. There's also a warning that appears when `unsafe` doesn't cover
any unsafe code, making it easier to clean up extraneous `unsafe`.
This approach requires that `@unsafe` be present on any declaration
that involves unsafe constructs within its signature. Outside of the
signature, the `unsafe` expression is used to identify unsafe code.
When a witness is unsafe and its corresponding requirement is not
unsafe, the conformance itself needs to be determined to be unsafe.
When doing this check, account for the fact that the requirement might
be implicitly unsafe, i.e., it uses unsafe types. In such cases, the
requirement is effectively unsafe, so the unsafe witness to it does not
make the conformance unsafe. Therefore, suppress the diagnostic. This
accounts for cases where the protocol comes from a module that didn't
enable strict memory safety checking, or didn't suppress all warnings
related to it, as well as cases where substitution of type witnesses
introduces the unsafe type.
The same thing occurs with overriding a method: an unsafe override of
a method that involves unsafe types (but was not marked @unsafe for
whatever reason) does not make the subclassing itself unsafe, so don't
diagnose it as such.
The `@unchecked` conformance is effectively the same as
`@safe(unchecked)`, in that it asserts memory safety in a place where
it cannot be automatically checked. But once that has been asserted,
there is no reason to diagnose anywhere else.
While here, drop the "unsafe declaration here" note, which isn't
adding value but did add noise.
Thanks, Alex!
Protocol conformances have a handful attributes that can apply to them
directly, including @unchecked (for Sendable), @preconcurrency, and
@retroactive. Generalize this into an option set that we carry around,
so it's a bit easier to add them, as well as reworking the
serialization logic to deal with an arbitrary number of such options.
Use this generality to add support for @unsafe conformances, which are
needed when unsafe witnesses are used to conform to safe requirements.
Implement general support for @unsafe conformances, including
producing a single diagnostic per missing @unsafe that provides a
Fix-It and collects together all of the unsafe witnesses as notes.
Drive the strict-safety diagnostics for a particular declaration from
primary type checking for declarations, so any memory-safety-related
diagnostics will only be emitted for the primary files. This also
brings them together as notes under a single warning for each
declaration.
Instead of producing a warning for each use of an unsafe entity,
collect all of the uses of unsafe constructs within a given function
and batch them together in a single diagnostic at the function level
that tells you what you can do (add `@unsafe` or `@safe(unchecked)`,
depending on whether all unsafe uses were in the definition), plus
notes identifying every unsafe use within that declaration. The new
diagnostic renderer nicely collects together in a single snippet, so
it's easier to reason about.
Here's an example from the embedded runtime that previously would have
been 6 separate warnings, each with 1-2 notes:
```
swift/stdlib/public/core/EmbeddedRuntime.swift:397:13: warning: global function 'swift_retainCount' involves unsafe code; use '@safe(unchecked)' to assert that the code is memory-safe
395 |
396 | @_cdecl("swift_retainCount")
397 | public func swift_retainCount(object: Builtin.RawPointer) -> Int {
| `- warning: global function 'swift_retainCount' involves unsafe code; use '@safe(unchecked)' to assert that the code is memory-safe
398 | if !isValidPointerForNativeRetain(object: object) { return 0 }
399 | let o = UnsafeMutablePointer<HeapObject>(object)
| | `- note: call to unsafe initializer 'init(_:)'
| `- note: reference to unsafe generic struct 'UnsafeMutablePointer'
400 | let refcount = refcountPointer(for: o)
| | `- note: reference to let 'o' involves unsafe type 'UnsafeMutablePointer<HeapObject>'
| `- note: call to global function 'refcountPointer(for:)' involves unsafe type 'UnsafeMutablePointer<Int>'
401 | return loadAcquire(refcount) & HeapObject.refcountMask
| | `- note: reference to let 'refcount' involves unsafe type 'UnsafeMutablePointer<Int>'
| `- note: call to global function 'loadAcquire' involves unsafe type 'UnsafeMutablePointer<Int>'
402 | }
403 |
```
Note that we have lost a little bit of information, because we no
longer produce "unsafe declaration was here" notes pointing back at
things like `UnsafeMutablePointer` or `recountPointer(for:)`. However,
strict memory safety tends to be noisy to turn on, so it's worth
losing a little bit of easily-recovered information to gain some
brevity.
When a declaration is `@unsafe`, don't emit strict safety diagnostics
for uses of unsafe entities, constructs, or types within it. This
allows one to account for all unsafe behavior in a module using strict
memory safety by marking the appropriate declarations `@unsafe`.
Enhance the strict-safety diagnostics to suggest the addition of
`@unsafe` where it is needed to suppress them, with a Fix-It. Ensure
that all such diagnostics can be suppressed via `@unsafe` so it's
possible to get to the above state.
Also includes a drive-by bug fix where we weren't diagnosing unsafe
methods overriding safe ones in some cases.
Fixes rdar://139467327.
Find all the usages of `--enable-experimental-feature` or
`--enable-upcoming-feature` in the tests and replace some of the
`REQUIRES: asserts` to use `REQUIRES: swift-feature-Foo` instead, which
should correctly apply to depending on the asserts/noasserts mode of the
toolchain for each feature.
Remove some comments that talked about enabling asserts since they don't
apply anymore (but I might had miss some).
All this was done with an automated script, so some formatting weirdness
might happen, but I hope I fixed most of those.
There might be some tests that were `REQUIRES: asserts` that might run
in `noasserts` toolchains now. This will normally be because their
feature went from experimental to upcoming/base and the tests were not
updated.
When referencing a declaration, check whether any of the types in that
reference are unsafe. This can diagnose cases where the original
declaration either wasn't actually unsafe, or is being provided with
unsafe types via generics.
Allow any declaration to be marked with `@unsafe`, meaning that it
involves unsafe code. This also extends to C declarations marked with
the `swift_attr("unsafe")` attribute.
Under a separate experimental flag (`DisallowUnsafe`), diagnose any
attempt to use an `@unsafe` declaration or any unsafe language feature
(such as `unowned(unsafe)`, `@unchecked Sendable`). This begins to
define a "safe" mode in Swift that prohibits memory-unsafe constructs.