Skip to content
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

Diagnose Escaping self In Initializer through Property Wrapper #30715

Closed
wants to merge 1 commit into from
Closed

Diagnose Escaping self In Initializer through Property Wrapper #30715

wants to merge 1 commit into from

Conversation

briantkelley
Copy link

@briantkelley briantkelley commented Mar 29, 2020

If a property wrapper is implemented using the static subscript to gain access to the enclosing self, an object must be fully initialized before assigning to the property wrapper.

Add an attribute, [enclosingSelfAccess], to the assign_by_wrapper instruction so use analysis can determine if self is escaping.

Resolves SR-12430.

@CodaFi
Copy link
Member

CodaFi commented Mar 31, 2020

This needs more SILGen tests. In particular, I'd like to see a test for valid property wrapper code that emits this instruction with the attribute attached.

@briantkelley
Copy link
Author

@CodaFi Thank you for the feedback! I've added a SILGen test that exercises the new attribute and expanded the test case for valid and invalid uses of assigning through a property wrapper in a class initializer.

@briantkelley
Copy link
Author

Hi @theblixguy and @DougGregor, I'm just checking in to see if you had feedback on this PR or if there's anything else I can do to help move it along. Thanks in advance! I appreciate your time!

@CodaFi
Copy link
Member

CodaFi commented Apr 15, 2020

This also needs a SIL parsing test.

If a property wrapper is implemented using the static subscript to gain access to the enclosing self, an object must be fully initialized before assigning to the property wrapper.

Add an attribute, `[enclosingSelfAccess]`, to the `assign_by_wrapper` instruction so use analysis can determine if `self` is escaping.

Resolves SR-SR-12430.
@briantkelley
Copy link
Author

@CodaFi thanks for pointing me to that test type. I added a test to verify the parsing of that instruction. I tried to minimize the supporting code as much as possible but if you have pointers on how to further simplify the test I'd be happy to update it. Thanks again!

@briantkelley
Copy link
Author

Hey @CodaFi, I was just checking in to see if you had any additional feedback for this PR. Thank you!

@briantkelley
Copy link
Author

Hello @CodaFi, @DougGregor, and @theblixguy! I know everyone is busy, especially with the Swift 5.3 release underway. But if you had a moment to review the change and provide any further feedback, I'd definitely appreciate it! I'm looking forward to getting this fix merged! Thank you!

@@ -1934,6 +1934,10 @@ void LifetimeChecker::updateInstructionForInitState(DIMemoryUse &Use) {
assert(InitKind == IsInitialization);
AI->setOwnershipQualifier(AssignOwnershipQualifier::Reinit);
} else {
if (AI->hasEnclosingSelfAccess() && InitKind != IsInitialization &&
getSelfInitializedAtInst(AI) != DIKind::Yes) {
diagnose(Module, Inst->getLoc(), diag::self_closure_use_uninit);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be using the same shouldEmitError(Inst) precondition as the rest of the diagnostics. Otherwise dead usages of self will show up.

@briantkelley
Copy link
Author

Thank you for the feedback @CodaFi! Please accept my apologies for the delay in responding to it. PR #31248 changed the semantics of property wrapper initialization and I'm not sure about the following corner case:

class SubclassWithEnclosingSelfInInitializer: Superclass {

  init(test: String) {
    // Verify the init operand is accepted.
    self.y = test

    // Verify the init operand is not accepted.
    self.z = test
  }

  @Observable
  var y: String

  @Observable
  var z: String = ""
}

With the change from the PR, the compiler now treats self.z = test1 as an initialization rather than an assign. I need to do some more investigation to see if it's possible for self to escape with this new behavior or whether the PR fully mitigates this problem.

cc: @hborla

@hborla
Copy link
Member

hborla commented Jun 3, 2020

Hi @briantkelley ! Sorry it took me a few days to get to this. The change in #31248 ensures that an assign_by_wrapper instruction is never re-written as assignment before all of self is initialized, including all inherited properties in superclasses. This is because re-assignment of a property wrapper can invoke property observers, which could access uninitialized memory if self is not fully initialized. So, I think self can never escape before all stored properties are initialized and super.init is called because that will always be treated as another property wrapper initialization rather than re-assignment.

@briantkelley
Copy link
Author

briantkelley commented Sep 4, 2020

So sorry this fell off my radar. Thanks for the explanation @hborla ! After working through a bunch of test cases I agree the ability to access uninitialized memory is fixed so I'm going to close this PR. However, I did identify an issue where it's possible to leak resources when assigning through the property wrapper in the initializer, which I suspect is due to not re-writing assign_by_wrapper to assignment, so I opened SR-13495/FB8621431 to track that. Thanks everyone for your input on this PR!

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.

None yet

3 participants