diff --git a/CHANGELOG.md b/CHANGELOG.md index 4054f490ef..1b15b799db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - Android Profiler on calling thread ([#2691](https://github.com/getsentry/sentry-java/pull/2691)) - Use `configureScope` instead of `withScope` in `Hub.close()`. This ensures that the main scope releases the in-memory data when closing a hub instance. ([#2688](https://github.com/getsentry/sentry-java/pull/2688)) +- Remove null keys/values before creating concurrent hashmap in order to avoid NPE ([#2708](https://github.com/getsentry/sentry-java/pull/2708)) ### Dependencies diff --git a/sentry/src/main/java/io/sentry/util/CollectionUtils.java b/sentry/src/main/java/io/sentry/util/CollectionUtils.java index 702037e535..7dd54ef2bf 100644 --- a/sentry/src/main/java/io/sentry/util/CollectionUtils.java +++ b/sentry/src/main/java/io/sentry/util/CollectionUtils.java @@ -34,7 +34,8 @@ public static int size(final @NotNull Iterable data) { } /** - * Creates a new {@link ConcurrentHashMap} as a shallow copy of map given by parameter. + * Creates a new {@link ConcurrentHashMap} as a shallow copy of map given by parameter. Also makes + * sure no null keys or values are put into the resulting {@link ConcurrentHashMap}. * * @param map the map to copy * @param the type of map keys @@ -44,7 +45,14 @@ public static int size(final @NotNull Iterable data) { public static @Nullable Map newConcurrentHashMap( @Nullable Map map) { if (map != null) { - return new ConcurrentHashMap<>(map); + Map concurrentMap = new ConcurrentHashMap<>(); + + for (Map.Entry entry : map.entrySet()) { + if (entry.getKey() != null && entry.getValue() != null) { + concurrentMap.put(entry.getKey(), entry.getValue()); + } + } + return concurrentMap; } else { return null; } diff --git a/sentry/src/test/java/io/sentry/protocol/SentryBaseEventSerializationTest.kt b/sentry/src/test/java/io/sentry/protocol/SentryBaseEventSerializationTest.kt index 02d9d298b8..b0f8e429f6 100644 --- a/sentry/src/test/java/io/sentry/protocol/SentryBaseEventSerializationTest.kt +++ b/sentry/src/test/java/io/sentry/protocol/SentryBaseEventSerializationTest.kt @@ -87,9 +87,10 @@ class SentryBaseEventSerializationTest { @Test fun deserialize() { + val inputJson = SerializationUtils.sanitizedFile("json/sentry_base_event_with_null_extra.json") val expectedJson = SerializationUtils.sanitizedFile("json/sentry_base_event.json") val actual = SerializationUtils.deserializeJson( - expectedJson, + inputJson, Sut.Deserializer(), fixture.logger ) diff --git a/sentry/src/test/java/io/sentry/util/CollectionUtilsTest.kt b/sentry/src/test/java/io/sentry/util/CollectionUtilsTest.kt index 462900247a..266d188b4d 100644 --- a/sentry/src/test/java/io/sentry/util/CollectionUtilsTest.kt +++ b/sentry/src/test/java/io/sentry/util/CollectionUtilsTest.kt @@ -1,5 +1,7 @@ package io.sentry.util +import io.sentry.JsonObjectReader +import java.io.StringReader import kotlin.test.Test import kotlin.test.assertEquals @@ -25,4 +27,39 @@ class CollectionUtilsTest { assertEquals("key3", result[1]) assertEquals(2, result.size) } + + @Test + fun `concurrent hashmap creation with null returns null`() { + val result: Map? = CollectionUtils.newConcurrentHashMap(null) + assertEquals(null, result) + } + + @Test + fun `concurrent hashmap creation ignores null keys`() { + val map = mutableMapOf("key1" to "value1", null to "value2", "key3" to "value3") + val result = CollectionUtils.newConcurrentHashMap(map) + + assertEquals(2, result?.size) + assertEquals("value1", result?.get("key1")) + assertEquals("value3", result?.get("key3")) + } + + @Test + fun `concurrent hashmap creation ignores null values`() { + val json = """ + { + "key1": "value1", + "key2": null, + "key3": "value3" + } + """.trimIndent() + val reader = JsonObjectReader(StringReader(json)) + val deserializedMap = reader.nextObjectOrNull() as Map + + val result: Map? = CollectionUtils.newConcurrentHashMap(deserializedMap) + + assertEquals(2, result?.size) + assertEquals("value1", result?.get("key1")) + assertEquals("value3", result?.get("key3")) + } } diff --git a/sentry/src/test/resources/json/sentry_base_event_with_null_extra.json b/sentry/src/test/resources/json/sentry_base_event_with_null_extra.json new file mode 100644 index 0000000000..c8e9887895 --- /dev/null +++ b/sentry/src/test/resources/json/sentry_base_event_with_null_extra.json @@ -0,0 +1,234 @@ +{ + "event_id": "afcb46b1140ade5187c4bbb5daa804df", + "contexts": + { + "app": + { + "app_identifier": "3b7a3313-53b4-43f4-a6a1-7a7c36a9b0db", + "app_start_time": "1918-11-17T07:46:04.000Z", + "device_app_hash": "3d1fcf36-2c25-4378-bdf8-1e65239f1df4", + "build_type": "d78c56cd-eb0f-4213-8899-cd10ddf20763", + "app_name": "873656fd-f620-4edf-bb7a-a0d13325dba0", + "app_version": "801aab22-ad4b-44fb-995c-bacb5387e20c", + "app_build": "660f0cde-eedb-49dc-a973-8aa1c04f4a28", + "permissions": + { + "WRITE_EXTERNAL_STORAGE": "not_granted", + "CAMERA": "granted" + }, + "in_foreground": true + }, + "browser": + { + "name": "e1c723db-7408-4043-baa7-f4e96234e5dc", + "version": "724a48e9-2d35-416b-9f79-132beba2473a" + }, + "device": + { + "name": "83f1de77-fdb0-470e-8249-8f5c5d894ec4", + "manufacturer": "e21b2405-e378-4a0b-ad2c-4822d97cd38c", + "brand": "1abbd13e-d1ca-4d81-bd1b-24aa2c339cf9", + "family": "67a4b8ea-6c38-4c33-8579-7697f538685c", + "model": "d6ca2f35-bcc5-4dd3-ad64-7c3b585e02fd", + "model_id": "d3f133bd-b0a2-4aa4-9eed-875eba93652e", + "archs": + [ + "856e5da3-774c-4663-a830-d19f0b7dbb5b", + "b345bd5a-90a5-4301-a5a2-6c102d7589b6", + "fd7ed64e-a591-49e0-8dc1-578234356d23", + "8cec4101-0305-480b-91ee-f3c007f668c3", + "22583b9b-195e-49bf-bfe8-825ae3a346f2", + "8675b7aa-5b94-42d0-bc14-72ea1bb7112e" + ], + "battery_level": 0.45770407, + "charging": false, + "online": true, + "orientation": "portrait", + "simulator": true, + "memory_size": -6712323365568152393, + "free_memory": -953384122080236886, + "usable_memory": -8999512249221323968, + "low_memory": false, + "storage_size": -3227905175393990709, + "free_storage": -3749039933924297357, + "external_storage_size": -7739608324159255302, + "external_free_storage": -1562576688560812557, + "screen_width_pixels": 1101873181, + "screen_height_pixels": 1902392170, + "screen_density": 0.9829039, + "screen_dpi": -2092079070, + "boot_time": "2004-11-04T08:38:00.000Z", + "timezone": "Europe/Vienna", + "id": "e0fa5c8d-83f5-4e70-bc60-1e82ad30e196", + "language": "6dd45f60-111d-42d8-9204-0452cc836ad8", + "connection_type": "9ceb3a6c-5292-4ed9-8665-5732495e8ed4", + "battery_temperature": 0.14775127, + "processor_count": 4, + "processor_frequency": 800.0, + "cpu_description": "cpu0" + }, + "gpu": + { + "name": "d623a6b5-e1ab-4402-931b-c06f5a43a5ae", + "id": -596576280, + "vendor_id": "1874778041", + "vendor_name": "d732cf76-07dc-48e2-8920-96d6bfc2439d", + "memory_size": -1484004451, + "api_type": "95dfc8bc-88ae-4d66-b85f-6c88ad45b80f", + "multi_threaded_rendering": true, + "version": "3f3f73c3-83a2-423a-8a6f-bb3de0d4a6ae", + "npot_support": "e06b074a-463c-45de-a959-cbabd461d99d" + }, + "os": + { + "name": "686a11a8-eae7-4393-aa10-a1368d523cb2", + "version": "3033f32d-6a27-4715-80c8-b232ce84ca61", + "raw_description": "eb2d0c5e-f5d4-49c7-b876-d8a654ee87cf", + "build": "bd197b97-eb68-49c3-9d07-ef789caf3069", + "kernel_version": "1df24aec-3a6f-49a9-8b50-69ae5f9dde08", + "rooted": true + }, + "response": + { + "cookies": "PHPSESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;", + "headers": { + "content-type": "text/html" + }, + "status_code": 500, + "body_size": 1000, + "arbitrary_field": "arbitrary" + }, + "runtime": + { + "name": "4ed019c4-9af9-43e0-830e-bfde9fe4461c", + "version": "16534f6b-1670-4bb8-aec2-647a1b97669b", + "raw_description": "773b5b05-a0f9-4ee6-9f3b-13155c37ad6e" + }, + "trace": + { + "trace_id": "afcb46b1140ade5187c4bbb5daa804df", + "span_id": "bf6b582d-8ce3-412b-a334-f4c5539b9602", + "parent_span_id": "c7500f2a-d4e6-4f5f-a0f4-6bb67e98d5a2", + "op": "e481581d-35a4-4e97-8a1c-b554bf49f23e", + "description": "c204b6c7-9753-4d45-927d-b19789bfc9a5", + "status": "resource_exhausted", + "tags": + { + "2a5fa3f5-7b87-487f-aaa5-84567aa73642": "4781d51a-c5af-47f2-a4ed-f030c9b3e194", + "29106d7d-7fa4-444f-9d34-b9d7510c69ab": "218c23ea-694a-497e-bf6d-e5f26f1ad7bd", + "ba9ce913-269f-4c03-882d-8ca5e6991b14": "35a74e90-8db8-4610-a411-872cbc1030ac" + } + } + }, + "sdk": + { + "name": "3e934135-3f2b-49bc-8756-9f025b55143e", + "version": "3e31738e-4106-42d0-8be2-4a3a1bc648d3", + "packages": + [ + { + "name": "b59a1949-9950-4203-b394-ddd8d02c9633", + "version": "3d7790f3-7f32-43f7-b82f-9f5bc85205a8" + } + ], + "integrations": + [ + "daec50ae-8729-49b5-82f7-991446745cd5", + "8fc94968-3499-4a2c-b4d7-ecc058d9c1b0" + ] + }, + "request": + { + "url": "67369bc9-64d3-4d31-bfba-37393b145682", + "method": "8185abc3-5411-4041-a0d9-374180081044", + "query_string": "e3dc7659-f42e-413c-a07c-52b24bf9d60d", + "data": + { + "d9d709db-b666-40cc-bcbb-093bb12aad26": "1631d0e6-96b7-4632-85f8-ef69e8bcfb16" + }, + "cookies": "d84f4cfc-5310-4818-ad4f-3f8d22ceaca8", + "headers": + { + "c4991f66-9af9-4914-ac5e-e4854a5a4822": "37714d22-25a7-469b-b762-289b456fbec3" + }, + "env": + { + "6d569c89-5d5e-40e0-a4fc-109b20a53778": "ccadf763-44e4-475c-830c-de6ba0dbd202" + }, + "other": + { + "669ff1c1-517b-46dc-a889-131555364a56": "89043294-f6e1-4e2e-b152-1fdf9b1102fc" + }, + "fragment": "fragment", + "body_size": 1000 + }, + "tags": + { + "79ba41db-8dc6-4156-b53e-6cf6d742eb88": "690ce82f-4d5d-4d81-b467-461a41dd9419" + }, + "release": "be9b8133-72f5-497b-adeb-b0a245eebad6", + "environment": "89204175-e462-4628-8acb-3a7fa8d8da7d", + "platform": "38decc78-2711-4a6a-a0be-abb61bfa5a6e", + "user": + { + "email": "c4d61c1b-c144-431e-868f-37a46be5e5f2", + "id": "efb2084b-1871-4b59-8897-b4bd9f196a01", + "username": "60c05dff-7140-4d94-9a61-c9cdd9ca9b96", + "ip_address": "51d22b77-f663-4dbe-8103-8b749d1d9a48", + "name": "c8c60762-b1cf-11ed-afa1-0242ac120002", + "geo": { + "city": "0e6ed0b0-b1c5-11ed-afa1-0242ac120002", + "country_code": "JP", + "region": "273a3d0a-b1c5-11ed-afa1-0242ac120002" + }, + "data": + { + "dc2813d0-0f66-4a3f-a995-71268f61a8fa": "991659ad-7c59-4dd3-bb89-0bd5c74014bd" + } + }, + "server_name": "e6f0ae04-0f40-421b-aad1-f68c15117937", + "dist": "27022a08-aace-40c6-8d0a-358a27fcaa7a", + "breadcrumbs": + [ + { + "timestamp": "2009-11-16T01:08:47.000Z", + "message": "46f233c0-7c2d-488a-b05a-7be559173e16", + "type": "ace57e2e-305e-4048-abf0-6c8538ea7bf4", + "data": + { + "6607d106-d426-462b-af74-f29fce978e48": "149bb94a-1387-4484-90be-2df15d1322ab" + }, + "category": "b6eea851-5ae5-40ed-8fdd-5e1a655a879c", + "level": "debug" + } + ], + "debug_meta": + { + "sdk_info": + { + "sdk_name": "182c4407-c1e1-4427-9b5a-ad2e22b1046a", + "version_major": 2045114005, + "version_minor": 1436566288, + "version_patchlevel": 1637914973 + }, + "images": + [ + { + "uuid": "8994027e-1cd9-4be8-b611-88ce08cf16e6", + "type": "fd6e053b-a7fe-4754-916e-bfb3ab77177d", + "debug_id": "8c653f5a-3418-4823-ba91-29a84c9c1235", + "debug_file": "55cc15dd-51f3-4cad-803c-6fd90eac21f6", + "code_id": "01230ece-f729-4af4-8b48-df74700aa4bf", + "code_file": "415c8995-1cb4-4bed-ba5c-5b3d6ba1ad47", + "image_addr": "8a258c81-641d-4e54-b06e-a0f56b1ee2ef", + "image_size": -7905338721846826571, + "arch": "d00d5bea-fb5c-43c9-85f0-dc1350d957a4" + } + ] + }, + "extra": + { + "34a7d067-fad2-49d9-97b9-71eff243127b": "fe3dc1cf-4a99-4213-85bb-e0957b8349b8", + "keyWithNullValue": null + } +}