Skip to content

Commit

Permalink
Add some missing validation to property getters and setters
Browse files Browse the repository at this point in the history
6c02999 shifted around where the checks for
valid threads and objects were done, and a few turned out to be wrong. This
resulted in either crashing within core rather than throwing an exception, or
throwing an untranslated C++ exception rather than the appropriate obj-c
exception.
  • Loading branch information
tgoyne committed Oct 19, 2018
1 parent 57394d1 commit ebecc65
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 5 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ x.y.z Release notes (yyyy-MM-dd)
### Fixed
* Fix `SyncUser.requestEmailConfirmation` not triggering the email confirmation
flow on ROS. (PR [#5953](https://github.com/realm/realm-cocoa/pull/5953), since 3.5.0)
* Add some missing validation in the getters and setters of properties on
managed Realm objects, which would sometimes result in an application
crashing with a segfault rather than the appropriate exception being thrown
when trying to write to an object which has been deleted.
(PR [#5952](https://github.com/realm/realm-cocoa/pull/5952), since 2.8.0)

### Compatibility
* File format: Generates Realms with format v9 (Reads and upgrades all previous formats)
Expand Down
11 changes: 7 additions & 4 deletions Realm/RLMAccessor.mm
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@ void setValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex,
}

template<typename Fn>
void translateError(Fn&& fn) {
auto translateError(Fn&& fn) {
try {
fn();
return fn();
}
catch (std::exception const& e) {
@throw RLMException(e);
Expand Down Expand Up @@ -108,8 +108,8 @@ void setValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex,

void setValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex,
__unsafe_unretained RLMObjectBase *const val) {
RLMVerifyInWriteTransaction(obj);
if (!val) {
RLMVerifyInWriteTransaction(obj);
obj->_row.nullify_link(colIndex);
return;
}
Expand Down Expand Up @@ -528,6 +528,7 @@ void RLMReplaceSharedSchemaMethod(Class accessorClass, RLMObjectSchema *schema)
}

void RLMDynamicValidatedSet(RLMObjectBase *obj, NSString *propName, id val) {
RLMVerifyAttached(obj);
RLMObjectSchema *schema = obj->_objectSchema;
RLMProperty *prop = schema[propName];
if (!prop) {
Expand Down Expand Up @@ -557,7 +558,9 @@ id RLMDynamicGet(__unsafe_unretained RLMObjectBase *const obj, __unsafe_unretain
realm::Object o(obj->_realm->_realm, *obj->_info->objectSchema, obj->_row);
RLMAccessorContext c(obj);
c.currentProperty = prop;
return RLMCoerceToNil(o.get_property_value<id>(c, prop.columnName.UTF8String));
return translateError([&] {
return RLMCoerceToNil(o.get_property_value<id>(c, prop.columnName.UTF8String));
});
}

id RLMDynamicGetByName(__unsafe_unretained RLMObjectBase *const obj,
Expand Down
2 changes: 1 addition & 1 deletion Realm/RLMObjectBase.mm
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ void RLMObjectBaseSetObjectForKeyedSubscript(RLMObjectBase *object, NSString *ke
return;
}

if (object->_realm) {
if (object->_realm || object.class == object->_objectSchema.accessorClass) {
RLMDynamicValidatedSet(object, key, obj);
}
else {
Expand Down
129 changes: 129 additions & 0 deletions Realm/Tests/ObjectInterfaceTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -447,4 +447,133 @@ - (void)testRenamedProperties {
[realm cancelWriteTransaction];
}

- (void)testAllMethodsCheckThread {
RLMRealm *realm = [RLMRealm defaultRealm];
__block AllTypesObject *obj;
__block StringObject *stringObj;
NSDictionary *values = @{@"boolCol": @NO,
@"intCol": @0,
@"floatCol": @0,
@"doubleCol": @0,
@"stringCol": @"",
@"binaryCol": NSData.data,
@"dateCol": NSDate.date,
@"cBoolCol": @NO,
@"longCol": @0,
@"objectCol": NSNull.null};
[realm transactionWithBlock:^{
obj = [AllTypesObject createInRealm:realm withValue:values];
stringObj = [StringObject createInRealm:realm withValue:@[@""]];
}];
[realm beginWriteTransaction];

NSArray<NSString *> *propertyNames = [obj.objectSchema.properties valueForKey:@"name"];
[self dispatchAsyncAndWait:^{
// Getters
for (NSString *prop in propertyNames) {
RLMAssertThrowsWithReasonMatching(obj[prop], @"thread");
RLMAssertThrowsWithReasonMatching([obj valueForKey:prop], @"thread");
}
RLMAssertThrowsWithReasonMatching(obj.boolCol, @"thread");
RLMAssertThrowsWithReasonMatching(obj.intCol, @"thread");
RLMAssertThrowsWithReasonMatching(obj.floatCol, @"thread");
RLMAssertThrowsWithReasonMatching(obj.doubleCol, @"thread");
RLMAssertThrowsWithReasonMatching(obj.stringCol, @"thread");
RLMAssertThrowsWithReasonMatching(obj.binaryCol, @"thread");
RLMAssertThrowsWithReasonMatching(obj.dateCol, @"thread");
RLMAssertThrowsWithReasonMatching(obj.cBoolCol, @"thread");
RLMAssertThrowsWithReasonMatching(obj.longCol, @"thread");
RLMAssertThrowsWithReasonMatching(obj.objectCol, @"thread");
RLMAssertThrowsWithReasonMatching(obj.linkingObjectsCol, @"thread");

// Setters
for (NSString *prop in propertyNames) {
RLMAssertThrowsWithReasonMatching(obj[prop] = values[prop], @"thread");
RLMAssertThrowsWithReasonMatching([obj setValue:values[prop] forKey:prop], @"thread");
}
RLMAssertThrowsWithReasonMatching(obj.boolCol = 0, @"thread");
RLMAssertThrowsWithReasonMatching(obj.intCol = 0, @"thread");
RLMAssertThrowsWithReasonMatching(obj.floatCol = 0, @"thread");
RLMAssertThrowsWithReasonMatching(obj.doubleCol = 0, @"thread");
RLMAssertThrowsWithReasonMatching(obj.stringCol = nil, @"thread");
RLMAssertThrowsWithReasonMatching(obj.binaryCol = nil, @"thread");
RLMAssertThrowsWithReasonMatching(obj.dateCol = nil, @"thread");
RLMAssertThrowsWithReasonMatching(obj.cBoolCol = 0, @"thread");
RLMAssertThrowsWithReasonMatching(obj.longCol = 0, @"thread");
RLMAssertThrowsWithReasonMatching(obj.objectCol = nil, @"thread");
RLMAssertThrowsWithReasonMatching(obj.objectCol = [StringObject new], @"thread");
RLMAssertThrowsWithReasonMatching(obj.objectCol = stringObj, @"thread");
}];
[realm cancelWriteTransaction];
}

- (void)testAllMethodsCheckForInvalidation {
RLMRealm *realm = [RLMRealm defaultRealm];
__block StringObject *stringObj;
NSDictionary *values = @{@"boolCol": @NO,
@"intCol": @0,
@"floatCol": @0,
@"doubleCol": @0,
@"stringCol": @"",
@"binaryCol": NSData.data,
@"dateCol": NSDate.date,
@"cBoolCol": @NO,
@"longCol": @0,
@"objectCol": NSNull.null};
[realm transactionWithBlock:^{
[AllTypesObject createInRealm:realm withValue:values];
stringObj = [StringObject createInRealm:realm withValue:@[@""]];
}];

for (int i = 0; i < 2; ++i) {
AllTypesObject *obj = [[AllTypesObject allObjectsInRealm:realm] firstObject];
[realm beginWriteTransaction];
// Deleting the object directly and indirectly leave the managed
// accessor in different states, so test both
if (i == 0) {
[realm deleteObject:obj];
}
else {
[realm deleteObjects:[AllTypesObject allObjectsInRealm:realm]];
}

NSArray<NSString *> *propertyNames = [obj.objectSchema.properties valueForKey:@"name"];
// Getters
for (NSString *prop in propertyNames) {
RLMAssertThrowsWithReasonMatching(obj[prop], @"invalidated");
RLMAssertThrowsWithReasonMatching([obj valueForKey:prop], @"invalidated");
}
RLMAssertThrowsWithReasonMatching(obj.boolCol, @"invalidated");
RLMAssertThrowsWithReasonMatching(obj.intCol, @"invalidated");
RLMAssertThrowsWithReasonMatching(obj.floatCol, @"invalidated");
RLMAssertThrowsWithReasonMatching(obj.doubleCol, @"invalidated");
RLMAssertThrowsWithReasonMatching(obj.stringCol, @"invalidated");
RLMAssertThrowsWithReasonMatching(obj.binaryCol, @"invalidated");
RLMAssertThrowsWithReasonMatching(obj.dateCol, @"invalidated");
RLMAssertThrowsWithReasonMatching(obj.cBoolCol, @"invalidated");
RLMAssertThrowsWithReasonMatching(obj.longCol, @"invalidated");
RLMAssertThrowsWithReasonMatching(obj.objectCol, @"invalidated");
RLMAssertThrowsWithReasonMatching(obj.linkingObjectsCol, @"invalidated");

// Setters
for (NSString *prop in propertyNames) {
RLMAssertThrowsWithReasonMatching(obj[prop] = values[prop], @"invalidated");
RLMAssertThrowsWithReasonMatching([obj setValue:values[prop] forKey:prop], @"invalidated");
}
RLMAssertThrowsWithReasonMatching(obj.boolCol = 0, @"invalidated");
RLMAssertThrowsWithReasonMatching(obj.intCol = 0, @"invalidated");
RLMAssertThrowsWithReasonMatching(obj.floatCol = 0, @"invalidated");
RLMAssertThrowsWithReasonMatching(obj.doubleCol = 0, @"invalidated");
RLMAssertThrowsWithReasonMatching(obj.stringCol = nil, @"invalidated");
RLMAssertThrowsWithReasonMatching(obj.binaryCol = nil, @"invalidated");
RLMAssertThrowsWithReasonMatching(obj.dateCol = nil, @"invalidated");
RLMAssertThrowsWithReasonMatching(obj.cBoolCol = 0, @"invalidated");
RLMAssertThrowsWithReasonMatching(obj.longCol = 0, @"invalidated");
RLMAssertThrowsWithReasonMatching(obj.objectCol = nil, @"invalidated");
RLMAssertThrowsWithReasonMatching(obj.objectCol = [StringObject new], @"invalidated");
RLMAssertThrowsWithReasonMatching(obj.objectCol = stringObj, @"invalidated");
[realm cancelWriteTransaction];
}
}

@end

0 comments on commit ebecc65

Please sign in to comment.