Skip to content

fix(backend): interpreter fallback on runtime VerifyError#542

Closed
fglock wants to merge 2 commits intomasterfrom
fix/dbi-verifier-fallback
Closed

fix(backend): interpreter fallback on runtime VerifyError#542
fglock wants to merge 2 commits intomasterfrom
fix/dbi-verifier-fallback

Conversation

@fglock
Copy link
Copy Markdown
Owner

@fglock fglock commented Apr 22, 2026

Summary

The existing compile-time interpreter fallback in PerlLanguageProvider.compileToExecutable catches VerifyError / ClassFormatError thrown while the JVM class is being defined, instantiated, and looked up. HotSpot, however, defers per-method bytecode verification to the first invocation, so a compiled class that the generator produced with inconsistent stack map frames sails past compileToExecutable and only crashes later, when executeCode invokes runtimeCode.apply().

The DBI test suite hits this routinely. t/01basics.t's main body has 200+ top-level statements and blows up with

Bad local variable type
  Reason: Type top (current frame, locals[203]) is not assignable to reference type
  Location: org/perlonjava/anon1762.apply(...) @25039: aload

even though class loading appeared to succeed (test 1 from the BEGIN block prints OK; the JVM only chokes when the main body's apply() is invoked for the first time).

This PR adds a second try/catch at the apply() call site in executeCode. On a recoverable error (same needsInterpreterFallback predicate used by the compile-time path), we recompile the AST via BytecodeCompiler and retry apply() on the resulting InterpretedCode. BEGIN / CHECK / INIT have already run by this point and the main body has not, so re-executing apply() on the interpreted form is safe.

JPERL_SHOW_FALLBACK=1 now also prints Note: Using interpreter fallback (verify error at first call). when this new path fires.

Effect on jcpan -t DBI

Files Subtests Passing Failing
before 200 638 368 270
after 200 946 676 270

+308 additional subtests now execute successfully. The same 270 still fail — those are DBI-level issues (missing install_driver / _new_drh, DBD::File, gofer, ...) that were previously hidden behind the verifier crash. They are tracked in dev/modules/dbi_test_parity.md as Phase 2/3.

Test plan

  • make (full unit tests) passes.
  • ./jperl ~/.cpan/build/DBI-1.647-5/t/01basics.t now runs 78+ tests before hitting an unrelated DBI->internal missing-method issue (previously: exploded after test 1).
  • JPERL_SHOW_FALLBACK=1 ./jperl ... prints the new notice exactly once.
  • Baseline jcpan -t DBI numbers match the table above (captured on this branch).

Related

Generated with Devin

fglock and others added 2 commits April 22, 2026 15:49
The bundled DBI.pm declared SQL_* constants but never set up
@EXPORT_OK / %EXPORT_TAGS, so `use DBI qw(:sql_types ...)` (which
essentially every DBI test uses) pulled nothing into the caller's
namespace and failed at compile time with "Bareword SQL_GUID not
allowed while strict subs in use".

This change:
  - makes DBI inherit from Exporter and registers the four standard
    export tags (sql_types, sql_cursor_types, utils, profile);
  - adds the constants that were missing (SQL_INTERVAL_*,
    SQL_ARRAY_LOCATOR, SQL_MULTISET_LOCATOR, SQL_CURSOR_*,
    DBIstcf_STRICT/DISCARD_STRING);
  - ports the small utility functions from DBI / DBI::PurePerl
    (neat, neat_list, looks_like_number, data_string_diff,
    data_string_desc, data_diff, dump_results, sql_type_cast,
    dbi_time) into a sibling DBI::_Utils module, required by
    DBI.pm. The utils live in a separate .pm so PerlOnJava
    compiles them to their own JVM class; combining everything
    into a single DBI.pm tripped a per-method bytecode limit
    in our backend.

Effect on `jcpan -t DBI`:
  before: 200 files, 562 subtests, 308 passing, 254 failing
  after:  200 files, 638 subtests, 368 passing, 270 failing

The remaining failures are unrelated issues (missing
DBI::install_driver / DBI::_new_drh, DBD::File / DBD::DBM /
gofer / DBI::PurePerl not implemented, plus a separate bytecode
verifier bug on very large flat test scripts).

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The existing compile-time interpreter fallback in
PerlLanguageProvider.compileToExecutable only catches VerifyError /
ClassFormatError thrown while the JVM class is being defined,
instantiated, and looked up. HotSpot, however, defers per-method
bytecode verification to the first invocation of each method, so a
compiled class that the generator produced with inconsistent stack
map frames sails past compileToExecutable and only crashes later
when executeCode invokes runtimeCode.apply(). The DBI test suite
hits this routinely: t/01basics.t's main body has 200+ top-level
statements and blows up with
"Type top (locals[203]) is not assignable to reference type" at
first invocation, even though class loading appeared to succeed.

This change adds a second try/catch at the apply() call site in
executeCode. On a recoverable error (same needsInterpreterFallback
predicate used by the compile-time path), we recompile the AST via
BytecodeCompiler and retry apply() on the resulting InterpretedCode.
BEGIN / CHECK / INIT blocks have already run by this point and the
main body has not, so re-executing apply() on the interpreted form
is safe.

JPERL_SHOW_FALLBACK=1 now also prints
"Note: Using interpreter fallback (verify error at first call)."
when this new path fires.

Effect on the bundled DBI test suite (`jcpan -t DBI`):
  before: 638 subtests, 368 passing, 270 failing
  after:  946 subtests, 676 passing, 270 failing
  => 308 additional subtests now execute successfully. The same 270
     still fail; those are DBI-level issues (missing install_driver /
     _new_drh, DBD::File, gofer, ...) that were previously hidden
     behind the verifier crash.

See dev/modules/dbi_test_parity.md for the rest of the plan.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@fglock
Copy link
Copy Markdown
Owner Author

fglock commented Apr 22, 2026

Superseded by unified PR (see new PR). All four commits rebased into a single branch targeting master.

@fglock fglock closed this Apr 22, 2026
@fglock fglock deleted the fix/dbi-verifier-fallback branch April 22, 2026 16:03
fglock added a commit that referenced this pull request Apr 23, 2026
Previously `use DBI; DBI->install_driver("NullP")` died with
"Undefined subroutine &DBI::install_driver". The bundled DBI.pm
talked to the Java DBI backend only, bypassing the DBI driver
architecture (DBD::<name>::driver factories, DBI::_new_drh/dbh/sth,
DBD::_::common / dr / db / st base classes). That path covers JDBC
drivers (SQLite, H2, ...) but not the pure-Perl DBDs bundled with
upstream DBI — DBD::NullP, DBD::ExampleP, DBD::Sponge, DBD::Mem,
DBD::File, DBD::DBM — which the DBI self-tests rely on extensively.

This change adds the minimum driver-architecture pieces needed by
those DBDs, in a new file src/main/perl/lib/DBI/_Handles.pm:

  * DBI->install_driver / installed_drivers / data_sources /
    available_drivers / setup_driver;
  * DBI::_new_drh / _new_dbh / _new_sth (handle factories,
    returning plain blessed hashrefs — no tie magic);
  * DBI::_get_imp_data (stub);
  * DBD::_::common / dr / db / st base classes with FETCH, STORE,
    err, errstr, state, set_err, trace, trace_msg,
    parse_trace_flag(s), func, dump_handle, visit_child_handles,
    default connect / connect_cached, quote, quote_identifier,
    data_sources, disconnect, commit, rollback, ping, finish,
    fetchrow_array, fetchrow_hashref, rows, bind_col(s),
    bind_param(_array), execute_array, _set_fbav;
  * Stub DBI::dr / DBI::db / DBI::st packages so `isa('DBI::dr')`
    succeeds; DBD::_::<suffix> inherits from DBI::<suffix>.

DBI.pm's connect wrapper now detects a pure-Perl DBD (has `driver()`
but no `_dsn_to_jdbc`) and routes through
`install_driver($name)->connect(...)` instead of the JDBC backend.

Lives in a separate .pm for the same per-method bytecode-size
reason as DBI/_Utils.pm from PR #540.

Effect on `jcpan -t DBI` (stacked on PR #542):
  before: 200 files, 946 subtests, 676 passing, 270 failing
  after:  200 files, 1600 subtests, 1240 passing, 360 failing
  => +564 subtests now pass (+654 newly executed). 10 fewer test
     files fail overall; the remaining failures are real DBI-level
     issues (DBI::PurePerl, DBD::File/DBM/Gofer, a handful of
     handle-tracking edge cases) tracked as Phase 3 in the plan.

See dev/modules/dbi_test_parity.md.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
fglock added a commit that referenced this pull request Apr 23, 2026
Phase 3 first batch of the DBI test-parity plan: add the methods
the DBI self-tests and the bundled pure-Perl DBDs
(DBD::File / DBD::DBM / DBD::Sponge / DBD::Mem / DBI::DBD::SqlEngine)
call on DBI and on handles.

Top-level DBI methods / helpers:
  * DBI->internal      (fake DBD::Switch::dr drh, isa('DBI::dr'))
  * DBI->parse_dsn
  * DBI->driver_prefix (accepts both 'File' and 'DBD::File')
  * DBI->dbixs_revision
  * DBI->install_method / DBI->_install_method
  * DBI::hash          (ported from DBI::PurePerl)
  * DBI::_concat_hash_sorted
  * DBI::dbi_profile / dbi_profile_merge / dbi_profile_merge_nodes
  * DBI->data_sources now accepts "dbi:DRIVER:" form.

Trace fix in DBI.pm:
  * DBI->trace / DBI->trace_msg now work as class methods
    (previously crashed on strict refs when the invocant was "DBI").

DBD::_::db base class:
  * do, prepare_cached
  * selectrow_array / _arrayref / _hashref
  * selectall_arrayref / _hashref
  * selectcol_arrayref
  * type_info stub

DBD::_::st base class:
  * fetchall_arrayref (plain / slice / hash)
  * fetchall_hashref
  * _get_fbav
  * FETCH override computing NAME_lc / NAME_uc / NAME_hash /
    NAME_lc_hash / NAME_uc_hash from NAME when called via
    $sth->FETCH(...). Direct $sth->{NAME_lc} access still requires
    tied-hash semantics, which we do not provide.

DBD::_::common base class:
  * FETCH_many, debug, dbixs_revision, install_method, dump_handle.

Effect on `jcpan -t DBI` (stacked on #544):
  before: 200 files, 1600 subtests, 1240 passing, 360 failing
  after:  200 files, 5610 subtests, 3978 passing, 1632 failing
  => +2738 subtests now pass (+4010 more executed). 4 fewer test
     files fail overall. The remaining 166 files are dominated by
     (a) DBD::File / DBD::DBM-specific methods not yet wired and
     (b) tied-hash-dependent attribute access.

Cumulative across the four stacked PRs (#540, #542, #544, this one):
  master:  562 subtests, 308 passing
  now:     5610 subtests, 3978 passing  (~13× more passes)

See dev/modules/dbi_test_parity.md.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant