Skip to content

Fix SAFEARRAY ownership for VT_ARRAY | VT_RECORD values. - #984

Merged
junkmd merged 8 commits into
record_safearray_characterization_testsfrom
safearray_record_lifetime
Nov 5, 2026
Merged

junkmd merged 8 commits into
record_safearray_characterization_testsfrom
safearray_record_lifetime

Conversation

@junkmd

@junkmd junkmd commented Nov 5, 2026 •

Copy link
Copy Markdown
Collaborator

Related to #63

Summary

This change makes record arrays safer to unpack, resolves the correct record type, and manages the lifetime of the SAFEARRAY used by the unpacked records.

Changes

The previous implementation (#104) could unpack record arrays with a shallow memory copy. This is not safe (at least for Python 3.14 and later,) for records containing BSTR values or other managed resources.

This change gives the unpacked records their own copy of the SAFEARRAY and keeps that copy alive for as long as the records need it.
On Python 3.14 and later, the related operation no longer crashes. This may be because garbage collection behavior has changed, even in builds without free-threading, and the release order is now different from earlier Python versions.

The updated test checks that the operation completes successfully, which confirms this behavior.

It is an instance attribute, not a class attribute. Each `SAFEARRAY` pointer
instance has its own `_needsfree` flag to track memory ownership.
Add helper `_is_recordinfo_guids` and type alias `_RIGuids` to check if a type
is a `Structure` with valid `_recordinfo_` class variable.
Problem:
- `self.vt & VT_RECORD` is wrong because `VT_RECORD` (36) is a value, not a flag.
- Bitwise AND with `VT_RECORD` can be true for unrelated vt like `VT_INT` (22).

Solution:
- Change the check to `(self.vt & ~VT_ARRAY) == VT_RECORD`.
- This correctly checks if the base type (without `VT_ARRAY` flag) is `VT_RECORD`.
- Remove redundant code and simplify the logic.
…for `Structure` without `_recordinfo_`.

After adding the `is_struct_type_with_recordinfo` early-return in
`_get_elements_raw`, the else branch reached by a `Structure` subtype without
`_recordinfo_` could no longer be handled correctly by the remaining
`memmove`-based `keep_safearray`.

A `Structure` `SAFEARRAY` that lacks `IRecordInfo` is not constructable through
normal COM codegen: the COM spec requires `IRecordInfo` for any `VT_RECORD`
array.  Silently returning data from a shallow byte-copy risks double-free
crashes for fields with `BSTR` or pointer semantics.

Raising `NotImplementedError` makes the unsupported state explicit instead of
returning potentially corrupt data, and signals to future maintainers that
a safe extraction path needs to be designed if this case ever becomes real.
In `_get_elements_raw`, use `self._vartype_ == VT_RECORD` instead of
`is_struct_type_with_recordinfo(self._itemtype_)`.

The `_vartype_` is set to `VT_RECORD` when the item type is a `Structure`
with valid `_recordinfo_`. This check is done in `_make_safearray_type`.

Benefits:
- No function call needed
- No import from typeinfo needed
- Simple integer comparison is faster
- Code is shorter and clearer

Also remove unused `is_struct_type_with_recordinfo` and `_is_recordinfo_guids`
from `typeinfo.py` because they are not used anywhere now.
@junkmd
junkmd added this pull request to stack #983 November 5, 2026 22:57
@junkmd junkmd added enhancement New feature or request tests enhance or fix tests labels Nov 5, 2026
@codecov-commenter

codecov-commenter commented Nov 5, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.77419% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 90.73%. Comparing base (f3b4180) to head (cb361e1).

Files with missing lines Patch % Lines
comtypes/safearray.py 94.11% 1 Missing ⚠️
Additional details and impacted files
@@                           Coverage Diff                            @@
##           record_safearray_characterization_tests     #984   +/-   ##
========================================================================
  Coverage                                    90.72%   90.73%           
========================================================================
  Files                                          147      147           
  Lines                                        15344    15353    +9     
========================================================================
+ Hits                                         13921    13930    +9     
  Misses                                        1423     1423           

☔ 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.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@junkmd
junkmd merged commit 6e74343 into main Nov 5, 2026
222 of 227 checks passed
@junkmd
junkmd deleted the safearray_record_lifetime branch November 5, 2026 23:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request tests enhance or fix tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants