Skip to content

Commit

Permalink
Separates finalize() methods from the generated dealloc methods. Catc…
Browse files Browse the repository at this point in the history
…h and swallow exceptions thrown by finalize().

This fixes other potential issues with finalizers, for example ensuring that the finalizer is executed before any fields are cleaned up.
	Change on 2016/02/01 by kstanger <kstanger@google.com>
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=113535791
  • Loading branch information
kstanger authored and Keith Stanger committed Feb 3, 2016
1 parent c8de3cd commit a2932c7
Show file tree
Hide file tree
Showing 12 changed files with 110 additions and 123 deletions.
4 changes: 4 additions & 0 deletions jre_emul/Classes/IOSReference.m
Expand Up @@ -20,6 +20,7 @@
#import "IOSReference.h"

#import "IOSClass.h"
#import "J2ObjC_source.h"
#import "java/lang/ref/PhantomReference.h"
#import "java/lang/ref/Reference.h"
#import "java/lang/ref/SoftReference.h"
Expand Down Expand Up @@ -306,6 +307,9 @@ static void ReferentSubclassDealloc(id self, SEL _cmd) {
}
}

// The class swizzling causes finalize not to be called from the
// referent's dealloc, so we call it here.
JreFinalize(self);
// Real dealloc.
RealReferentDealloc(self);
// Remove reference associations.
Expand Down
13 changes: 13 additions & 0 deletions jre_emul/Classes/J2ObjC_common.m
Expand Up @@ -24,6 +24,8 @@
#import "java/lang/AssertionError.h"
#import "java/lang/ClassCastException.h"
#import "java/lang/NullPointerException.h"
#import "java/util/logging/Level.h"
#import "java/util/logging/Logger.h"
#import "java_lang_IntegralToString.h"
#import "java_lang_RealToString.h"

Expand Down Expand Up @@ -59,6 +61,17 @@ id nil_chk(id __unsafe_unretained p) {
}
#endif

void JreFinalize(id self) {
@try {
[self javaFinalize];
} @catch (JavaLangThrowable *e) {
[JavaUtilLoggingLogger_getLoggerWithNSString_([[self getClass] getName])
logWithJavaUtilLoggingLevel:JavaUtilLoggingLevel_get_WARNING()
withNSString:@"Uncaught exception in finalizer"
withJavaLangThrowable:e];
}
}

id JreStrongAssign(__strong id *pIvar, id value) {
return JreAutoreleasedAssign(pIvar, [value retain]);
}
Expand Down
8 changes: 8 additions & 0 deletions jre_emul/Classes/J2ObjC_source.h
Expand Up @@ -68,6 +68,14 @@ FOUNDATION_EXPORT void JreThrowAssertionError(id __unsafe_unretained msg);
FOUNDATION_EXPORT void JreRelease(id obj);
#endif

FOUNDATION_EXPORT void JreFinalize(id self);

__attribute__((always_inline)) inline void JreCheckFinalize(id self, Class cls) {
if ([self class] == cls) {
JreFinalize(self);
}
}

/*!
* Macros that simplify the syntax for loading of static fields.
*
Expand Down
3 changes: 3 additions & 0 deletions jre_emul/Classes/JavaObject.h
Expand Up @@ -44,6 +44,9 @@
- (void)waitWithLong:(long long)timeout;
- (void)waitWithLong:(long long)timeout withInt:(int)nanos;

// Called upon deallocation of the object.
- (void)javaFinalize;

@end

#endif // _JavaObject_H_
5 changes: 4 additions & 1 deletion jre_emul/Classes/NSObject+JavaObject.m
Expand Up @@ -160,6 +160,9 @@ - (void)waitWithLong:(long long)timeout withInt:(int)nanos {
doWait(self, timeout + (nanos == 0 ? 0 : 1));
}

- (void)javaFinalize {
}

- (void)__javaClone:(id)original {
}

Expand All @@ -171,7 +174,7 @@ + (const J2ObjcClassInfo *)__metadata {
{ "isEqual:", "equals", "Z", 0x1, NULL, NULL },
{ "clone", NULL, "Ljava.lang.Object;", 0x4, "Ljava.lang.CloneNotSupportedException;", NULL },
{ "description", "toString", "Ljava.lang.String;", 0x1, NULL, NULL },
{ "dealloc", "finalize", "V", 0x4, "Ljava.lang.Throwable;", NULL },
{ "javaFinalize", "finalize", "V", 0x4, "Ljava.lang.Throwable;", NULL },
{ "notify", NULL, "V", 0x11, NULL, NULL },
{ "notifyAll", NULL, "V", 0x11, NULL, NULL },
{ "waitWithLong:", "wait", "V", 0x11, "Ljava.lang.InterruptedException;", NULL },
Expand Down
Expand Up @@ -23,13 +23,13 @@
import com.google.devtools.j2objc.ast.ExpressionStatement;
import com.google.devtools.j2objc.ast.FunctionInvocation;
import com.google.devtools.j2objc.ast.MethodDeclaration;
import com.google.devtools.j2objc.ast.NativeStatement;
import com.google.devtools.j2objc.ast.PrefixExpression;
import com.google.devtools.j2objc.ast.SimpleName;
import com.google.devtools.j2objc.ast.Statement;
import com.google.devtools.j2objc.ast.SuperMethodInvocation;
import com.google.devtools.j2objc.ast.TreeUtil;
import com.google.devtools.j2objc.ast.TreeVisitor;
import com.google.devtools.j2objc.ast.TryStatement;
import com.google.devtools.j2objc.ast.TypeDeclaration;
import com.google.devtools.j2objc.ast.VariableDeclarationFragment;
import com.google.devtools.j2objc.types.FunctionBinding;
Expand All @@ -55,64 +55,49 @@
public class DestructorGenerator extends TreeVisitor {

@Override
public boolean visit(TypeDeclaration node) {
public void endVisit(TypeDeclaration node) {
if (node.isInterface()) {
return true;
}
MethodDeclaration destructor = findDestructor(node);
if (destructor != null && Options.useARC()) {
removeSuperFinalizeStatement(destructor.getBody());
return;
}

ITypeBinding type = node.getTypeBinding();
boolean hasFinalize = hasFinalizeMethod(type);
List<Statement> releaseStatements = createReleaseStatements(node);
if (destructor == null && !releaseStatements.isEmpty()) {
// No destructor, so create a new one if there are releasable fields.
destructor = buildFinalizeMethod(node.getTypeBinding());
node.getBodyDeclarations().add(destructor);
if (releaseStatements.isEmpty() && !hasFinalize) {
return;
}
if (destructor != null) {
addReleaseStatements(destructor, releaseStatements);
}
return true;
}

private MethodDeclaration findDestructor(TypeDeclaration node) {
for (MethodDeclaration method : TreeUtil.getMethodDeclarations(node)) {
if (BindingUtil.isDestructor(method.getMethodBinding())) {
return method;
}
ITypeBinding voidType = typeEnv.resolveJavaType("void");
int modifiers = Modifier.PUBLIC | BindingUtil.ACC_SYNTHETIC;
GeneratedMethodBinding deallocBinding = GeneratedMethodBinding.newMethod(
NameTable.DEALLOC_METHOD, modifiers, voidType, type);
MethodDeclaration deallocDecl = new MethodDeclaration(deallocBinding);
Block block = new Block();
deallocDecl.setBody(block);
List<Statement> stmts = block.getStatements();
if (hasFinalize) {
String clsName = nameTable.getFullName(type);
stmts.add(new NativeStatement("JreCheckFinalize(self, [" + clsName + " class]);"));
}
stmts.addAll(releaseStatements);
if (Options.useReferenceCounting()) {
stmts.add(new ExpressionStatement(new SuperMethodInvocation(typeEnv.getDeallocMethod())));
}
return null;
}

private void removeSuperFinalizeStatement(Block body) {
body.accept(new TreeVisitor() {
@Override
public boolean visit(final ExpressionStatement node) {
Expression e = node.getExpression();
if (e instanceof SuperMethodInvocation) {
IMethodBinding m = ((SuperMethodInvocation) e).getMethodBinding();
if (BindingUtil.isDestructor(m)) {
node.remove();
return false;
}
}
return true;
}
});
node.getBodyDeclarations().add(deallocDecl);
}

private SuperMethodInvocation findSuperFinalizeInvocation(MethodDeclaration node) {
// Find existing super.finalize(), if any.
final SuperMethodInvocation[] superFinalize = new SuperMethodInvocation[1];
node.accept(new TreeVisitor() {
@Override
public void endVisit(SuperMethodInvocation node) {
if (BindingUtil.isDestructor(node.getMethodBinding())) {
superFinalize[0] = node;
}
private boolean hasFinalizeMethod(ITypeBinding type) {
if (type == null || typeEnv.isJavaObjectType(type)) {
return false;
}
for (IMethodBinding method : type.getDeclaredMethods()) {
if (method.getName().equals(NameTable.FINALIZE_METHOD)
&& method.getParameterTypes().length == 0) {
return true;
}
});
return superFinalize[0];
}
return hasFinalizeMethod(type.getSuperclass());
}

private List<Statement> createReleaseStatements(TypeDeclaration node) {
Expand Down Expand Up @@ -145,42 +130,4 @@ private Statement createRelease(IVariableBinding var) {
releaseInvocation.getArguments().add(arg);
return new ExpressionStatement(releaseInvocation);
}

private void addReleaseStatements(MethodDeclaration method, List<Statement> releaseStatements) {
SuperMethodInvocation superFinalize = findSuperFinalizeInvocation(method);

List<Statement> statements = method.getBody().getStatements();
if (superFinalize != null) {
// Release statements must be inserted before the [super dealloc] call.
statements =
TreeUtil.asStatementList(TreeUtil.getOwningStatement(superFinalize)).subList(0, 0);
} else if (!statements.isEmpty() && statements.get(0) instanceof TryStatement) {
TryStatement tryStatement = ((TryStatement) statements.get(0));
if (tryStatement.getBody() != null) {
statements = tryStatement.getBody().getStatements();
}
}

statements.addAll(releaseStatements);

if (Options.useReferenceCounting() && superFinalize == null) {
IMethodBinding methodBinding = method.getMethodBinding();
GeneratedMethodBinding binding = GeneratedMethodBinding.newMethod(
NameTable.DEALLOC_METHOD, Modifier.PUBLIC, typeEnv.mapTypeName("void"),
methodBinding.getDeclaringClass());
SuperMethodInvocation call = new SuperMethodInvocation(binding);
ExpressionStatement stmt = new ExpressionStatement(call);
statements.add(stmt);
}
}

private MethodDeclaration buildFinalizeMethod(ITypeBinding declaringClass) {
ITypeBinding voidType = typeEnv.mapTypeName("void");
int modifiers = Modifier.PUBLIC | BindingUtil.ACC_SYNTHETIC;
GeneratedMethodBinding binding = GeneratedMethodBinding.newMethod(
NameTable.DEALLOC_METHOD, modifiers, voidType, declaringClass);
MethodDeclaration method = new MethodDeclaration(binding);
method.setBody(new Block());
return method;
}
}
Expand Up @@ -125,8 +125,7 @@ private boolean canFunctionize(MethodDeclaration node) {

// Never functionize these types of methods.
if (Modifier.isStatic(modifiers) || Modifier.isAbstract(modifiers)
|| BindingUtil.isSynthetic(modifiers) || m.isAnnotationMember()
|| BindingUtil.isDestructor(m)) {
|| BindingUtil.isSynthetic(modifiers) || m.isAnnotationMember()) {
return false;
}

Expand Down
Expand Up @@ -78,6 +78,7 @@ public class Types {
private final IOSMethodBinding releaseMethod;
private final IOSMethodBinding autoreleaseMethod;
private final IOSMethodBinding allocMethod;
private final IOSMethodBinding deallocMethod;

public Types(AST ast) {
this.ast = ast;
Expand Down Expand Up @@ -116,6 +117,8 @@ public Types(AST ast) {
NameTable.AUTORELEASE_METHOD, Modifier.PUBLIC, idType, NSObject);
allocMethod = IOSMethodBinding.newMethod(
NameTable.ALLOC_METHOD, Modifier.PUBLIC, idType, NSObject);
deallocMethod = IOSMethodBinding.newMethod(
NameTable.DEALLOC_METHOD, Modifier.PUBLIC, idType, NSObject);
}

private IOSTypeBinding mapIOSType(IOSTypeBinding type) {
Expand Down Expand Up @@ -332,4 +335,8 @@ public IOSMethodBinding getAutoreleaseMethod() {
public IOSMethodBinding getAllocMethod() {
return allocMethod;
}

public IOSMethodBinding getDeallocMethod() {
return deallocMethod;
}
}
Expand Up @@ -505,16 +505,6 @@ public int compare(IMemberValuePairBinding o1, IMemberValuePairBinding o2) {
return valuePairs;
}

/**
* Returns true if method is an Objective-C dealloc method.
*/
public static boolean isDestructor(IMethodBinding m) {
String methodName = m.getName();
return !m.isConstructor() && !isStatic(m) && m.getParameterTypes().length == 0
&& (methodName.equals(NameTable.FINALIZE_METHOD)
|| methodName.equals(NameTable.DEALLOC_METHOD));
}

/**
* Returns the attributes of a Property annotation.
*/
Expand Down
Expand Up @@ -496,7 +496,7 @@ public static String getMethodName(IMethodBinding method) {
return "init";
}
String name = method.getName();
if (isReservedName(name)) {
if (!BindingUtil.isSynthetic(method) && isReservedName(name)) {
name += "__";
}
return name;
Expand All @@ -520,9 +520,6 @@ public String getMethodSelector(IMethodBinding method) {
if (method instanceof IOSMethodBinding) {
return ((IOSMethodBinding) method).getSelector();
}
if (BindingUtil.isDestructor(method)) {
return DEALLOC_METHOD;
}
if (method.isConstructor() || BindingUtil.isStatic(method)) {
return selectorForOriginalBinding(method);
}
Expand Down Expand Up @@ -560,8 +557,8 @@ private String selectorForOriginalBinding(IMethodBinding method) {
* not returned by getMethodSelector().
*/
public List<String> getExtraSelectors(IMethodBinding method) {
if (method instanceof IOSMethodBinding || method.isConstructor() || BindingUtil.isStatic(method)
|| BindingUtil.isDestructor(method)) {
if (method instanceof IOSMethodBinding || method.isConstructor()
|| BindingUtil.isStatic(method)) {
return Collections.emptyList();
}
List<IMethodBinding> originalMethods = getOriginalMethodBindings(method);
Expand Down
Expand Up @@ -43,6 +43,7 @@ java.lang.Object.equals(Ljava/lang/Object;)Z = isEqual:
java.lang.Object.getClass()Ljava/lang/Class; = getClass
java.lang.Object.hashCode()I = hash
java.lang.Object.toString()Ljava/lang/String; = description
java.lang.Object.finalize()V = javaFinalize

# java.lang.Class
java.lang.Class.asSubclass(Ljava/lang/Class;)Ljava/lang/Class; = asSubclass:
Expand Down

0 comments on commit a2932c7

Please sign in to comment.