Skip to content

Commit

Permalink
Avoid assertion crash on realm_get_object if classKey is unknown to o…
Browse files Browse the repository at this point in the history
…bject store
  • Loading branch information
nielsenko committed Dec 16, 2022
1 parent 9acd3a2 commit cd47f1d
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 21 deletions.
2 changes: 2 additions & 0 deletions lib/src/native/realm_core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ class _RealmCore {
_realmLib.realm_config_set_max_number_of_active_versions(configHandle._pointer, config.maxNumberOfActiveVersions!);
}
if (config is LocalConfiguration) {
//_realmLib.realm_config_set_schema_mode(configHandle._pointer, realm_schema_mode.RLM_SCHEMA_MODE_ADDITIVE_DISCOVERED);
if (config.initialDataCallback != null) {
_realmLib.realm_config_set_data_initialization_function(
configHandle._pointer,
Expand Down Expand Up @@ -2714,6 +2715,7 @@ extension on Pointer<realm_value_t> {
case realm_value_type.RLM_TYPE_LINK:
final objectKey = ref.values.link.target;
final classKey = ref.values.link.target_table;
if (realm.metadata.getByClassKeyIfExists(classKey) == null) return null; // temprorary workaround to avoid crash on assertion
return realmCore._getObject(realm, classKey, objectKey);
case realm_value_type.RLM_TYPE_BINARY:
throw Exception("Not implemented");
Expand Down
21 changes: 10 additions & 11 deletions lib/src/realm_class.dart
Original file line number Diff line number Diff line change
Expand Up @@ -767,8 +767,9 @@ class RealmLogLevel {

/// @nodoc
class RealmMetadata {
final Map<Type, RealmObjectMetadata> _typeMap = <Type, RealmObjectMetadata>{};
final Map<String, RealmObjectMetadata> _stringMap = <String, RealmObjectMetadata>{};
final _typeMap = <Type, RealmObjectMetadata>{};
final _stringMap = <String, RealmObjectMetadata>{};
final _classKeyMap = <int, RealmObjectMetadata>{};

RealmMetadata._(Iterable<RealmObjectMetadata> objectMetadatas) {
for (final metadata in objectMetadatas) {
Expand All @@ -777,6 +778,7 @@ class RealmMetadata {
} else {
_stringMap[metadata.schema.name] = metadata;
}
_classKeyMap[metadata.classKey] = metadata;
}
}

Expand All @@ -803,17 +805,14 @@ class RealmMetadata {
return metadata;
}

Tuple<Type, RealmObjectMetadata> getByClassKey(int key) {
final type = _typeMap.entries.firstWhereOrNull((e) => e.value.classKey == key);
if (type != null) {
return Tuple(type.key, type.value);
}
RealmObjectMetadata? getByClassKeyIfExists(int key) => _classKeyMap[key];

final metadata = _stringMap.values.firstWhereOrNull((e) => e.classKey == key);
if (metadata != null) {
return Tuple(RealmObjectBase, metadata);
Tuple<Type, RealmObjectMetadata> getByClassKey(int key) {
final meta = _classKeyMap[key];
if (meta != null) {
final type = _typeMap.entries.firstWhereOrNull((e) => e.value.classKey == key)?.key ?? RealmObjectBase;
return Tuple(type, meta);
}

throw RealmError("Object with classKey $key not found in the current Realm's schema.");
}
}
Expand Down
2 changes: 1 addition & 1 deletion lib/src/realm_object.dart
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ class RealmCoreAccessor implements RealmAccessor {
late RealmObjectMetadata targetMetadata;

if (propertyMeta.propertyType == RealmPropertyType.mixed) {
final tuple = meta.getByClassKey(realmCore.getClassKey(object.handle));
final tuple = meta.getByClassKey(realmCore.getClassKey(value));
type = tuple.item1;
targetMetadata = tuple.item2;
} else {
Expand Down
42 changes: 33 additions & 9 deletions test/realm_value_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
//
////////////////////////////////////////////////////////////////////////////////
import 'dart:io';

import 'package:test/test.dart' hide test, throws;
import '../lib/realm.dart';

Expand Down Expand Up @@ -50,17 +52,19 @@ void main() {
42,
3.14,
AnythingGoes(),
Stuff(),
now,
ObjectId.fromTimestamp(now),
Uuid.v4(),
];

final config = Configuration.inMemory([AnythingGoes.schema, TuckedIn.schema]);
final config = Configuration.inMemory([AnythingGoes.schema, Stuff.schema, TuckedIn.schema]);
final realm = getRealm(config);

for (final x in values) {
test('Roundtrip ${x.runtimeType} $x', () {
final something = realm.write(() => realm.add(AnythingGoes(oneAny: RealmValue.from(x))));
expect(something.oneAny.type, x.runtimeType);
expect(something.oneAny.value, x);
expect(something.oneAny, RealmValue.from(x));
});
Expand Down Expand Up @@ -96,7 +100,9 @@ void main() {
break;
case AnythingGoes: // RealmObject won't work with switch
expect(value, isA<AnythingGoes>());
expect(value, isA<RealmObject>());
break;
case Stuff: // RealmObject won't work with switch
expect(value, isA<Stuff>());
break;
case DateTime:
expect(value, isA<DateTime>());
Expand Down Expand Up @@ -136,28 +142,45 @@ void main() {
expect(type, ObjectId);
} else if (value is AnythingGoes) {
expect(type, AnythingGoes);
} else if (value is Stuff) {
expect(type, Stuff);
} else {
fail('$value not handled correctly in if-is');
}
});
}

test('Unknown embedded', () {
test('Unknown schema for RealmValue.value after bad migration', () {
{
final config = Configuration.local([AnythingGoes.schema, Stuff.schema], path: 'unknown_embedded');
final config = Configuration.local([AnythingGoes.schema, Stuff.schema], path: 'unknown_embedded', schemaVersion: 0);
Realm.deleteRealm(config.path);
final realm = Realm(config);

realm.write(() => realm.add(AnythingGoes(oneAny: RealmValue.realmObject(Stuff()))));
final something = realm.write(() => realm.add(AnythingGoes(oneAny: RealmValue.realmObject(Stuff()))));
expect(something.oneAny, isA<RealmValue>());
expect(something.oneAny.value, isA<Stuff>());
expect(something.oneAny.as<Stuff>().i, 42);

realm.close();
}

// From here on Stuff is unknown
final config = Configuration.local([AnythingGoes.schema], path: 'unknown_embedded');
final config = Configuration.local(
[AnythingGoes.schema],
path: 'unknown_embedded',
schemaVersion: 1,
migrationCallback: (migration, oldSchemaVersion) {
// forget to handle RealmValue pointing to Stuff
},
);
final realm = getRealm(config);

final something = realm.all<AnythingGoes>()[0];
expect(something.oneAny, isA<RealmObject>());
}, skip: 'This currently crashes!');
// something.oneAny points to a Stuff, but that is not known, so returns null.
// A better option would be to return a DynamicRealmObject, but c-api does
// not currently allow this.
expect(something.oneAny, const RealmValue.nullValue()); // at least we don't crash :-)
});
});

group('List<RealmValue>', () {
Expand All @@ -169,12 +192,13 @@ void main() {
42,
3.14,
AnythingGoes(),
Stuff(),
now,
ObjectId.fromTimestamp(now),
Uuid.v4(),
];

final config = Configuration.inMemory([AnythingGoes.schema]);
final config = Configuration.inMemory([AnythingGoes.schema, Stuff.schema]);
final realm = getRealm(config);

test('Roundtrip', () {
Expand Down

0 comments on commit cd47f1d

Please sign in to comment.