Fix CFPropertyListCreateDeepCopy for mutable-copy options#27
Open
DTW-Thalion wants to merge 1 commit into
Open
Fix CFPropertyListCreateDeepCopy for mutable-copy options#27DTW-Thalion wants to merge 1 commit into
DTW-Thalion wants to merge 1 commit into
Conversation
* Source/CFPropertyList.c: Two related bugs prevented the function
from producing correct mutable deep copies.
(1) The function dispatches on file-scope _kCF*TypeID variables
that are lazily populated by CFPropertyListInitTypeIDs. The only
other caller that initialised those variables was
CFPlistTypeIsValid, so CFPropertyListCreateDeepCopy would drop
into its trailing `else` branch and return NULL whenever it was
the first plist-API call in the process. Add an explicit
CFPropertyListInitTypeIDs() call at the top of the function.
(2) The mutable-array and mutable-dictionary branches called
CFArrayApplyFunction / CFDictionaryApplyFunction on the newly
created empty destination container rather than on the source
plist. The apply function therefore visited zero elements and
every mutable deep copy came back empty. On Linux the iteration
also read uninitialised slots in the destination container and
crashed via CFRetain(NULL) inside CFArrayCopyFunction. Iterate
on `plist` in both branches so the copy actually walks the
source values.
* Tests/CFPropertyList/deepcopy.m: New regression test exercising
every branch of the mutable-copy path:
- mutable array deep copy has the same count as the source;
- mutable array deep copy preserves element identity;
- the result is genuinely mutable (append grows the count);
- immutable array deep copy still works (guards the non-buggy
branch against a fix that accidentally regresses it);
- mutable dictionary deep copy has the same count as the
source (the dictionary branch had the same bug as the
array branch and had been missed by a previous partial fix);
- mutable dictionary deep copy preserves values by key;
- the result is genuinely mutable;
- a nested mutable deep copy preserves both outer and inner
contents, since the recursive descent reuses the same
mutable-copy path.
Plain C / public CoreFoundation API only, using the PASS_CF
macro from Tests/CFTesting.h - no Objective-C runtime
assumptions, no blocks.
The test was validated both ways against a clang build on Ubuntu
24.04: with both fixes in place, all 17 assertions pass; with the
apply-function swap reverted, the test process crashes in
CFRetain(NULL) on the first mutable array deep copy, which
gnustep-tests correctly reports as "Failed file: aborted without
running all tests". The discrimination between fixed and unfixed
builds is therefore strict, not incidental.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
CFPropertyListCreateDeepCopyis broken for mutable-copy options (kCFPropertyListMutableContainers,kCFPropertyListMutableContainersAndLeaves) in two independent ways that compound to make the function unusable for the mutable-copy path in most real processes.Bug 1 — uninitialised type IDs
The function dispatches on file-scope
_kCF*TypeIDvariables that are lazily populated byCFPropertyListInitTypeIDs. The only other caller that initialises those variables isCFPlistTypeIsValid.CFPropertyListCreateDeepCopynever calls the init routine itself, so whenever it is the first plist-API call in the process, everytypeID == _kCF*TypeIDcomparison fails (because every such variable is still zero), the function drops into its trailingelsebranch, and returnsNULL.This is a latent bug that only surfaced when a regression test linked against libgnustep-corebase alone. Tests that also linked libgnustep-base had their type IDs initialised as a side effect of Foundation class initialisation, which is why it had not been noticed.
Bug 2 — apply function iterates the destination, not the source
In the mutable-array branch:
The apply function is invoked on the freshly created empty destination container rather than on the source
plist. Since the destination has zero live elements, the apply function visits nothing, and the returned mutable array comes back empty.On Linux the same code path reads uninitialised slots in the destination container (
CFArrayCreateMutableallocates capacity but not elements) and hands zero-valued pointers toCFArrayCopyFunction, which callsCFArrayAppendValue→CFArrayReplaceValues→CFRetain(NULL)→ segfault. The observable failure mode therefore differs between Linux and macOS/Windows.The identical bug exists in the mutable-dictionary branch at line 346 —
CFDictionaryApplyFunction (dict, CFDictionaryCopyFunction, &ctx)should iterateplist. A previous partial fix only addressed the array case.Fix
Source/CFPropertyList.c:CFPropertyListInitTypeIDs()at the top ofCFPropertyListCreateDeepCopyso the function is self-sufficient and doesn't silently returnNULLwhen invoked as the first plist call.plist, not the destinationarray. Commented inline with the rationale.plist, not the destinationdict. Commented inline.Regression test
Tests/CFPropertyList/deepcopy.m— plain-C harness usingPASS_CFfromTests/CFTesting.h, matching the style of the existingvalidate.mtest in the same directory. Public CoreFoundation C API only — no Objective-C runtime assumptions, no blocks.17 assertions across:
PASS_CFEQagainst the originals, and appending to the copy grows the count (proves the returned object is genuinely mutable).PASS_CFEQ, and adding to the copy grows the count.CFPropertyListCreateDeepCopyrecurses through itself.Validation
Built and run against a clang 18.1.3 build on Ubuntu 24.04:
CFRetain(NULL)insideCFArrayCopyFunctionon the first mutable array deep copy; gnustep-tests reportsFailed file: deepcopy.m aborted without running all testsThe discrimination between the fixed and unfixed build is strict — the unfixed build doesn't fail some assertions, it dies outright on the first mutable-copy call, which is the worst-case manifestation of the bug and the one that would surface in any real application.
ChangeLog
Entry added in
ChangeLogmatching the repository's GNU-style format.