Merge pull request #56 from realm/sk-notification-fixes

Protect realm and callback from GC in addNotification
This commit is contained in:
Scott Kyle 2015-10-13 12:58:38 -07:00
commit d61082681f
3 changed files with 38 additions and 16 deletions

View File

@ -346,7 +346,7 @@ JSValueRef RealmWrite(JSContextRef ctx, JSObjectRef function, JSObjectRef thisOb
try { try {
RJSValidateArgumentCount(argumentCount, 1); RJSValidateArgumentCount(argumentCount, 1);
JSObjectRef object = RJSValidatedValueToObject(ctx, arguments[0]); JSObjectRef object = RJSValidatedValueToFunction(ctx, arguments[0]);
SharedRealm realm = *RJSGetInternal<SharedRealm *>(thisObject); SharedRealm realm = *RJSGetInternal<SharedRealm *>(thisObject);
realm->begin_transaction(); realm->begin_transaction();
JSObjectCallAsFunction(ctx, object, thisObject, 0, NULL, jsException); JSObjectCallAsFunction(ctx, object, thisObject, 0, NULL, jsException);
@ -370,7 +370,21 @@ JSValueRef RealmWrite(JSContextRef ctx, JSObjectRef function, JSObjectRef thisOb
namespace realm { namespace realm {
struct Notification { struct Notification {
JSGlobalContextRef ctx; JSGlobalContextRef ctx;
JSObjectRef realmObject;
JSObjectRef callbackObject;
RJSRealmDelegate::NotificationFunction func; RJSRealmDelegate::NotificationFunction func;
Notification(JSGlobalContextRef c, JSObjectRef r, JSObjectRef cb, RJSRealmDelegate::NotificationFunction f) : ctx(c), realmObject(r), callbackObject(cb), func(f) {
JSGlobalContextRetain(ctx);
JSValueProtect(ctx, realmObject);
JSValueProtect(ctx, callbackObject);
}
~Notification() {
JSValueUnprotect(ctx, callbackObject);
JSValueUnprotect(ctx, realmObject);
JSGlobalContextRelease(ctx);
}
}; };
} }
@ -378,22 +392,23 @@ JSValueRef RealmAddNotification(JSContextRef ctx, JSObjectRef function, JSObject
try { try {
RJSValidateArgumentCount(argumentCount, 1); RJSValidateArgumentCount(argumentCount, 1);
JSObjectRef user_function = RJSValidatedValueToObject(ctx, arguments[0]); JSObjectRef callback = RJSValidatedValueToFunction(ctx, arguments[0]);
SharedRealm realm = *RJSGetInternal<SharedRealm *>(thisObject); SharedRealm realm = *RJSGetInternal<SharedRealm *>(thisObject);
JSGlobalContextRef gCtx = JSGlobalContextRetain(JSContextGetGlobalContext(ctx)); JSGlobalContextRef gCtx = JSContextGetGlobalContext(ctx);
RJSRealmDelegate::NotificationFunction func = std::make_shared<std::function<void(const std::string)>>([=](std::string notification_name) { RJSRealmDelegate::NotificationFunction func = std::make_shared<std::function<void(const std::string)>>([=](std::string notification_name) {
JSValueRef arguments[2]; JSValueRef arguments[2];
arguments[0] = thisObject; arguments[0] = thisObject;
arguments[1] = RJSValueForString(gCtx, notification_name); arguments[1] = RJSValueForString(gCtx, notification_name);
JSValueRef ex = NULL; JSValueRef ex = NULL;
JSObjectCallAsFunction(gCtx, user_function, thisObject, 2, arguments, &ex); JSObjectCallAsFunction(gCtx, callback, thisObject, 2, arguments, &ex);
if (ex) { if (ex) {
throw RJSException(gCtx, ex); throw RJSException(gCtx, ex);
} }
}); });
static_cast<RJSRealmDelegate *>(realm->m_delegate.get())->add_notification(func); static_cast<RJSRealmDelegate *>(realm->m_delegate.get())->add_notification(func);
return RJSWrapObject<Notification *>(ctx, RJSNotificationClass(), new Notification { gCtx, func }); return RJSWrapObject<Notification *>(ctx, RJSNotificationClass(), new Notification { gCtx, thisObject, callback, func });
} }
catch (std::exception &exp) { catch (std::exception &exp) {
if (jsException) { if (jsException) {
@ -418,12 +433,6 @@ JSValueRef RealmClose(JSContextRef ctx, JSObjectRef function, JSObjectRef thisOb
return NULL; return NULL;
} }
void RJSNotificationFinalize(JSObjectRef object) {
Notification *notification = RJSGetInternal<Notification *>(object);
JSGlobalContextRelease(notification->ctx);
RJSFinalize<Notification *>(object);
}
const JSStaticFunction RJSRealmFuncs[] = { const JSStaticFunction RJSRealmFuncs[] = {
{"objects", RealmObjects, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, {"objects", RealmObjects, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete},
{"create", RealmCreateObject, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, {"create", RealmCreateObject, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete},
@ -441,7 +450,7 @@ JSClassRef RJSRealmClass() {
} }
JSClassRef RJSNotificationClass() { JSClassRef RJSNotificationClass() {
static JSClassRef s_notificationClass = RJSCreateWrapperClass<Notification *>("Notification", NULL, NULL, NULL, RJSNotificationFinalize); static JSClassRef s_notificationClass = RJSCreateWrapperClass<Notification *>("Notification", NULL, NULL, NULL);
return s_notificationClass; return s_notificationClass;
} }

View File

@ -86,7 +86,7 @@ inline void RJSValidateArgumentRange(size_t argumentCount, size_t min, size_t ma
class RJSException : public std::runtime_error { class RJSException : public std::runtime_error {
public: public:
RJSException(JSContextRef ctx, JSValueRef &ex) : std::runtime_error(RJSValidatedStringForValue(ctx, ex, "exception")), RJSException(JSContextRef ctx, JSValueRef &ex) : std::runtime_error(RJSStringForValue(ctx, ex)),
m_jsException(ex) {} m_jsException(ex) {}
JSValueRef exception() { return m_jsException; } JSValueRef exception() { return m_jsException; }
@ -106,6 +106,14 @@ static inline JSObjectRef RJSValidatedValueToObject(JSContextRef ctx, JSValueRef
return object; return object;
} }
static inline JSObjectRef RJSValidatedValueToFunction(JSContextRef ctx, JSValueRef value, const char *message = NULL) {
JSObjectRef object = JSValueToObject(ctx, value, NULL);
if (!object || !JSObjectIsFunction(ctx, object)) {
throw std::runtime_error(message ?: "Value is not a function.");
}
return object;
}
static inline double RJSValidatedValueToNumber(JSContextRef ctx, JSValueRef value) { static inline double RJSValidatedValueToNumber(JSContextRef ctx, JSValueRef value) {
JSValueRef exception = NULL; JSValueRef exception = NULL;
double number = JSValueToNumber(ctx, value, &exception); double number = JSValueToNumber(ctx, value, &exception);

View File

@ -272,14 +272,19 @@ var RealmTests = {
}, },
testNotifications: function() { testNotifications: function() {
var notificationCount = 0;
var realm = new Realm({schema: []}); var realm = new Realm({schema: []});
var notification = realm.addNotification(function() { var notificationCount = 0;
notificationCount++; var notificationName;
var notification = realm.addNotification(function(realm, name) {
notificationCount++;
notificationName = name;
}); });
TestCase.assertEqual(notificationCount, 0); TestCase.assertEqual(notificationCount, 0);
realm.write(function() {}); realm.write(function() {});
TestCase.assertEqual(notificationCount, 1); TestCase.assertEqual(notificationCount, 1);
TestCase.assertEqual(notificationName, 'DidChangeNotification');
}, },
}; };