Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Stackoverflow on RealmAny toString #7479

Open
wants to merge 3 commits into
base: releases
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
### Fixed
* Removed wrong `@Nullable` annotation on `RealmQuery.maxRealmAny()`.
* Fixed `RealmAny.getValueClass()` returning the `RealmObject` proxy class instead of the model class on a `RealmAny` referencing a managed `RealmObject`.
* Fixes `StackOverflow` when calling `toString` on a `DynamicRealmObject` containing a cyclic reference through a `RealmAny` field.

### Compatibility
* File format: Generates Realms with format v21. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -354,4 +354,29 @@ class DynamicRealmAnyTests {
assertEquals(0, realmAnyList.size)
}
}

@Test
fun toString_test() {
realm.executeTransaction { transactionRealm ->
val anObject = transactionRealm.createObject("RealmAnyObject")

mapOf(
RealmAny.valueOf(10.toInt()) to "RealmAny<Integer>(10)",
RealmAny.valueOf(true) to "RealmAny<Boolean>(true)",
RealmAny.valueOf("Hello world") to "RealmAny<String>(Hello world)",
RealmAny.valueOf(byteArrayOf(0, 1)) to "RealmAny<byte[]>([0, 1])",
RealmAny.valueOf(Date(100)) to "RealmAny<Date>(Thu Jan 01 01:00:00 GMT+01:00 1970)",
RealmAny.valueOf(10.0.toFloat()) to "RealmAny<Float>(10.0)",
RealmAny.valueOf(10.0.toDouble()) to "RealmAny<Double>(10.0)",
RealmAny.valueOf(Decimal128(100)) to "RealmAny<Decimal128>(100)",
RealmAny.valueOf(ObjectId(TestHelper.generateObjectIdHexString(0))) to "RealmAny<ObjectId>(0123456789abcdef01234567)",
RealmAny.valueOf(anObject) to "RealmAny<RealmAnyObject>(id:0)",
RealmAny.valueOf(UUID.fromString("00000000-0000-0000-0000-000000000000")) to "RealmAny<UUID>(00000000-0000-0000-0000-000000000000)",
RealmAny.nullValue() to "RealmAny<Null>"
).map {
anObject.setRealmAny("myRealmAny", it.key)
assertEquals("RealmAnyObject = dynamic[{myRealmAny:${it.value}}]", anObject.toString())
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1760,7 +1760,48 @@ public String toString() {
sb.append(proxyState.getRow$realm().isNull(columnKey) ? "null" : proxyState.getRow$realm().getUUID(columnKey));
break;
case MIXED:
sb.append(proxyState.getRow$realm().isNull(columnKey) ? "null" : getRealmAny(columnKey));
RealmAny realmAny = getRealmAny(columnKey);
switch (realmAny.getType()){
case INTEGER:
sb.append(String.format(Locale.US, "RealmAny<%s>(%s)", "Integer", realmAny.asInteger().toString()));
break;
case BOOLEAN:
sb.append(String.format(Locale.US, "RealmAny<%s>(%s)", "Boolean", realmAny.asBoolean().toString()));
break;
case STRING:
sb.append(String.format(Locale.US, "RealmAny<%s>(%s)", "String", realmAny.asString()));
break;
case BINARY:
sb.append(String.format(Locale.US, "RealmAny<%s>(%s)", "byte[]", Arrays.toString(realmAny.asBinary())));
break;
case DATE:
sb.append(String.format(Locale.US, "RealmAny<%s>(%s)", "Date", realmAny.asDate().toString()));
break;
case FLOAT:
sb.append(String.format(Locale.US, "RealmAny<%s>(%s)", "Float", realmAny.asFloat().toString()));
break;
case DOUBLE:
sb.append(String.format(Locale.US, "RealmAny<%s>(%s)", "Double", realmAny.asDouble().toString()));
break;
case DECIMAL128:
sb.append(String.format(Locale.US, "RealmAny<%s>(%s)", "Decimal128", realmAny.asDecimal128().toString()));
break;
case OBJECT_ID:
sb.append(String.format(Locale.US, "RealmAny<%s>(%s)", "ObjectId", realmAny.asObjectId().toString()));
break;
case OBJECT:
// if a Realm object don't print the contents as it might end in a cycle.
String tableName = realmAny.asRealmModel(DynamicRealmObject.class).proxyState.getRow$realm().getTable().getClassName();
long objectKey = realmAny.asRealmModel(DynamicRealmObject.class).proxyState.getRow$realm().getObjectKey();
sb.append(String.format(Locale.US, "RealmAny<%s>(id:%d)", tableName, objectKey));
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cmelchior , I thought it would be useful to print the object key for discerning between object instances, but not sure if it will confuse users to expose it, ideally we would be showing the PK. What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is probably fine to just show the ObjKey. It is a bit more secret, but easily available. Also, some objects might not have primary keys.

break;
case UUID:
sb.append(String.format(Locale.US, "RealmAny<%s>(%s)", "UUID", realmAny.asUUID().toString()));
break;
case NULL:
sb.append("RealmAny<Null>");
break;
}
break;
case OBJECT:
sb.append(proxyState.getRow$realm().isNullLink(columnKey)
Expand Down
10 changes: 10 additions & 0 deletions realm/realm-library/src/main/java/io/realm/RealmAny.java
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,16 @@ public static RealmAny valueOf(@Nullable RealmModel value) {
return new RealmAny((value == null) ? new NullRealmAnyOperator() : new RealmModelOperator(value));
}

/**
* Creates a new RealmAny with the specified value.
*
* @param value the RealmAny value.
* @return a new RealmAny of a DynamicRealmObject.
*/
public static RealmAny valueOf(@Nullable DynamicRealmObject value) {
return new RealmAny((value == null) ? new NullRealmAnyOperator() : new DynamicRealmModelRealmAnyOperator(value));
}

/**
* Returns true if the inner value is null, false otherwise.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,10 @@ private static <T extends RealmModel> T getRealmModel(BaseRealm realm, NativeRea
return realm.get((Class<T>) DynamicRealmObject.class, className, nativeRealmAny.getRealmModelRowKey());
}

DynamicRealmModelRealmAnyOperator(RealmModel realmModel) {
super(realmModel);
}

DynamicRealmModelRealmAnyOperator(BaseRealm realm, NativeRealmAny nativeRealmAny) {
super(getRealmModel(realm, nativeRealmAny));
}
Expand Down