Skip to content

Commit 0af043f

Browse files
mergify[bot]raynelfssCryoris
authored
Fix: Memory leakage in QkCountOps creation. (backport Qiskit#14930) (Qiskit#14959)
* Fix: Memory leakage in `QkCountOps` creation. (Qiskit#14930) * Fix: Memory leakage in `QkCountOps` creation. The following commits attempt to fix some memoery issues that the struct `QkOpCounts`. The `qk_opcounts_free` method would segfault if an empty object was passed. The function now checks whether the passed option includes a null pointer or an empty instance. The commits also change how the `data` attribute is generated without using `std::mem::forget`, but instead collection the `OpCount` vec into a boxed_slice and consuming it into a `Box`. Inspired by what was done for Qiskit#14884. * Docs: Add release note. * Fix: Ironic memory leakage * Fix c opcounts free suggestion (Qiskit#7) * Rename to _clear, free name * rm dangling file * use pointers * ... forgot to register test * fix `size_t` * Fix: Missed some renaming * add note on clearing opscount * be explicit about loading Box<[OpCount]> * fix merge artifact * Apply suggestions from code review Co-authored-by: Eli Arbel <[email protected]> * goto * happy miri happy life * Fix merge artifacts --------- Co-authored-by: Julien Gacon <[email protected]> Co-authored-by: Julien Gacon <[email protected]> Co-authored-by: Eli Arbel <[email protected]> (cherry picked from commit 28aa4bd) # Conflicts: # test/c/test_elide_permutations.c # test/c/test_optimize_1q_sequences.c # test/c/test_sabre_layout.c # test/c/test_split_2q_unitaries.c * rm merge artifacts --------- Co-authored-by: Raynel Sanchez <[email protected]> Co-authored-by: Julien Gacon <[email protected]>
1 parent 20ba77d commit 0af043f

File tree

3 files changed

+106
-29
lines changed

3 files changed

+106
-29
lines changed

crates/cext/src/circuit.rs

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -755,16 +755,22 @@ pub unsafe extern "C" fn qk_circuit_unitary(
755755
/// @ingroup QkCircuit
756756
/// Return a list of string names for instructions in a circuit and their counts.
757757
///
758+
/// To properly free the memory allocated by the struct, you should call ``qk_opcounts_clear``.
759+
/// Dropping the ``QkOpCounts`` struct without doing so will leave the stored array of ``QkOpCount``
760+
/// allocated and produce a memory leak.
761+
///
758762
/// @param circuit A pointer to the circuit to get the counts for.
759763
///
760-
/// @return An ``OpCounts`` struct containing the circuit operation counts.
764+
/// @return An ``QkOpCounts`` struct containing the circuit operation counts.
761765
///
762766
/// # Example
763767
/// ```c
764768
/// QkCircuit *qc = qk_circuit_new(100, 0);
765769
/// uint32_t qubits[1] = {0};
766770
/// qk_circuit_gate(qc, QkGate_H, qubits, NULL);
767771
/// QkOpCounts counts = qk_circuit_count_ops(qc);
772+
/// // .. once done
773+
/// qk_opcounts_clear(&counts);
768774
/// ```
769775
///
770776
/// # Safety
@@ -776,16 +782,18 @@ pub unsafe extern "C" fn qk_circuit_count_ops(circuit: *const CircuitData) -> Op
776782
// SAFETY: Per documentation, the pointer is non-null and aligned.
777783
let circuit = unsafe { const_ptr_as_ref(circuit) };
778784
let count_ops = circuit.count_ops();
779-
let mut output: Vec<OpCount> = count_ops
780-
.into_iter()
781-
.map(|(name, count)| OpCount {
782-
name: CString::new(name).unwrap().into_raw(),
783-
count,
784-
})
785-
.collect();
786-
let data = output.as_mut_ptr();
785+
let output = {
786+
let vec: Vec<OpCount> = count_ops
787+
.into_iter()
788+
.map(|(name, count)| OpCount {
789+
name: CString::new(name).unwrap().into_raw(),
790+
count,
791+
})
792+
.collect();
793+
vec.into_boxed_slice()
794+
};
787795
let len = output.len();
788-
std::mem::forget(output);
796+
let data = Box::into_raw(output) as *mut OpCount;
789797
OpCounts { data, len }
790798
}
791799

@@ -993,7 +1001,7 @@ pub unsafe extern "C" fn qk_circuit_instruction_clear(inst: *mut CInstruction) {
9931001
}
9941002

9951003
/// @ingroup QkCircuit
996-
/// Free a circuit op count list.
1004+
/// Clear the content in a circuit operation count list.
9971005
///
9981006
/// @param op_counts The returned op count list from ``qk_circuit_count_ops``.
9991007
///
@@ -1002,14 +1010,29 @@ pub unsafe extern "C" fn qk_circuit_instruction_clear(inst: *mut CInstruction) {
10021010
/// Behavior is undefined if ``op_counts`` is not the object returned by ``qk_circuit_count_ops``.
10031011
#[no_mangle]
10041012
#[cfg(feature = "cbinding")]
1005-
pub unsafe extern "C" fn qk_opcounts_free(op_counts: OpCounts) {
1006-
// SAFETY: Loading data contained in OpCounts as a slice which was constructed from a Vec
1007-
let data = unsafe { std::slice::from_raw_parts_mut(op_counts.data, op_counts.len) };
1008-
let data = data.as_mut_ptr();
1009-
// SAFETY: Loading a box from the slice pointer created above
1010-
unsafe {
1011-
let _ = Box::from_raw(data);
1013+
pub unsafe extern "C" fn qk_opcounts_clear(op_counts: *mut OpCounts) {
1014+
// SAFETY: The user guarantees the input is a valid OpCounts pointer.
1015+
let op_counts = unsafe { mut_ptr_as_ref(op_counts) };
1016+
1017+
if op_counts.len > 0 && !op_counts.data.is_null() {
1018+
// SAFETY: We load the box from a slice pointer created from
1019+
// the raw parts from the OpCounts::data attribute.
1020+
unsafe {
1021+
let slice: Box<[OpCount]> = Box::from_raw(std::slice::from_raw_parts_mut(
1022+
op_counts.data,
1023+
op_counts.len,
1024+
));
1025+
// free the allocated strings in each OpCount
1026+
for count in slice.iter() {
1027+
if !count.name.is_null() {
1028+
let _ = CString::from_raw(count.name as *mut c_char);
1029+
}
1030+
}
1031+
// the variable vec goes out of bounds and is freed too
1032+
}
10121033
}
1034+
op_counts.len = 0;
1035+
op_counts.data = std::ptr::null_mut();
10131036
}
10141037

10151038
/// @ingroup QkCircuit
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
fixes:
3+
- |
4+
Fixed memory leakage issues during the creation of
5+
a :cpp:struct:`QkOpCounts` instance and during any calls to
6+
:cpp:func:`qk_opcounts_clear` whenever an empty instance is passed.

test/c/test_circuit.c

Lines changed: 59 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,17 @@ int test_empty(void) {
2727
uint32_t num_qubits = qk_circuit_num_qubits(qc);
2828
uint32_t num_clbits = qk_circuit_num_clbits(qc);
2929
size_t num_instructions = qk_circuit_num_instructions(qc);
30+
31+
QkOpCounts counts = qk_circuit_count_ops(qc);
32+
size_t opcount = counts.len;
33+
34+
qk_opcounts_clear(&counts);
3035
qk_circuit_free(qc);
3136

37+
if (opcount != 0) {
38+
printf("The operation count %zu is not 0", opcount);
39+
return EqualityError;
40+
}
3241
if (num_qubits != 0) {
3342
printf("The number of qubits %d is not 0", num_qubits);
3443
return EqualityError;
@@ -254,6 +263,45 @@ int test_gate_num_params(void) {
254263
return Ok;
255264
}
256265

266+
/**
267+
* Test edge cases for getting the op counts.
268+
*/
269+
int test_get_gate_counts(void) {
270+
QkCircuit *qc = qk_circuit_new(3, 3);
271+
272+
// test empty circuit
273+
int result = Ok;
274+
QkOpCounts c1 = qk_circuit_count_ops(qc);
275+
if (c1.len != 0) {
276+
result = EqualityError;
277+
qk_opcounts_clear(&c1);
278+
goto circuit_cleanup;
279+
}
280+
qk_opcounts_clear(&c1);
281+
282+
// add some instructions
283+
uint32_t qubits[2] = {1, 0};
284+
double params[1] = {0.12};
285+
qk_circuit_gate(qc, QkGate_CRX, qubits, params);
286+
QkOpCounts c2 = qk_circuit_count_ops(qc);
287+
288+
if (c2.len != 1) {
289+
result = EqualityError;
290+
qk_opcounts_clear(&c1);
291+
goto circuit_cleanup;
292+
}
293+
qk_opcounts_clear(&c2);
294+
295+
// check that after clearing, the object is still valid
296+
if (c2.len != 0 || c2.data != NULL) {
297+
result = EqualityError;
298+
}
299+
300+
circuit_cleanup:
301+
qk_circuit_free(qc);
302+
return result;
303+
}
304+
257305
int test_get_gate_counts_bv_no_measure(void) {
258306
QkCircuit *qc = qk_circuit_new(1000, 1000);
259307
double *params = NULL;
@@ -304,7 +352,7 @@ int test_get_gate_counts_bv_no_measure(void) {
304352
}
305353
cleanup:
306354
qk_circuit_free(qc);
307-
qk_opcounts_free(op_counts);
355+
qk_opcounts_clear(&op_counts);
308356
return result;
309357
}
310358

@@ -369,7 +417,7 @@ int test_get_gate_counts_bv_measures(void) {
369417
}
370418
cleanup:
371419
qk_circuit_free(qc);
372-
qk_opcounts_free(op_counts);
420+
qk_opcounts_clear(&op_counts);
373421
return result;
374422
}
375423

@@ -443,13 +491,12 @@ int test_get_gate_counts_bv_barrier_and_measures(void) {
443491
goto cleanup;
444492
}
445493
if (op_counts.data[3].count != 2) {
446-
return EqualityError;
447494
result = EqualityError;
448495
goto cleanup;
449496
}
450497
cleanup:
451498
qk_circuit_free(qc);
452-
qk_opcounts_free(op_counts);
499+
qk_opcounts_clear(&op_counts);
453500
return result;
454501
}
455502

@@ -671,7 +718,7 @@ int test_get_gate_counts_bv_resets_barrier_and_measures(void) {
671718
}
672719
cleanup:
673720
qk_circuit_free(qc);
674-
qk_opcounts_free(op_counts);
721+
qk_opcounts_clear(&op_counts);
675722
return result;
676723
}
677724

@@ -707,10 +754,10 @@ int test_unitary_gate(void) {
707754
if (op_counts.len != 1 || strcmp(op_counts.data[0].name, "unitary") != 0 ||
708755
op_counts.data[0].count != 1) {
709756
result = EqualityError;
710-
qk_opcounts_free(op_counts);
757+
qk_opcounts_clear(&op_counts);
711758
goto cleanup;
712759
}
713-
qk_opcounts_free(op_counts);
760+
qk_opcounts_clear(&op_counts);
714761

715762
QkCircuitInstruction inst;
716763
qk_circuit_get_instruction(qc, 0, &inst);
@@ -755,10 +802,10 @@ int test_unitary_gate_1q(void) {
755802
if (op_counts.len != 1 || strcmp(op_counts.data[0].name, "unitary") != 0 ||
756803
op_counts.data[0].count != 1) {
757804
result = EqualityError;
758-
qk_opcounts_free(op_counts);
805+
qk_opcounts_clear(&op_counts);
759806
goto cleanup;
760807
}
761-
qk_opcounts_free(op_counts);
808+
qk_opcounts_clear(&op_counts);
762809

763810
QkCircuitInstruction inst;
764811
qk_circuit_get_instruction(qc, 0, &inst);
@@ -809,10 +856,10 @@ int test_unitary_gate_3q(void) {
809856
if (op_counts.len != 1 || strcmp(op_counts.data[0].name, "unitary") != 0 ||
810857
op_counts.data[0].count != 1) {
811858
result = EqualityError;
812-
qk_opcounts_free(op_counts);
859+
qk_opcounts_clear(&op_counts);
813860
goto cleanup;
814861
}
815-
qk_opcounts_free(op_counts);
862+
qk_opcounts_clear(&op_counts);
816863
QkCircuitInstruction inst;
817864
qk_circuit_get_instruction(qc, 0, &inst);
818865
if (strcmp(inst.name, "unitary") != 0 || inst.num_clbits != 0 || inst.num_params != 0 ||
@@ -886,6 +933,7 @@ int test_circuit(void) {
886933
num_failed += RUN_TEST(test_circuit_copy);
887934
num_failed += RUN_TEST(test_circuit_copy_with_instructions);
888935
num_failed += RUN_TEST(test_no_gate_1000_bits);
936+
num_failed += RUN_TEST(test_get_gate_counts);
889937
num_failed += RUN_TEST(test_get_gate_counts_bv_no_measure);
890938
num_failed += RUN_TEST(test_get_gate_counts_bv_measures);
891939
num_failed += RUN_TEST(test_get_gate_counts_bv_barrier_and_measures);

0 commit comments

Comments
 (0)