From 4e3f660cce5a83d6f906a7c28a4017b3c39ddc01 Mon Sep 17 00:00:00 2001 From: Clemente Tort Date: Thu, 20 May 2021 13:23:22 +0200 Subject: [PATCH 1/2] Fix stackoverflow when converting to string a DynamicRealmObject containing a cycle through a RealmAny field --- CHANGELOG.md | 1 + .../io/realm/realmany/DynamicRealmAnyTests.kt | 25 +++++++++++ .../java/io/realm/DynamicRealmObject.java | 43 ++++++++++++++++++- .../src/main/java/io/realm/RealmAny.java | 10 +++++ .../main/java/io/realm/RealmAnyOperator.java | 4 ++ 5 files changed, 82 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cb64f138b..06bb670db1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/realmany/DynamicRealmAnyTests.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/realmany/DynamicRealmAnyTests.kt index dfeb076cff..2c33d794a6 100644 --- a/realm/realm-library/src/androidTest/kotlin/io/realm/realmany/DynamicRealmAnyTests.kt +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/realmany/DynamicRealmAnyTests.kt @@ -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(10)", + RealmAny.valueOf(true) to "RealmAny(true)", + RealmAny.valueOf("Hello world") to "RealmAny(Hello world)", + RealmAny.valueOf(byteArrayOf(0, 1)) to "RealmAny([0, 1])", + RealmAny.valueOf(Date(100)) to "RealmAny(Thu Jan 01 01:00:00 GMT+01:00 1970)", + RealmAny.valueOf(10.0.toFloat()) to "RealmAny(10.0)", + RealmAny.valueOf(10.0.toDouble()) to "RealmAny(10.0)", + RealmAny.valueOf(Decimal128(100)) to "RealmAny(100)", + RealmAny.valueOf(ObjectId(TestHelper.generateObjectIdHexString(0))) to "RealmAny(0123456789abcdef01234567)", + RealmAny.valueOf(anObject) to "RealmAny(id:0)", + RealmAny.valueOf(UUID.fromString("00000000-0000-0000-0000-000000000000")) to "RealmAny(00000000-0000-0000-0000-000000000000)", + RealmAny.nullValue() to "RealmAny" + ).map { + anObject.setRealmAny("myRealmAny", it.key) + assertEquals("RealmAnyObject = dynamic[{myRealmAny:${it.value}}]", anObject.toString()) + } + } + } } diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java index 103790c6ef..b5e39e03d1 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java @@ -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)); + break; + case UUID: + sb.append(String.format(Locale.US, "RealmAny<%s>(%s)", "UUID", realmAny.asUUID().toString())); + break; + case NULL: + sb.append("RealmAny"); + break; + } break; case OBJECT: sb.append(proxyState.getRow$realm().isNullLink(columnKey) diff --git a/realm/realm-library/src/main/java/io/realm/RealmAny.java b/realm/realm-library/src/main/java/io/realm/RealmAny.java index 0d71fd6606..0d13be7530 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmAny.java +++ b/realm/realm-library/src/main/java/io/realm/RealmAny.java @@ -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. * diff --git a/realm/realm-library/src/main/java/io/realm/RealmAnyOperator.java b/realm/realm-library/src/main/java/io/realm/RealmAnyOperator.java index bf96c349cd..9a04356dd4 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmAnyOperator.java +++ b/realm/realm-library/src/main/java/io/realm/RealmAnyOperator.java @@ -448,6 +448,10 @@ private static T getRealmModel(BaseRealm realm, NativeRea return realm.get((Class) DynamicRealmObject.class, className, nativeRealmAny.getRealmModelRowKey()); } + DynamicRealmModelRealmAnyOperator(RealmModel realmModel) { + super(realmModel); + } + DynamicRealmModelRealmAnyOperator(BaseRealm realm, NativeRealmAny nativeRealmAny) { super(getRealmModel(realm, nativeRealmAny)); } From 32b651eba64e4529243bff5f8fd35171e8cf3007 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 17 Jun 2021 12:00:54 +0200 Subject: [PATCH 2/2] Fix changelog and minor cleanup --- CHANGELOG.md | 18 +++++++++++++++++- .../main/java/io/realm/DynamicRealmObject.java | 4 ++-- version.txt | 2 +- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7976aec56f..5bc8ad9d65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +## 10.6.1 (YYYY-MM-DD) + +### Enhancements +* None. + +### Fixed +* `StackOverflow` when calling `toString()` on a `DynamicRealmObject` containing a cyclic reference through a `RealmAny` field. + +### Compatibility +* File format: Generates Realms with format v22. 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. +* APIs are backwards compatible with all previous release of realm-java in the 10.6.y series. +* Realm Studio 11.0.0-alpha.0 or above is required to open Realms created by this version. + +### Internal +* None. + + ## 10.6.0 (2021-06-15) This release combines all changes from 10.6.0-BETA.1 and 10.6.0-BETA.2. @@ -58,7 +75,6 @@ This release combines all changes from 10.6.0-BETA.1 and 10.6.0-BETA.2. ### 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 v22. 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. diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java index 6e31de5c5d..5cf2fe6970 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java @@ -1790,10 +1790,10 @@ public String toString() { 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. + // 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)); + sb.append(String.format(Locale.US, "RealmAny<%s>(objKey:%d)", tableName, objectKey)); break; case UUID: sb.append(String.format(Locale.US, "RealmAny<%s>(%s)", "UUID", realmAny.asUUID().toString())); diff --git a/version.txt b/version.txt index 55ecf6fee6..c41a9c46e3 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -10.6.0 \ No newline at end of file +10.6.1-SNAPSHOT \ No newline at end of file