Skip to content

Free a stack's elements with the stack - #112

Merged
quinnj-bot[bot] merged 1 commit into
mainfrom
claude/stack-owns-elements
Oct 29, 2026
Merged

quinnj-bot[bot] merged 1 commit into
mainfrom
claude/stack-owns-elements

Conversation

@quinnj-bot

@quinnj-bot quinnj-bot Bot commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

A StackOf{T} holds a reference to each element:

  • push! takes one: X509_up_ref for certificates, a copy (X509_EXTENSION_dup, X509_ATTRIBUTE_dup, BN_dup) for the rest.
  • The stacks OpenSSL returns hand over their elements: req.extensions (X509_REQ_get_extensions) and the CA stack from unpack (PKCS12_parse).
  • pop! already passes the stack's reference on to the wrapper it returns.

But free (also the finalizer) called only OPENSSL_sk_free, which frees the stack and none of its elements. Every element still in a stack when it went leaked: the pushed certificate (its count never reached zero), and the copied extension. Each req.extensions call leaked the request's extensions.

Fix

  • free pops and frees the remaining elements before freeing the stack.
  • pop! on an empty stack throws ArgumentError. Before, it wrapped the NULL that OPENSSL_sk_pop returns, and the first use of that wrapper crashed.

Verification

  • New testset StackOwnsElements:

    • It counts certificate frees with an ex_data free callback (CRYPTO_get_ex_new_index on CRYPTO_EX_INDEX_X509), with GC disabled around the count.
    • It pushes three certificates and frees each wrapper, then frees the stack: three frees.
    • On main, none (4 == 7 fails), and the pop! check fails.
  • Memory growth (maxrss) over N rounds, main → this branch:

    Round N main this branch
    push a new certificate, free stack and wrapper 40,000 227.9 MiB 2.5 MiB
    push an extension, free both 100,000 6.6 MiB 0.7 MiB
    req.extensions (left to the finalizer) 100,000 8.1 MiB 1.2 MiB
  • Full suite on Julia 1.12 -t 4: passes. The existing StackOf tests push, pop, finalize and free in every order.

Self-review

  • Every StackOf the package makes owns its elements: StackOf{T}() with push!, get_extensions, and unpack (OpenSSL's documentation: the caller frees *ca with OSSL_STACK_OF_X509_free). No code in the package wraps a borrowed stack such as SSL_get_peer_cert_chain.
  • A NULL stack (unpack of a PKCS#12 with no CA certificates leaves *ca NULL): OPENSSL_sk_num(NULL) is -1, so nothing is popped.
  • The finalizer now allocates one wrapper per element and frees it at once. Allocation and finalizer registration are allowed in a finalizer, and nothing there waits or takes a lock.
  • pop! on an empty stack used to return a wrapper around NULL; it now throws, as pop! on an empty Vector does.

🤖 Generated with Claude Code

`push!` gives a StackOf its own reference to the element (X509_up_ref, or
a copy for extensions, attributes and big numbers), and the stacks OpenSSL
returns (`req.extensions`, the CA stack from `unpack`) hand over theirs,
but freeing a StackOf, explicitly or by its finalizer, only freed the
stack: every element leaked. Pushing a certificate and freeing the stack
leaked the certificate (228 MiB over 40,000 rounds; 2.5 MiB now), and each
`req.extensions` leaked its extensions.

`free` now pops and frees the elements first. `pop!` on an empty stack
throws, rather than wrapping the NULL OpenSSL returns.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov

codecov Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.12%. Comparing base (14a9842) to head (867771d).
⚠️ Report is 4 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #112      +/-   ##
==========================================
+ Coverage   83.07%   83.12%   +0.05%     
==========================================
  Files           2        2              
  Lines        1341     1345       +4     
==========================================
+ Hits         1114     1118       +4     
  Misses        227      227              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@quinnj-bot
quinnj-bot Bot merged commit 1c3dc67 into main Oct 29, 2026
13 of 14 checks passed
@quinnj-bot
quinnj-bot Bot deleted the claude/stack-owns-elements branch October 29, 2026 23:56
@quinnj-bot quinnj-bot Bot mentioned this pull request Oct 30, 2026
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