Skip to content

Viewport lazy-load profiling crashes on object-shaped profile data #1122

Description

@pirate-bot

Summary

Viewport-based lazy-load processing can terminate a frontend request when stored page-profile data is object-shaped rather than array-shaped.

Expected behavior: Invalid or unavailable profile data is treated as unavailable, allowing image processing to continue without a fatal error.

Actual behavior: The page-profiler lookup accesses the object as an array and throws Cannot use object of type stdClass as array.

Impact: Affected frontend page requests can fail during Optimole image replacement.

Customer context

  • Product / area: Optimole WordPress plugin, page profiler and viewport lazy loading
  • Version: 4.2.11
  • Environment: WordPress 7.1, PHP 8.3.31
  • Integration / third party: An external object cache may be in use; this is inferred from the plugin's storage selection and is not confirmed by telemetry.
  • Reported error / symptom: Cannot use object of type stdClass as array at inc/v2/PageProfiler/Profile.php:369
  • Impact: 10 telemetry occurrences across 2 production sites between 2026-08-27 and 2026-08-28 during frontend requests.

Reproduction notes

  1. Enable the viewport-based lazy-load workflow so Optml_Lazyload_Replacer::can_lazyload_for() consults page-profile data.
  2. Arrange for the current profile's mobile or desktop storage value, or its af member, to be object-shaped rather than an array.
  3. Process frontend HTML containing an eligible image.

Observed in production: the lookup throws Cannot use object of type stdClass as array from the reported Profile.php location. The exact storage backend and data origin are unavailable.

Diagnosis

Conclusion

Telemetry directly records the exception in the viewport-profile lookup. In v4.2.11, that lookup assumes each stored device profile and its af value are arrays, while the selected storage reader returns its value without runtime shape validation. An object-shaped value therefore reaches array access and produces the captured error. The internal REST write path constructs arrays, so the source of the object-shaped stored value remains unconfirmed.

Where this likely occurs

  • inc/manager.phpOptml_Manager::replace_content() lines 446-482 enables page profiling, loads current profile data, then processes frontend image content.
  • inc/lazyload_replacer.phpOptml_Lazyload_Replacer::can_lazyload_for() lines 461-478 calls Profile::is_in_all_viewports() for viewport lazy loading before deciding whether an image is lazy-loaded.
  • inc/v2/PageProfiler/Profile.phpProfile::set_current_profile_data() lines 336-347 copies each storage value into the current profile without type normalization; Profile::is_in_all_viewports() lines 362-375 then accesses ['af'][$image_id].
  • inc/v2/PageProfiler/Storage/ObjectCache.phpObjectCache::get() lines 54-62 returns wp_cache_get() directly despite the documented array|false return contract.
  • v4.2.11 is release commit e9ee20c5a5e10565d2f8ffc56aa77fe76b202332; the failing is_in_all_viewports() access is present there and unchanged through the inspected HEAD. The lookup originated in d096d4bb28e01ddc56c5fce937adbd20d4b0fa63.

Engineering notes

  • The profiler selects Storage\ObjectCache when WordPress reports an external object cache, or Storage\Transients otherwise, via Profile::__construct() in inc/v2/PageProfiler/Profile.php lines 89-106.
  • Profile::store() writes the expected device data as arrays containing af, bg, and lcp in inc/v2/PageProfiler/Profile.php lines 183-218. Optml_Rest::optimizations() also normalizes its collected payload before calling that method at inc/rest.php lines 935-1046.
  • The profiler storage class is filterable through optml_page_profiler_storage; a custom storage implementation is another possible source of an object-shaped value. Neither cache backend behavior nor a custom storage integration was available for inspection.
  • The failure occurs before the existing is_data_available() result is used in can_lazyload_for(), so that later availability check cannot prevent this exception for malformed non-empty device values.

Test coverage status

tests/test-lazyload-viewport.php covers normal array-backed profile lookup in test_is_in_viewport_detection() lines 179-203, stored structure in test_profile_data_storage_and_retrieval() lines 211-247, and missing or partial device data in lines 302-343. No relevant coverage was found during inspection for object-shaped cache values, malformed profile storage, or custom page-profiler storage. No test suite was run.

What to verify or explore next

  • Reproduce with a page-profile storage value whose device entry or af member is an object, under viewport lazy loading.
  • Check affected sites' active object-cache drop-ins and whether the optml_page_profiler_storage filter is present.
  • Run the page-profiler viewport test suite with array, false, and object-shaped stored values.
  • Confirm whether transient-backed storage can encounter the same serialized data shape.

Unknowns / follow-up

  • Telemetry does not identify which stored profile field was object-shaped or how it entered storage.
  • The affected sites' cache backend, installed extensions, and profiler configuration are unavailable.
  • Git history establishes that the unsafe access predates 4.2.11 but does not establish a previously working release boundary.

Confidence

Confidence: 94/100

Production telemetry records 10 frontend crashes on two sites in Optimole 4.2.11, and the reported source line directly indexes a stored profile value as an array without validating its documented array|false storage contract. The relevant call path is reachable during frontend viewport lazy-loading.

Crash telemetry

Occurrences 10
Distinct sites 2
First seen 2026-08-27 10:11 UTC
Last seen 2026-08-28 17:29 UTC
Crash location product:inc/v2/PageProfiler/Profile.php:369
Request context frontend
Inside Themeisle SDK no
Product versions 4.2.11
WP versions 7.1
PHP versions 8.3.31
SDK versions 3.3.58

Source: automated crash report — optimole-wp, fingerprint 3025c25bb349ed9cf9cdbed87b66cec3
Generated by bug-report-triage (ID: bug-report-triage_6a9275be3e9dc7.49048068)

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions