Skip to content

[4.2] Implement verification that noescape closures passed to Objecti… #16348

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conversation

aschwaighofer
Copy link
Contributor

…ve-C are not escaped

The series of commit introduces a copy_block_without_escaping block
instruction that ties a swift closure sentinel to the block that is
escaped.

SILGen emits this instruction when it generates a conversion from a
@noescape swift closure to a block.

       %noescape_closure = ...
       %wae_Thunk = function_ref @$withoutActuallyEscapingThunk
       %sentinel =
         partial_apply [callee_guaranteed] %wae_thunk(%noescape_closure)
       %noescaped_wrapped = mark_dependence %sentinel on %noescape_closure
       %sentinel_copy = copy_value %noescaped_wrapped
       %storage = alloc_stack
       %storage_address = project_block_storage %storage
       store %noescaped_wrapped to [init] %storage_address
       %block = init_block_storage_header %storage ...
       %block = copy_block_without_escaping %block withoutEscaping %noescaped_wrapped

The ClosureLifetimeFixup pass then lowers
copy_block_without_escaping into a copy_block and
is_escaping/cond_fail/destroy_value combination at a point
that succeeds the end of the block's lifetime.

As a result whenever a semantic noescape Swift closure synchronously
escapes to Objective C we will trap with an error after we return from
the Objective C call.

rdar://39682865

Cherry-pick of #16236

Commits:

  • Add a copy_block_without_escaping %block withoutEscaping %closure instruction

Mandatory pass will clean it up and replace it by a copy_block and
is_escaping/cond_fail/release combination on the %closure in follow-up
patches.

The instruction marks the dependence of a block on a closure that is
used as an 'withoutActuallyEscaping' sentinel.

  • SIL: Allow is_escaping_closure on optional closure types

  • SILGen: Emit withoutActuallyEscaping verification for @NoEscape closures stored in blocks

  • SIL: Add getSingleDealloc to AllocStack and remove two copies of it

I am going to introduce a third use in a follow-up

  • ClosureLifetimeFixup: Add support for copy_block_without_escaping instructions

Replace:

%copy = copy_block_without_escaping %block withoutEscaping %closure

...
destroy_value %copy

by (roughly) the instruction sequence:

%copy = copy_block %block

...
destroy_value %copy
%e = is_escaping %closure
cond_fail %e
destroy_value %closure

  • Fix DiagnoseStaticExclusivity

After the copy_block_without_actually_escaping change it might see a
mark_dependence instruction on a noescape closure.

  • Fixes to AccessSummaryAnalysis

The pattern we see for noescape closure passed to objective c is different:
There is the additional without actually escaping closure sentinel.

  • Fix test cases after SIL representation changes

  • Executable test case for passing a noescape closure to Objective-c which
    escapes the closure.

We expect the program to crash with an explanation.

  • Distinguish between withoutActuallyEscaping and passing @NoEscape
    Objective C closures when reporting that a closure has escaped

rdar://39682865

Replace this paragraph with a description of your changes and rationale. Provide links to external references/discussions if appropriate.

Resolves SR-NNNN.

…ve-C are not escaped

The series of commit introduces a ``copy_block_without_escaping`` block
instruction that ties a swift closure sentinel to the block that is
escaped.

SILGen emits this instruction when it generates a conversion from a
``@noescape`` swift closure to a block.

```
       %noescape_closure = ...
       %wae_Thunk = function_ref @$withoutActuallyEscapingThunk
       %sentinel =
         partial_apply [callee_guaranteed] %wae_thunk(%noescape_closure)
       %noescaped_wrapped = mark_dependence %sentinel on %noescape_closure
       %sentinel_copy = copy_value %noescaped_wrapped
       %storage = alloc_stack
       %storage_address = project_block_storage %storage
       store %noescaped_wrapped to [init] %storage_address
       %block = init_block_storage_header %storage ...
       %block = copy_block_without_escaping %block withoutEscaping %noescaped_wrapped
```

The ClosureLifetimeFixup pass then lowers
``copy_block_without_escaping`` into a ``copy_block`` and
``is_escaping``/``cond_fail``/``destroy_value`` combination at a point
that succeeds the end of the block's lifetime.

As a result whenever a semantic noescape Swift closure synchronously
escapes to Objective C we will trap with an error after we return from
the Objective C call.

rdar://39682865

Cherry-pick of swiftlang#16236

Commits:

* Add a copy_block_without_escaping %block withoutEscaping %closure instruction

Mandatory pass will clean it up and replace it by a copy_block and
is_escaping/cond_fail/release combination on the %closure in follow-up
patches.

The instruction marks the dependence of a block on a closure that is
used as an 'withoutActuallyEscaping' sentinel.

* SIL: Allow is_escaping_closure on optional closure types

* SILGen: Emit withoutActuallyEscaping verification for @NoEscape closures stored in blocks

* SIL: Add getSingleDealloc to AllocStack and remove two copies of it

I am going to introduce a third use in a follow-up

* ClosureLifetimeFixup: Add support for copy_block_without_escaping instructions

Replace:

  %copy = copy_block_without_escaping %block withoutEscaping %closure

  ...
  destroy_value %copy

by (roughly) the instruction sequence:

  %copy = copy_block %block

  ...
  destroy_value %copy
  %e = is_escaping %closure
  cond_fail %e
  destroy_value %closure

* Fix DiagnoseStaticExclusivity

After the copy_block_without_actually_escaping change it might see a
mark_dependence instruction on a noescape closure.

* Fixes to AccessSummaryAnalysis

The pattern we see for noescape closure passed to objective c is different:
There is the additional without actually escaping closure sentinel.

* Fix test cases after SIL representation changes

* Executable test case for passing a noescape closure to Objective-c which
escapes the closure.

We expect the program to crash with an explanation.

* Distinguish between withoutActuallyEscaping and passing @NoEscape
Objective C closures when reporting that a closure has escaped

rdar://39682865
@aschwaighofer
Copy link
Contributor Author

@swift-ci Please test

@swift-ci
Copy link
Contributor

swift-ci commented May 3, 2018

Build failed
Swift Test Linux Platform
Git Sha - 8cf6189

@aschwaighofer
Copy link
Contributor Author

@swift-ci Please test linux

@aschwaighofer aschwaighofer requested a review from eeckstein May 3, 2018 21:40
@aschwaighofer
Copy link
Contributor Author

@eeckstein I can't merge this. Can you?

@eeckstein eeckstein merged commit c4a25fb into swiftlang:swift-4.2-branch-04-30-2018 May 3, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants