Start emitting stubs for unimplemented designated initializers of the superclass.

When a subclass does not implement a designated initializer of its
superclass, introduce a stub initializer that simply traps. Such stubs
cannot be invoked directly using Swift syntax, but can be invoked
through the Objective-C runtime and from Objective-C code. Catch such
errors rather than allowing them to violate the memory safety of the
language.

Note that we're currently using cond_fail to trap; this will be
improved in the future,


Swift SVN r14839
This commit is contained in:
Doug Gregor
2014-03-09 07:16:30 +00:00
parent c92f159a8f
commit 00162dc243
10 changed files with 306 additions and 80 deletions

View File

@@ -279,8 +279,17 @@ class alignas(8) Decl {
/// Whether this is a complete object initializer.
unsigned CompleteObjectInit : 1;
/// Whether this initializer is a stub placed into a subclass to
/// catch invalid delegations to a subobject initializer not
/// overridden by the subclass. A stub will always trap at runtime.
///
/// Initializer stubs can be invoked from Objective-C or through
/// the Objective-C runtime; there is no way to directly express
/// an object construction that will invoke a stub.
unsigned HasStubImplementation : 1;
};
enum { NumConstructorDeclBits = NumAbstractFunctionDeclBits + 5 };
enum { NumConstructorDeclBits = NumAbstractFunctionDeclBits + 6 };
static_assert(NumConstructorDeclBits <= 32, "fits in an unsigned");
class TypeDeclBitfields {
@@ -3742,6 +3751,17 @@ public:
return !isCompleteObjectInit();
}
/// Whether the implementation of this method is a stub that traps at runtime.
bool hasStubImplementation() const {
return ConstructorDeclBits.HasStubImplementation;
}
/// Set whether the implementation of this method is a stub that
/// traps at runtime.
void setStubImplementation(bool stub) {
ConstructorDeclBits.HasStubImplementation = stub;
}
ConstructorDecl *getOverriddenDecl() const { return OverriddenDecl; }
void setOverriddenDecl(ConstructorDecl *over) { OverriddenDecl = over; }

View File

@@ -464,6 +464,16 @@ public:
/// \brief Retrieve the type without any default arguments.
Type getWithoutDefaultArgs(const ASTContext &Context);
/// Replace the result type of the given function type with a new
/// result type.
///
/// \param newResultType The new result type.
///
/// \param uncurryLevel The number of uncurry levels to apply before
/// replacing the type. With uncurry level == 0, this simply
/// replaces the current type with the new result type.
Type replaceResultType(Type newResultType, unsigned uncurryLevel = 1);
/// getRValueType - For an @lvalue type, retrieves the underlying object type.
/// Otherwise, returns the type itself.
Type getRValueType();

View File

@@ -1155,6 +1155,10 @@ bool ClassDecl::inheritsSuperclassInitializers(LazyResolver *resolver) {
if (!ctor->hasType())
resolver->resolveDeclSignature(ctor);
// Ignore any stub implementations.
if (ctor->hasStubImplementation())
continue;
if (auto overridden = ctor->getOverriddenDecl()) {
if (overridden->isSubobjectInit())
overriddenInits.insert(overridden);
@@ -1983,6 +1987,7 @@ ConstructorDecl::ConstructorDecl(Identifier NameHack, SourceLoc ConstructorLoc,
ConstructorDeclBits.ComputedBodyInitKind = 0;
ConstructorDeclBits.Required = 0;
ConstructorDeclBits.CompleteObjectInit = 0;
ConstructorDeclBits.HasStubImplementation = 0;
}
void ConstructorDecl::setArgParams(Pattern *selfPattern, Pattern *argParams) {

View File

@@ -885,10 +885,9 @@ bool DeclContext::lookupQualified(Type type,
// Allow filtering of the visible declarations based on
bool onlyCompleteObjectInits = false;
auto isAcceptableDecl = [&](NominalTypeDecl *current, Decl *decl) -> bool {
// Filter out subobject initializers, if requestred.
// Filter out subobject initializers, if requested.
if (onlyCompleteObjectInits) {
// Allow any initializer in an Objective-C class that neither declares nor
// inherits designated initializers.
// Allow any initializer in an Objective-C class.
bool assumeCompleteObjectInit = false;
if (current->hasClangNode()) {
if (auto objcClass
@@ -906,6 +905,12 @@ bool DeclContext::lookupQualified(Type type,
}
}
// Ignore stub implementations.
if (auto ctor = dyn_cast<ConstructorDecl>(decl)) {
if (ctor->hasStubImplementation())
return false;
}
return true;
};

View File

@@ -491,6 +491,32 @@ Type TypeBase::getWithoutDefaultArgs(const ASTContext &Context) {
/*defaultArgs=*/true);
}
Type TypeBase::replaceResultType(Type newResultType, unsigned uncurryLevel) {
if (uncurryLevel == 0)
return newResultType;
// Determine the input and result types of this function.
auto fnType = this->castTo<AnyFunctionType>();
Type inputType = fnType->getInput();
Type resultType = fnType->getResult()->replaceResultType(newResultType,
uncurryLevel - 1);
// Produce the resulting function type.
if (auto genericFn = dyn_cast<GenericFunctionType>(fnType)) {
return GenericFunctionType::get(genericFn->getGenericSignature(),
inputType, resultType,
fnType->getExtInfo());
}
if (auto polyFn = dyn_cast<PolymorphicFunctionType>(fnType)) {
return PolymorphicFunctionType::get(inputType, resultType,
&polyFn->getGenericParams(),
fnType->getExtInfo());
}
return FunctionType::get(inputType, resultType, fnType->getExtInfo());
}
/// Retrieve the object type for a 'self' parameter, digging into one-element
/// tuples, lvalue types, and metatypes.
Type TypeBase::getRValueInstanceType() {

View File

@@ -2717,11 +2717,15 @@ void SILGenFunction::emitClassConstructorInitializer(ConstructorDecl *ctor) {
// FIXME: Hack until all delegation is dispatched.
IsCompleteObjectInit = ctor->isCompleteObjectInit();
assert(ctor->getBody() && "Class constructor without a body?");
assert((ctor->getBody() || ctor->hasStubImplementation()) &&
"Class constructor without a body?");
// True if this constructor delegates to a peer constructor with self.init().
bool isDelegating = ctor->getDelegatingOrChainedInitKind(nullptr) ==
bool isDelegating = false;
if (!ctor->hasStubImplementation()) {
isDelegating = ctor->getDelegatingOrChainedInitKind(nullptr) ==
ConstructorDecl::BodyInitKind::Delegating;
}
// FIXME: The (potentially partially initialized) value here would need to be
// cleaned up on a constructor failure unwinding.
@@ -2738,7 +2742,8 @@ void SILGenFunction::emitClassConstructorInitializer(ConstructorDecl *ctor) {
auto selfTypeContext = ctor->getDeclContext()->getDeclaredTypeInContext();
auto selfClassDecl =
cast<ClassDecl>(selfTypeContext->getNominalOrBoundGenericNominal());
bool NeedsBoxForSelf = isDelegating || selfClassDecl->hasSuperclass();
bool NeedsBoxForSelf = isDelegating ||
(selfClassDecl->hasSuperclass() && !ctor->hasStubImplementation());
if (NeedsBoxForSelf)
emitLocalVariable(selfDecl);
@@ -2754,16 +2759,18 @@ void SILGenFunction::emitClassConstructorInitializer(ConstructorDecl *ctor) {
if (!NeedsBoxForSelf)
B.createDebugValue(selfDecl, selfArg);
// If needed, mark 'self' as uninitialized so that DI knows to enforce its DI
// properties on stored properties.
bool usesObjCAllocator = Lowering::usesObjCAllocator(selfClassDecl);
if (!ctor->hasStubImplementation()) {
// If needed, mark 'self' as uninitialized so that DI knows to
// enforce its DI properties on stored properties.
MarkUninitializedInst::Kind MUKind;
bool usesObjCAllocator = Lowering::usesObjCAllocator(selfClassDecl);
if (isDelegating)
MUKind = MarkUninitializedInst::DelegatingSelf;
else if (selfClassDecl->requiresStoredPropertyInits() && usesObjCAllocator) {
// Stored properties will be initialized in a separate .cxx_construct method
// called by the Objective-C runtime.
else if (selfClassDecl->requiresStoredPropertyInits() &&
usesObjCAllocator) {
// Stored properties will be initialized in a separate
// .cxx_construct method called by the Objective-C runtime.
assert(selfClassDecl->hasSuperclass() &&
"Cannot use ObjC allocation without a superclass");
MUKind = MarkUninitializedInst::DerivedSelfOnly;
@@ -2773,7 +2780,8 @@ void SILGenFunction::emitClassConstructorInitializer(ConstructorDecl *ctor) {
MUKind = MarkUninitializedInst::RootSelf;
selfArg = B.createMarkUninitialized(selfDecl, selfArg, MUKind);
assert(selfTy.hasReferenceSemantics() && "can't emit a value type ctor here");
assert(selfTy.hasReferenceSemantics() &&
"can't emit a value type ctor here");
if (NeedsBoxForSelf) {
SILLocation prologueLoc = RegularLocation(ctor);
@@ -2782,6 +2790,7 @@ void SILGenFunction::emitClassConstructorInitializer(ConstructorDecl *ctor) {
} else {
VarLocs[selfDecl] = VarLoc::getConstant(selfArg);
}
}
// Prepare the end of initializer location.
SILLocation endOfInitLoc = RegularLocation(ctor);
@@ -2795,7 +2804,10 @@ void SILGenFunction::emitClassConstructorInitializer(ConstructorDecl *ctor) {
if (isDelegating) {
// A delegating initializer does not initialize instance
// variables.
} else if (selfClassDecl->requiresStoredPropertyInits() && usesObjCAllocator) {
} else if (ctor->hasStubImplementation()) {
// Nor does a stub implementation.
} else if (selfClassDecl->requiresStoredPropertyInits() &&
usesObjCAllocator) {
// When the class requires all stored properties to have initial
// values and we're using Objective-C's allocation, stored
// properties are initialized via the .cxx_construct method, which
@@ -2808,7 +2820,14 @@ void SILGenFunction::emitClassConstructorInitializer(ConstructorDecl *ctor) {
}
// Emit the constructor body.
if (ctor->hasStubImplementation()) {
auto int1Ty = SILType::getBuiltinIntegerType(1, getASTContext());
auto *trueInst = B.createIntegerLiteral(endOfInitLoc, int1Ty, 1);
auto *failInst = B.createCondFail(endOfInitLoc, trueInst);
(void)failInst;
} else {
visit(ctor->getBody());
}
// Return 'self' in the epilog.
Optional<SILValue> maybeReturnValue;

View File

@@ -1998,6 +1998,78 @@ public:
}
}
/// Create a new initializer that overrides the given subobject
/// initializer.
///
/// \param classDecl The subclass in which the new initializer will
/// be declared.
///
/// \param superclassCtor The superclass initializer for which this
/// routine will create an override.
///
/// \returns the newly-created initializer that overrides \p
/// superclassCtor.
ConstructorDecl *
createSubobjectInitOverride(ClassDecl *classDecl,
ConstructorDecl *superclassCtor) {
// Determine the initializer parameters.
Type superInitType = superclassCtor->getInitializerInterfaceType();
if (superInitType->is<GenericFunctionType>() ||
classDecl->getGenericParamsOfContext()) {
// FIXME: Handle generic initializers as well.
return nullptr;
}
auto &ctx = TC.Context;
// Create the 'self' declaration and patterns.
auto *selfDecl = new (ctx) VarDecl(/*static*/ false, /*IsLet*/ true,
SourceLoc(), ctx.Id_self,
Type(), classDecl);
selfDecl->setImplicit();
Pattern *selfArgPattern
= new (ctx) NamedPattern(selfDecl, /*Implicit=*/true);
selfArgPattern = new (ctx) TypedPattern(selfArgPattern, TypeLoc());
Pattern *selfBodyPattern
= new (ctx) NamedPattern(selfDecl, /*Implicit=*/true);
selfBodyPattern = new (ctx) TypedPattern(selfBodyPattern, TypeLoc());
// Create the initializer parameter patterns.
Pattern *argParamPatterns
= superclassCtor->getArgParamPatterns()[1]->clone(ctx,/*Implicit=*/true);
Pattern *bodyParamPatterns
= superclassCtor->getBodyParamPatterns()[1]->clone(ctx,/*Implicit=*/true);
// Create the initializer declaration.
auto ctor = new (ctx) ConstructorDecl(ctx.Id_init, SourceLoc(),
selfArgPattern, argParamPatterns,
selfBodyPattern, bodyParamPatterns,
nullptr, classDecl);
ctor->setImplicit();
if (superclassCtor->hasSelectorStyleSignature())
ctor->setHasSelectorStyleSignature();
// Configure 'self'.
GenericParamList *outerGenericParams = nullptr;
Type selfType = configureImplicitSelf(ctor, outerGenericParams);
selfArgPattern->setType(selfType);
selfBodyPattern->setType(selfType);
cast<TypedPattern>(selfArgPattern)->getSubPattern()->setType(selfType);
cast<TypedPattern>(selfBodyPattern)->getSubPattern()->setType(selfType);
// Set the type of the initializer.
configureConstructorType(ctor, outerGenericParams, selfType,
argParamPatterns->getType());
if (superclassCtor->isObjC())
ctor->setIsObjC(true);
checkOverrides(ctor);
// Mark this as a stub implementation.
ctor->setStubImplementation(true);
return ctor;
}
void visitClassDecl(ClassDecl *CD) {
if (!IsSecondPass) {
TC.validateDecl(CD);
@@ -2025,10 +2097,10 @@ public:
TC.addImplicitConstructors(CD);
TC.addImplicitDestructor(CD);
// Check whether the superclass has any abstract initializers and whether
// they have been implemented.
// Check for inconsistencies between the initializers of our
// superclass and our own initializers.
if (auto superclassTy = CD->getSuperclass()) {
// Collect the set of constructors we override in the base class.
// Collect the set of initializers we override in superclass.
llvm::SmallPtrSet<ConstructorDecl *, 4> overriddenCtors;
for (auto member : TC.lookupConstructors(CD->getDeclaredTypeInContext(),
CD)) {
@@ -2037,13 +2109,19 @@ public:
overriddenCtors.insert(overridden);
}
// Diagnose any abstract constructors from our superclass that have
// not been overridden or inherited.
// Look for any required constructors or subobject initializers in the
// subclass that have not been overridden or otherwise provided.
bool diagnosed = false;
llvm::SmallVector<ConstructorDecl *, 2> synthesizedCtors;
for (auto superclassMember : TC.lookupConstructors(superclassTy, CD)) {
// We only care about abstract constructors.
// We only care about required or subobject initializers.
auto superclassCtor = cast<ConstructorDecl>(superclassMember);
if (!superclassCtor->isRequired())
if (!superclassCtor->isRequired() &&
!superclassCtor->isSubobjectInit())
continue;
// Skip invalid superclass initializers.
if (superclassCtor->isInvalid())
continue;
// If we have an override for this constructor, it's okay.
@@ -2053,9 +2131,13 @@ public:
// If the superclass constructor is a complete object initializer
// that is inherited into the current class, it's okay.
if (superclassCtor->isCompleteObjectInit() &&
CD->inheritsSuperclassInitializers(&TC))
CD->inheritsSuperclassInitializers(&TC)) {
assert(superclassCtor->isRequired());
continue;
}
// Diagnose a missing override of a required initializer.
if (superclassCtor->isRequired()) {
// Complain that we don't have an overriding constructor.
if (!diagnosed) {
TC.diagnose(CD, diag::abstract_incomplete_implementation,
@@ -2065,8 +2147,37 @@ public:
// FIXME: Using the type here is awful. We want to use the selector
// name and provide a nice Fix-It with that declaration.
TC.diagnose(superclassCtor, diag::abstract_initializer_not_overridden,
TC.diagnose(superclassCtor,
diag::abstract_initializer_not_overridden,
superclassCtor->getArgumentType());
continue;
}
// A subobject initializer has not been overridden.
// Skip this subobject initializer if it's in an extension.
// FIXME: We shouldn't allow this.
if (isa<ExtensionDecl>(superclassCtor->getDeclContext()))
continue;
// Create an override for it.
if (auto ctor = createSubobjectInitOverride(CD, superclassCtor)) {
assert(ctor->getOverriddenDecl() == superclassCtor &&
"Not an override?");
synthesizedCtors.push_back(ctor);
}
}
// If we synthesized any initializers, add them.
if (!synthesizedCtors.empty()) {
auto members = CD->getMembers();
unsigned numMembers = members.size();
MutableArrayRef<Decl *> newMembers
= TC.Context.Allocate<Decl*>(numMembers + synthesizedCtors.size());
std::copy(members.begin(), members.end(), newMembers.begin());
std::copy(synthesizedCtors.begin(), synthesizedCtors.end(),
newMembers.begin() + numMembers);
CD->setMembers(newMembers, CD->getBraces());
}
}
}
@@ -2633,6 +2744,8 @@ public:
return type;
});
}
} else if (isa<ConstructorDecl>(decl)) {
type = type->replaceResultType(subclass, /*uncurryLevel=*/2);
}
return type;
@@ -2796,7 +2909,7 @@ public:
// Check whether the types are identical.
// FIXME: It's wrong to use the uncurried types here for methods.
auto parentDeclTy = adjustSuperclassMemberDeclType(parentDecl,superclass);
auto parentDeclTy = adjustSuperclassMemberDeclType(parentDecl,owningTy);
auto uncurriedParentDeclTy = parentDeclTy;
if (method) {
uncurriedParentDeclTy = parentDeclTy->castTo<AnyFunctionType>()
@@ -3171,6 +3284,37 @@ public:
// their enclosing declaration.
}
/// Compute the allocating and initializing constructor types for
/// the given constructor.
void configureConstructorType(ConstructorDecl *ctor,
GenericParamList *outerGenericParams,
Type selfType,
Type argType) {
Type fnType;
Type allocFnType;
Type initFnType;
Type resultType = selfType->getInOutObjectType();
if (GenericParamList *innerGenericParams = ctor->getGenericParams()) {
innerGenericParams->setOuterParameters(outerGenericParams);
fnType = PolymorphicFunctionType::get(argType, resultType,
innerGenericParams);
} else
fnType = FunctionType::get(argType, resultType);
Type selfMetaType = MetatypeType::get(resultType, TC.Context);
if (outerGenericParams) {
allocFnType = PolymorphicFunctionType::get(selfMetaType, fnType,
outerGenericParams);
initFnType = PolymorphicFunctionType::get(selfType, fnType,
outerGenericParams);
} else {
allocFnType = FunctionType::get(selfMetaType, fnType);
initFnType = FunctionType::get(selfType, fnType);
}
ctor->setType(allocFnType);
ctor->setInitializerType(initFnType);
}
void visitConstructorDecl(ConstructorDecl *CD) {
if (CD->isInvalid()) {
CD->overwriteType(ErrorType::get(TC.Context));
@@ -3260,30 +3404,8 @@ public:
CD->overwriteType(ErrorType::get(TC.Context));
CD->setInvalid();
} else {
Type FnTy;
Type AllocFnTy;
Type InitFnTy;
Type ArgType = CD->getArgParamPatterns()[1]->getType();
Type ResultTy = SelfTy->getInOutObjectType();
if (GenericParamList *innerGenericParams = CD->getGenericParams()) {
innerGenericParams->setOuterParameters(outerGenericParams);
FnTy = PolymorphicFunctionType::get(ArgType, ResultTy,
innerGenericParams);
} else
FnTy = FunctionType::get(ArgType, ResultTy);
Type SelfMetaTy = MetatypeType::get(ResultTy, TC.Context);
if (outerGenericParams) {
AllocFnTy = PolymorphicFunctionType::get(SelfMetaTy, FnTy,
outerGenericParams);
InitFnTy = PolymorphicFunctionType::get(SelfTy, FnTy,
outerGenericParams);
} else {
AllocFnTy = FunctionType::get(SelfMetaTy, FnTy);
InitFnTy = FunctionType::get(SelfTy, FnTy);
}
CD->setType(AllocFnTy);
CD->setInitializerType(InitFnTy);
configureConstructorType(CD, outerGenericParams, SelfTy,
CD->getArgParamPatterns()[1]->getType());
}
validateAttributes(TC, CD);

View File

@@ -873,6 +873,12 @@ static bool shouldSerializeMember(Decl *D) {
case DeclKind::EnumCase:
return false;
case DeclKind::Constructor: {
// Never serialize a constructor with a stub implementation.
auto ctor = cast<ConstructorDecl>(D);
return !ctor->hasStubImplementation();
}
case DeclKind::EnumElement:
case DeclKind::Protocol:
case DeclKind::Destructor:
@@ -886,7 +892,6 @@ static bool shouldSerializeMember(Decl *D) {
case DeclKind::Class:
case DeclKind::Var:
case DeclKind::Func:
case DeclKind::Constructor:
return true;
}
}

View File

@@ -17,6 +17,7 @@ func onDestruct() { }
class SwiftGizmo : Gizmo {
var x : X
init()
deinit
}
sil @_TFCSo10SwiftGizmoD : $@cc(method) @thin (SwiftGizmo) -> ()
@@ -89,7 +90,7 @@ bb0(%0 : $SwiftGizmo):
// CHECK-NEXT: [[OBJC_SUPER_RECEIVER:%[a-zA-Z0-9]+]] = getelementptr %objc_super* [[OBJC_SUPER]], i32 0, i32 0
// CHECK-NEXT: store %objc_object* [[SUPER_OBJ]], %objc_object** [[OBJC_SUPER_RECEIVER]], align 8
// CHECK-NEXT: [[OBJC_SUPER_CLASS:%[a-zA-Z0-9]+]] = getelementptr %objc_super* [[OBJC_SUPER]], i32 0, i32 1
// CHECK-NEXT: store %objc_class* bitcast (%swift.type* getelementptr inbounds (%swift.full_heapmetadata* bitcast ({ void ([[SGIZMO]]*)*, i8**, i64, %objc_class*, %swift.opaque*, %swift.opaque*, i64, { i64, i8*, i64, i64, i8*, i64, i64 }*, i64, i64 }* @_TMdCSo10SwiftGizmo to %swift.full_heapmetadata*), i32 0, i32 2) to %objc_class*), %objc_class** [[OBJC_SUPER_CLASS]], align 8
// CHECK-NEXT: store %objc_class* bitcast (%swift.type* getelementptr inbounds (%swift.full_heapmetadata* bitcast ({{.*}}* @_TMdCSo10SwiftGizmo to %swift.full_heapmetadata*), i32 0, i32 2) to %objc_class*), %objc_class** [[OBJC_SUPER_CLASS]], align 8
// CHECK-NEXT: [[DEALLOC_SEL:%[a-zA-Z0-9]+]] = load i8** @"\01L_selector(dealloc)", align 8
// CHECK-NEXT: call void bitcast (void ()* @objc_msgSendSuper2 to void (%objc_super*, i8*)*)(%objc_super* [[OBJC_SUPER]], i8* [[DEALLOC_SEL]])
%5 = super_method %0 : $SwiftGizmo, #Gizmo.deinit!deallocator.foreign : $@cc(objc_method) @thin (Gizmo) -> () // user: %7
@@ -107,3 +108,13 @@ bb0(%0 : $SwiftGizmo):
%3 = tuple ()
return %3 : $() // id: %4
}
sil @_TToFCSo10SwiftGizmocfMS_FT_S_ : $@cc(objc_method) @thin (@owned SwiftGizmo) -> @owned SwiftGizmo {
bb0(%0 : $SwiftGizmo):
return %0 : $SwiftGizmo
}
sil @_TToFCSo10SwiftGizmocfMS_FT11withBellsOnSi_S_ : $@cc(objc_method) @thin (Int, @owned SwiftGizmo) -> @owned SwiftGizmo {
bb0(%0 : $Int, %1 : $SwiftGizmo):
return %1 : $SwiftGizmo
}

View File

@@ -14,6 +14,7 @@
#define NS_RETURNS_RETAINED __attribute__((ns_returns_retained))
#define NS_CONSUMES_SELF __attribute__((ns_consumes_self))
#define NS_CONSUMED __attribute__((ns_consumed))
#define OBJC_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer))
struct Rect {
float x;
@@ -38,7 +39,9 @@ typedef long NSInteger;
@interface Gizmo : NSObject
- (Gizmo*) clone NS_RETURNS_RETAINED;
- (Gizmo*) duplicate;
- (Gizmo*) initWithBellsOn:(NSInteger)x;
- (Gizmo*) init OBJC_DESIGNATED_INITIALIZER;
- (Gizmo*) initWithBellsOn:(NSInteger)x OBJC_DESIGNATED_INITIALIZER;
- (instancetype) initWithoutBells:(NSInteger)x;
- (void) fork NS_CONSUMES_SELF;
- (void) enumerateSubGizmos: (void (^)(Gizmo*))f;
+ (void) consume: (NS_CONSUMED Gizmo*) gizmo;