forked from google/j2objc
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds @RetainedWith, which can be used to prevent leaks from certain r…
…eference cycles. Change on 2016/05/25 by kstanger <kstanger@google.com> ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=123238011
- Loading branch information
Showing
14 changed files
with
495 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
63 changes: 63 additions & 0 deletions
63
annotations/src/main/java/com/google/j2objc/annotations/RetainedWith.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
/* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.google.j2objc.annotations; | ||
|
||
import java.lang.annotation.ElementType; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
import java.lang.annotation.Target; | ||
|
||
/** | ||
* PLEASE READ THIS DOCUMENTATION BEFORE USING THIS ANNOTATION! | ||
* Note the criteria listed below which cannot be enforced by static analysis in | ||
* j2objc. Prefer using {@link Weak} over @RetainedWith if possible. | ||
* | ||
* Indicates that the annotated field (child) forms a direct reference cycle | ||
* with the referring object (parent). Adding this annotation informs J2ObjC of | ||
* the reference cycle between the pair of objects allowing both objects to be | ||
* deallocated once there are no external references to either object. | ||
* | ||
* @RetainedWith can be applied when a parent object pairs itself with a child | ||
* object and cannot fully manage the child's lifecycle, usually because the | ||
* child is returned to the caller. It can also be applied when the child has | ||
* the same class as the parent, for example on an inverse field of an | ||
* invertible collection type. | ||
* | ||
* @RetainedWith can only be applied where there is a two-object pair with a | ||
* cycle created by one reference from each object. Note: the two references can | ||
* be from the same declared field. When the references are from different | ||
* fields only one field should be given a @RetainedWith annotation, and the | ||
* @RetainedWith field should point from parent to child. | ||
* | ||
* When applied carefully in the right circumstance this annotation is very | ||
* useful in preventing leaks from certain Java collection types without needing | ||
* to alter their behavior. | ||
* | ||
* The following criteria must be adhered to otherwise behavior will be | ||
* undefined: | ||
* - The @RetainedWithfield, once assigned, must not be reassigned. | ||
* - The child object must not reassign any references back to the parent | ||
* object. Preferably references from child to parent are declared final. | ||
* - The child object must not contain any {@link Weak} references back to the | ||
* parent object. | ||
* - The child object must not be available to other threads at the time of | ||
* assignment. (Normally, the cycle is created upon construction of the child) | ||
* | ||
* @author Keith Stanger | ||
*/ | ||
@Target({ ElementType.FIELD }) | ||
@Retention(RetentionPolicy.CLASS) | ||
public @interface RetainedWith { | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
// | ||
// JreRetainedWith.h | ||
// JreEmulation | ||
// | ||
// Created by Keith Stanger on Mar. 18, 2016. | ||
// | ||
// INTERNAL ONLY. For use by JRE emulation code. | ||
|
||
#ifndef JRE_RETAINED_WITH_H_ | ||
#define JRE_RETAINED_WITH_H_ | ||
|
||
#import <Foundation/Foundation.h> | ||
|
||
// Called by @RetainedWith assignment functions. Caller must ensure that value | ||
// has a retain count at least two. | ||
FOUNDATION_EXPORT void JreRetainedWithInitialize(id parent, id value); | ||
|
||
// Called during dealloc of the parent and before releasing the child. | ||
FOUNDATION_EXPORT void JreRetainedWithHandleDealloc(id parent, id child); | ||
|
||
#endif // JRE_RETAINED_WITH_H_ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
// | ||
// JreRetainedWith.h | ||
// JreEmulation | ||
// | ||
// Created by Keith Stanger on Mar. 18, 2016. | ||
// | ||
|
||
#include "FastPointerLookup.h" | ||
#include "J2ObjC_source.h" | ||
|
||
// Associate the return reference so that it can be artificially weakened when | ||
// the child's retain count is 1. | ||
static char returnRefKey; | ||
// Associate the child's original class for "super" calls in the swizzled | ||
// methods. | ||
static char superClsKey; | ||
|
||
static id RetainedWithRetain(id self, SEL _cmd) { | ||
@synchronized (self) { | ||
if ([self retainCount] == 1) { | ||
[objc_getAssociatedObject(self, &returnRefKey) retain]; | ||
} | ||
Class superCls = objc_getAssociatedObject(self, &superClsKey); | ||
IMP superRetain = class_getMethodImplementation(superCls, @selector(retain)); | ||
return ((id (*)(id, SEL))superRetain)(self, @selector(retain)); | ||
} | ||
} | ||
|
||
static void RetainedWithRelease(id self, SEL _cmd) { | ||
@synchronized (self) { | ||
if ([self retainCount] == 2) { | ||
[objc_getAssociatedObject(self, &returnRefKey) autorelease]; | ||
} | ||
Class superCls = objc_getAssociatedObject(self, &superClsKey); | ||
IMP superRelease = class_getMethodImplementation(superCls, @selector(release)); | ||
((id (*)(id, SEL))superRelease)(self, @selector(release)); | ||
} | ||
} | ||
|
||
static IOSClass *RetainedWithGetClass(id self, SEL _cmd) { | ||
Class superCls = objc_getAssociatedObject(self, &superClsKey); | ||
return IOSClass_fromClass(superCls); | ||
} | ||
|
||
// Creates a subclass with swizzled retain, release, and getClass methods. | ||
static void *CreateSubclass(void *clsPtr) { | ||
Class cls = (Class)clsPtr; | ||
NSString *newName = [NSString stringWithFormat:@"%s_JreRetainedWith", class_getName(cls)]; | ||
Class subclass = objc_allocateClassPair(cls, [newName UTF8String], 0); | ||
Method retain = class_getInstanceMethod(cls, @selector(retain)); | ||
class_addMethod(subclass, @selector(retain), (IMP)RetainedWithRetain, | ||
method_getTypeEncoding(retain)); | ||
Method release = class_getInstanceMethod(cls, @selector(release)); | ||
class_addMethod(subclass, @selector(release), (IMP)RetainedWithRelease, | ||
method_getTypeEncoding(release)); | ||
Method getClass = class_getInstanceMethod(cls, @selector(getClass)); | ||
class_addMethod(subclass, @selector(getClass), (IMP)RetainedWithGetClass, | ||
method_getTypeEncoding(getClass)); | ||
objc_registerClassPair(subclass); | ||
return subclass; | ||
} | ||
|
||
static FastPointerLookup_t subclassLookup = FAST_POINTER_LOOKUP_INIT(&CreateSubclass); | ||
|
||
// Swizzle the class of the child and make necessary associations. | ||
static void ApplyRetainedWithSubclass(id parent, id child) { | ||
Class cls = object_getClass(child); | ||
Class subclass = (Class)FastPointerLookup(&subclassLookup, cls); | ||
objc_setAssociatedObject(child, &returnRefKey, parent, OBJC_ASSOCIATION_ASSIGN); | ||
objc_setAssociatedObject(child, &superClsKey, cls, OBJC_ASSOCIATION_ASSIGN); | ||
object_setClass(child, subclass); | ||
} | ||
|
||
// Counts how many times the child refers back to the parent. | ||
static NSUInteger CountReturnRefs(id parent, id child) { | ||
NSUInteger returnRefs = 0; | ||
Class cls = object_getClass(child); | ||
while (cls && cls != [NSObject class]) { | ||
unsigned int ivarCount; | ||
Ivar *ivars = class_copyIvarList(cls, &ivarCount); | ||
for (unsigned int i = 0; i < ivarCount; i++) { | ||
Ivar ivar = ivars[i]; | ||
const char *ivarType = ivar_getTypeEncoding(ivar); | ||
if (*ivarType == '@') { | ||
ptrdiff_t offset = ivar_getOffset(ivar); | ||
if (*(id *)((uintptr_t)child + offset) == parent) { | ||
returnRefs++; | ||
}; | ||
} | ||
} | ||
free(ivars); | ||
cls = class_getSuperclass(cls); | ||
} | ||
return returnRefs; | ||
} | ||
|
||
// Called upon destruction of the parent. We must set all return refs to nil to | ||
// avoid calling release on a dealloc'ed object from child's dealloc method. | ||
void JreRetainedWithHandleDealloc(id parent, id child) { | ||
Class cls = object_getClass(child); | ||
while (cls && cls != [NSObject class]) { | ||
unsigned int ivarCount; | ||
Ivar *ivars = class_copyIvarList(cls, &ivarCount); | ||
for (unsigned int i = 0; i < ivarCount; i++) { | ||
Ivar ivar = ivars[i]; | ||
const char *ivarType = ivar_getTypeEncoding(ivar); | ||
if (*ivarType == '@') { | ||
ptrdiff_t offset = ivar_getOffset(ivar); | ||
id *objField = (id *)((uintptr_t)child + offset); | ||
if (*objField == parent) { | ||
*objField = nil; | ||
}; | ||
} | ||
} | ||
free(ivars); | ||
cls = class_getSuperclass(cls); | ||
} | ||
} | ||
|
||
// Requires that value has a retain count of at least 2 so that its return | ||
// reference can remain strong to start. | ||
void JreRetainedWithInitialize(id parent, id value) { | ||
NSUInteger returnRefs = CountReturnRefs(parent, value); | ||
if (returnRefs > 0) { | ||
// Make all but one of the return refs weak. | ||
while (returnRefs-- > 1) { | ||
[parent release]; | ||
} | ||
ApplyRetainedWithSubclass(parent, value); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.