Skip to content

Fix for class convenience initializers delegating to factory initializers (3.0) #4604

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

Merged

Conversation

slavapestov
Copy link
Contributor

@slavapestov slavapestov commented Sep 2, 2016

  • Description: This patch fixes runtime crashes with convenience initializer delegation due to issues in definite initialization.
  • Scope of the issue: The dispatch overlay adds convenience initializers via an extension on imported classes from libdispatch. These initializers delegate to factory initializers, which are really just top-level C functions imported as constructors via the 'import-as-member' mechanism. Right now this crashes at runtime in debug builds, causing those tests to be disabled. This does not affect users, but it is a problem for compiler developers. The moral equivalent in pure Swift code is to have a convenience initializer of a class delegate to a protocol extension initializer, which is also a desirable feature. This currently crashes in release builds too.
  • Risk: Low, the changes have been in master for a while and we have not seen any regressions.
  • Tested: There's a new test for the pure Swift case, and the existing XFAILs in the dispatch overlay tests have been removed.
  • Reviewed by: Jordan Rose (pending)
  • Radar: rdar://problem/27713221, rdar://problem/27226313

Previously, we were only able to detect factory initializers
dispatched through class_method. This didn't work for
factory initializers defined in protocol extensions.

The end result would be that we would strong_release an
uninitialized class instance, which could cause crashes.

Fix DI to correctly release the old instance using
dealloc_partial_ref instead.

Fixes <rdar://problem/27713221>.
I apologize in advance to @jrose-apple, who is not a fan
of this fix ;-)

In unoptimized builds, the convenience initializers on
DispatchQueue allocate and immediately deallocate an
instance of OS_dispatch_queue prior to calling the
C function that returns the "real" instance.

This is because we don't have a way to write user-defined
factory initializers yet; convenience initializers still
have an 'initializing' entry point that takes an existing
instance, which we have no choice but to throw away.

Unfortunately, when we perform the fake allocation, we
look up class metadata by calling the wrong Swift runtime
function, causing a crash when we send +allocWithZone:.

Fix this so that the metadata is accessed via a lookup
from the Objective-C runtime, instead of making a totally
fake 'foreign metadata' object -- it looks like there was
code for this already, it just wasn't used in all cases.

While getting metadata for a runtime-only class should be
rare, this feels like a real bug fix, to me.

Second, we would ultimately free the fake object by sending
-release, however OS_dispatch_queue has an override of
-dealloc which doesn't like to be called with a completely
uninitialized instance.

Here, I'm going to drop all pretense of sanity. The patch
just changes IRGen to lower the dealloc_partial_ref instruction
as a call to the object_dispose() Objective-C runtime function
when the class in question is a runtime-only class. This
frees the object without running -dealloc, which *happens*
to work for OS_dispatch_queue.

Fixes <rdar://problem/27226313>.
@slavapestov slavapestov changed the base branch from master to swift-3.0-branch September 2, 2016 22:56
@slavapestov slavapestov added this to the Swift 3.0 milestone Sep 2, 2016
@slavapestov
Copy link
Contributor Author

@swift-ci Please test OS X

@slavapestov
Copy link
Contributor Author

@jrose-apple Do you mind reviewing this, since you asked for the cherry-pick :)

@jrose-apple
Copy link
Contributor

I don't think I'm qualified to review the DI change, sorry. The IRGen change looks just as icky-but-valid as it did before.

@slavapestov
Copy link
Contributor Author

@DougGregor did some work on DI factory initializers recently, maybe he can review it.

// delegating to a factory initializer. In this case, the object
// was allocated with +allocWithZone:, so calling object_dispose()
// should be OK.
if (theClass->getForeignClassKind() == ClassDecl::ForeignKind::RuntimeOnly) {
Copy link
Member

Choose a reason for hiding this comment

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

Why shouldn't runtime-only classes be freed with-release? We know that we have a real Objective-C object that we're freeing, and the only thing that makes them "run-time only" is that the class and metaclass symbols are hidden.

Copy link
Contributor

@jrose-apple jrose-apple Sep 6, 2016

Choose a reason for hiding this comment

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

This is the "Jordan hates this patch" as mentioned in the commit message. It's not really the correct fix for deallocating a partially-allocated foreign class, but in practice it'll do the right thing and release won't, because some of them are in a state where dealloc shouldn't be called.

Copy link
Member

Choose a reason for hiding this comment

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

That same logic is equally true for non-runtime-only Objective-C classes for which we zero-initialized state doesn't imply that -dealloc is safe. I can live with this as a hack to improve on the status quo, but... ewww.

@DougGregor
Copy link
Member

The DI parts look reasonable to me.

@tkremenek tkremenek merged commit fa65a39 into swiftlang:swift-3.0-branch Sep 8, 2016
kateinoigakukun added a commit that referenced this pull request Aug 31, 2022
[pull] swiftwasm from main
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.

4 participants