wordpress-mobile / wordpress-mobile/WordPress-Android

Uncaught NPE in siteResponseToSiteModel when WP.com returns blogging_prompts_settings with null reminders_days/reminders_time

Open Beginner friendly
#23,014 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

[Type] Bug Blogging Prompts
Dominant language
Kotlin
Stars
3.2k
Forks
1.4k
Avg merge
1d 11h
Merged PRs (30d)
69

Description

Summary

  • SiteRestClient.siteResponseToSiteModel() parses blogging_prompts_settings from the WP.com sites response and dereferences two sub-fields without a null guard. If the server returns blogging_prompts_settings as a non-null object but with reminders_days or reminders_time null/absent, the parse throws an uncaught NullPointerException and the entire site fetch fails.
  • Both fields are plain unannotated Java fields (Kotlin platform types), so the compiler inserts no caller-side null check — same mechanism as the null-URL crash currently live in production (Sentry JETPACK-ANDROID-1JAS).
  • Latent today — no production occurrences yet — but reachable: blogging_prompts_settings ships under options, which is in the fields= allowlist, and there is zero test coverage of this path.

Root cause

In libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpcom/site/SiteRestClient.kt, inside siteResponseToSiteModel() (the from.options.blogging_prompts_settings?.let { ... } block, ~L1131–1150). The ?.let guards the parent object being null; it does nothing for the sub-fields:

1. reminders_days — null Map dereference (~L1137–1143) ❌
site.setIsBloggingReminderOnMonday(it.reminders_days["monday"] ?: false)
// ... through Sunday

it.reminders_days["monday"] desugars to it.reminders_days.get("monday"). The ?: false operates on the return value of get, not the receiver — so a null reminders_days map NPEs on the .get call. These seven lines are not inside any try/catch.

reminders_days is declared public Map<String, Boolean> reminders_days; (SiteWPComRestResponse.java:107) — no annotation, no @SerializedName, no custom deserializer. Stock Gson leaves it null when the key is absent or its value is JSON null.

2. reminders_time — null String dereference (~L1144–1149) ❌
try {
    site.bloggingReminderHour = it.reminders_time.split(".")[0].toInt()
    site.bloggingReminderMinute = it.reminders_time.split(".")[1].toInt()
} catch (ex: NumberFormatException) {
    AppLog.e(API, "Received malformed blogging reminder time: " + ex.message)
}

it.reminders_time.split(".") throws NPE on a null reminders_time. The surrounding try catches only NumberFormatExceptionNullPointerException is not a subclass of it (NFE → IllegalArgumentException; NPE → RuntimeException), so the NPE escapes the catch. reminders_time is public String reminders_time; (SiteWPComRestResponse.java:108), same platform-type/Gson story.

The crash propagates uncaught out of siteResponseToSiteModel() to its callers (fetchSite, fetchSites, and the site-feature path).

Why it can happen

The "the server always sends a complete blogging_prompts_settings object" assumption is unverified and unenforced — no @NonNull, no test, no fixture. We have direct, recent production proof that this exact endpoint violates field-presence assumptions: site.url = from.URL reads an identically-unannotated field and just started returning null in production (Sentry JETPACK-ANDROID-1JAS, ~1,300 crashes / ~180 users in the first ~7.5h). The method itself already distrusts this object's contents (the NumberFormatException catch on reminders_time) — it just doesn't guard the fields' presence. /me/sites/ and /sites/$site/ are also documented in-method as returning different shapes for the same fields.

Evidence

Adversarially validated by three independent reviewers; two reproduced the exact exceptions with a standalone kotlinc repro of the platform-type + deref pattern:

  • reminders_days null → java.lang.NullPointerException: Cannot invoke "java.util.Map.get(Object)" because "it.reminders_days" is null
  • reminders_time null → java.lang.NullPointerException: reminders_time must not be null (Kotlin Intrinsics.checkNotNullExpressionValue emitted before split)

Bytecode confirmed the ?: false Elvis sits on the get result, after the null receiver has already been invoked.

Fix

Null-safe both dereferences (the surrounding code already treats this object as untrusted):

// reminders_days — all seven day lines:
site.setIsBloggingReminderOnMonday(it.reminders_days?.get("monday") ?: false)

// reminders_time — safe-call before split (or broaden the catch to cover NPE/IndexOutOfBounds):
it.reminders_time?.split(".")?.let { parts ->
    try {
        site.bloggingReminderHour = parts[0].toInt()
        site.bloggingReminderMinute = parts[1].toInt()
    } catch (ex: NumberFormatException) {
        AppLog.e(API, "Received malformed blogging reminder time: " + ex.message)
    }
}

A regression test that feeds siteResponseToSiteModel a blogging_prompts_settings with each sub-field null would lock this in — the path currently has no coverage.

Related

  • Live null-URL incident (same root pattern, different field): Sentry JETPACK-ANDROID-1JAS. The URL case is being addressed server-side (it's the only field that crashes the java.net.URI parser); these two are the remaining client-side null-deref siblings in the same parse method.

Found while triaging the null-URL crash.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpcom/site/SiteRestClient.kt at siteResponseToSiteModel(), especially the blogging_prompts_settings block. Review the null handling for reminders_days and reminders_time, then add regression coverage that supplies each sub-field as null and confirms site parsing completes without an exception. Use the existing SiteWPComRestResponse.java field declarations to understand the response shape.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, kotlin
Domain
api, mobile-dev
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.