Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
7 changes: 7 additions & 0 deletions jitpack.yml

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@wildan-m, I don't think this should be needed, shouldn't this already be published to our local Maven repository?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@eVoloshchak nothing's pre-published — com.github.Expensify:… is a JitPack coordinate, so JitPack builds this repo from source on demand, and without jitpack.yml it fails (its default JDK 11 can't run the Hilt plugin, which needs 17 — tag .1 errored with No build artifacts found).

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
jdk:
- openjdk17
before_install:
- yes | "${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager" --licenses > /dev/null 2>&1 || true
- yes | sdkmanager --licenses > /dev/null 2>&1 || true
install:
- ./gradlew :pdfiumandroid:publishToMavenLocal -x test --no-daemon
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package io.legere.pdfiumandroid

import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import io.legere.pdfiumandroid.base.BasePDFTest
import io.legere.pdfiumandroid.util.AlreadyClosedBehavior
import io.legere.pdfiumandroid.util.Config
import io.legere.pdfiumandroid.util.pdfiumConfig
import org.junit.After
import org.junit.Test
import org.junit.runner.RunWith

/**
* E/App #93839 (Sentry APP-1VG) — Android "Already closed" crash on receipt PDF render.
*
* The production crash is a non-deterministic renderer-thread-vs-teardown race: a render
* lands on PdfDocument.openPage(0) after the document was already close()d. The app sets
* AlreadyClosedBehavior.IGNORE (react-native-pdf patch 002) so such a late call should be
* swallowed — but stock io.legere:pdfiumandroid 1.0.35 has openPage()/openTextPage() throw
* unconditionally, ignoring IGNORE, so the IllegalStateException crashes the renderer thread.
*
* This forces that exact frame deterministically: IGNORE -> newDocument -> close() -> openPage(0)
*
* Baseline (openPage does NOT honor IGNORE): FAILS — IllegalStateException("Already closed")
* at PdfDocument.openPage(PdfDocument.kt) == APP-1VG
* Fork (openPage honors IGNORE): PASSES — openPage returns instead of throwing
*/
@RunWith(AndroidJUnit4::class)
class AlreadyClosedOpenPageTest : BasePDFTest() {

@After
fun tearDown() {
// Don't leak the IGNORE config into other test classes.
pdfiumConfig = Config()
}

@Test
fun openPageAfterClose_withIgnore_doesNotCrash() {
val pdfBytes = getPdfBytes("f01.pdf")
assertThat(pdfBytes).isNotNull()

// Configure exactly as the app does (react-native-pdf patch 002): IGNORE via the
// PdfiumCore constructor. (PdfiumCore.init overwrites the global pdfiumConfig, so
// setting the global directly would be wiped by construction.)
val core = PdfiumCore(config = Config(alreadyClosedBehavior = AlreadyClosedBehavior.IGNORE))
val doc = core.newDocument(pdfBytes)
doc.close()

// The exact crashing call from the APP-1VG stack. With IGNORE set this must NOT throw.
// On stock 1.0.35 it throws IllegalStateException("Already closed") here.
val page = doc.openPage(0)
assertThat(page).isNotNull()
}
}
25 changes: 23 additions & 2 deletions pdfiumandroid/src/main/java/io/legere/pdfiumandroid/PdfDocument.kt
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,18 @@ class PdfDocument(
*/
fun openPage(pageIndex: Int): PdfPage {
synchronized(PdfiumCore.lock) {
check(!isClosed) { "Already closed" }
// Honor the configured AlreadyClosedBehavior instead of always throwing.
// Every other PdfDocument method routes its closed-state check through
// handleAlreadyClosed(); openPage/openTextPage were the only ones that did
// not, so AlreadyClosedBehavior.IGNORE never reached the exact call that
// crashes when a render lands on a just-closed document (see openPage in
// PdfiumCore.renderPageBitmap). When IGNORE is configured we return a page
// bound to this (closed) document; every PdfPage operation already guards on
// doc.isClosed via handleAlreadyClosed(), so the render is silently dropped
// with no native call instead of throwing on the renderer thread.
if (handleAlreadyClosed(isClosed)) {
return PdfPage(this, pageIndex, INVALID_PAGE_PTR, pageMap)
}
if (pageMap.containsKey(pageIndex)) {
pageMap[pageIndex]?.let {
it.count++
Expand Down Expand Up @@ -376,7 +387,11 @@ class PdfDocument(
@Deprecated("Use PdfPage.openTextPage instead", ReplaceWith("page.openTextPage()"))
fun openTextPage(page: PdfPage): PdfTextPage {
synchronized(PdfiumCore.lock) {
check(!isClosed) { "Already closed" }
// See openPage(): honor AlreadyClosedBehavior.IGNORE instead of throwing so a
// text-page open that races with document teardown is dropped, not fatal.
if (handleAlreadyClosed(isClosed)) {
return PdfTextPage(this, page.pageIndex, INVALID_PAGE_PTR, textPageMap)
}
if (textPageMap.containsKey(page.pageIndex)) {
textPageMap[page.pageIndex]?.let {
it.count++
Expand Down Expand Up @@ -483,6 +498,12 @@ class PdfDocument(
companion object {
private val TAG = PdfDocument::class.java.name

// Sentinel native pointer used for the no-op PdfPage/PdfTextPage returned when a
// page is opened on an already-closed document under AlreadyClosedBehavior.IGNORE.
// It is never dereferenced: every PdfPage/PdfTextPage operation short-circuits via
// handleAlreadyClosed(doc.isClosed) before touching the native pointer.
private const val INVALID_PAGE_PTR = -1L

const val FPDF_INCREMENTAL = 1
const val FPDF_NO_INCREMENTAL = 2
const val FPDF_REMOVE_SECURITY = 3
Expand Down