Skip to content

Commit 28aa4bd

Browse files
raynelfssCryoriseliarbel
authored
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 (#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]>
1 parent e868803 commit 28aa4bd

File tree

7 files changed

+114
-37
lines changed

7 files changed

+114
-37
lines changed

crates/cext/src/circuit.rs

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -761,16 +761,22 @@ pub unsafe extern "C" fn qk_circuit_unitary(
761761
/// @ingroup QkCircuit
762762
/// Return a list of string names for instructions in a circuit and their counts.
763763
///
764+
/// To properly free the memory allocated by the struct, you should call ``qk_opcounts_clear``.
765+
/// Dropping the ``QkOpCounts`` struct without doing so will leave the stored array of ``QkOpCount``
766+
/// allocated and produce a memory leak.
767+
///
764768
/// @param circuit A pointer to the circuit to get the counts for.
765769
///
766-
/// @return An ``OpCounts`` struct containing the circuit operation counts.
770+
/// @return An ``QkOpCounts`` struct containing the circuit operation counts.
767771
///
768772
/// # Example
769773
/// ```c
770774
/// QkCircuit *qc = qk_circuit_new(100, 0);
771775
/// uint32_t qubits[1] = {0};
772776
/// qk_circuit_gate(qc, QkGate_H, qubits, NULL);
773777
/// QkOpCounts counts = qk_circuit_count_ops(qc);
778+
/// // .. once done
779+
/// qk_opcounts_clear(&counts);
774780
/// ```
775781
///
776782
/// # Safety
@@ -782,16 +788,18 @@ pub unsafe extern "C" fn qk_circuit_count_ops(circuit: *const CircuitData) -> Op
782788
// SAFETY: Per documentation, the pointer is non-null and aligned.
783789
let circuit = unsafe { const_ptr_as_ref(circuit) };
784790
let count_ops = circuit.count_ops();
785-
let mut output: Vec<OpCount> = count_ops
786-
.into_iter()
787-
.map(|(name, count)| OpCount {
788-
name: CString::new(name).unwrap().into_raw(),
789-
count,
790-
})
791-
.collect();
792-
let data = output.as_mut_ptr();
791+
let output = {
792+
let vec: Vec<OpCount> = count_ops
793+
.into_iter()
794+
.map(|(name, count)| OpCount {
795+
name: CString::new(name).unwrap().into_raw(),
796+
count,
797+
})
798+
.collect();
799+
vec.into_boxed_slice()
800+
};
793801
let len = output.len();
794-
std::mem::forget(output);
802+
let data = Box::into_raw(output) as *mut OpCount;
795803
OpCounts { data, len }
796804
}
797805

@@ -999,7 +1007,7 @@ pub unsafe extern "C" fn qk_circuit_instruction_clear(inst: *mut CInstruction) {
9991007
}
10001008

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

10211044
/// @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;
@@ -306,7 +354,7 @@ int test_get_gate_counts_bv_no_measure(void) {
306354
}
307355
cleanup:
308356
qk_circuit_free(qc);
309-
qk_opcounts_free(op_counts);
357+
qk_opcounts_clear(&op_counts);
310358
return result;
311359
}
312360

@@ -373,7 +421,7 @@ int test_get_gate_counts_bv_measures(void) {
373421
}
374422
cleanup:
375423
qk_circuit_free(qc);
376-
qk_opcounts_free(op_counts);
424+
qk_opcounts_clear(&op_counts);
377425
return result;
378426
}
379427

@@ -449,13 +497,12 @@ int test_get_gate_counts_bv_barrier_and_measures(void) {
449497
goto cleanup;
450498
}
451499
if (op_counts.data[3].count != 2) {
452-
return EqualityError;
453500
result = EqualityError;
454501
goto cleanup;
455502
}
456503
cleanup:
457504
qk_circuit_free(qc);
458-
qk_opcounts_free(op_counts);
505+
qk_opcounts_clear(&op_counts);
459506
return result;
460507
}
461508

@@ -678,7 +725,7 @@ int test_get_gate_counts_bv_resets_barrier_and_measures(void) {
678725
}
679726
cleanup:
680727
qk_circuit_free(qc);
681-
qk_opcounts_free(op_counts);
728+
qk_opcounts_clear(&op_counts);
682729
return result;
683730
}
684731

@@ -714,10 +761,10 @@ int test_unitary_gate(void) {
714761
if (op_counts.len != 1 || strcmp(op_counts.data[0].name, "unitary") != 0 ||
715762
op_counts.data[0].count != 1) {
716763
result = EqualityError;
717-
qk_opcounts_free(op_counts);
764+
qk_opcounts_clear(&op_counts);
718765
goto cleanup;
719766
}
720-
qk_opcounts_free(op_counts);
767+
qk_opcounts_clear(&op_counts);
721768

722769
QkCircuitInstruction inst;
723770
qk_circuit_get_instruction(qc, 0, &inst);
@@ -762,10 +809,10 @@ int test_unitary_gate_1q(void) {
762809
if (op_counts.len != 1 || strcmp(op_counts.data[0].name, "unitary") != 0 ||
763810
op_counts.data[0].count != 1) {
764811
result = EqualityError;
765-
qk_opcounts_free(op_counts);
812+
qk_opcounts_clear(&op_counts);
766813
goto cleanup;
767814
}
768-
qk_opcounts_free(op_counts);
815+
qk_opcounts_clear(&op_counts);
769816

770817
QkCircuitInstruction inst;
771818
qk_circuit_get_instruction(qc, 0, &inst);
@@ -816,10 +863,10 @@ int test_unitary_gate_3q(void) {
816863
if (op_counts.len != 1 || strcmp(op_counts.data[0].name, "unitary") != 0 ||
817864
op_counts.data[0].count != 1) {
818865
result = EqualityError;
819-
qk_opcounts_free(op_counts);
866+
qk_opcounts_clear(&op_counts);
820867
goto cleanup;
821868
}
822-
qk_opcounts_free(op_counts);
869+
qk_opcounts_clear(&op_counts);
823870
QkCircuitInstruction inst;
824871
qk_circuit_get_instruction(qc, 0, &inst);
825872
if (strcmp(inst.name, "unitary") != 0 || inst.num_clbits != 0 || inst.num_params != 0 ||
@@ -893,6 +940,7 @@ int test_circuit(void) {
893940
num_failed += RUN_TEST(test_circuit_copy);
894941
num_failed += RUN_TEST(test_circuit_copy_with_instructions);
895942
num_failed += RUN_TEST(test_no_gate_1000_bits);
943+
num_failed += RUN_TEST(test_get_gate_counts);
896944
num_failed += RUN_TEST(test_get_gate_counts_bv_no_measure);
897945
num_failed += RUN_TEST(test_get_gate_counts_bv_measures);
898946
num_failed += RUN_TEST(test_get_gate_counts_bv_barrier_and_measures);

test/c/test_elide_permutations.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ int test_elide_permutations_swap_result(void) {
9292
}
9393

9494
ops_cleanup:
95-
qk_opcounts_free(op_counts);
95+
qk_opcounts_clear(&op_counts);
9696
result_cleanup:
9797
qk_transpile_layout_free(pass_result);
9898
cleanup:

test/c/test_optimize_1q_sequences.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ int inner_optimize_h_gates(QkTarget *target, char **gates, uint32_t *freq, int n
4848
if (!compare_gate_counts(&counts, gates, freq, num_gates)) {
4949
result = EqualityError;
5050
}
51-
qk_opcounts_free(counts);
51+
qk_opcounts_clear(&counts);
5252
qk_circuit_free(circuit);
5353
qk_target_free(target);
5454
return result;
@@ -182,7 +182,7 @@ int test_optimize_error_over_target_3(void) {
182182
}
183183

184184
cleanup:
185-
qk_opcounts_free(counts);
185+
qk_opcounts_clear(&counts);
186186
qk_target_free(target);
187187
qk_circuit_free(circuit);
188188
return result;
@@ -305,4 +305,4 @@ int test_optimize_1q_sequences(void) {
305305
fprintf(stderr, "=== Number of failed subtests: %i\n", num_failed);
306306

307307
return num_failed;
308-
}
308+
}

test/c/test_sabre_layout.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ int test_sabre_layout_applies_layout(void) {
121121
qk_circuit_free(expected_circuit);
122122

123123
cleanup:
124-
qk_opcounts_free(op_counts);
124+
qk_opcounts_clear(&op_counts);
125125
qk_transpile_layout_free(layout_result);
126126
qk_circuit_free(qc);
127127
qk_target_free(target);

test/c/test_split_2q_unitaries.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ int test_split_2q_unitaries_no_unitaries(void) {
5050
}
5151
}
5252
ops_cleanup:
53-
qk_opcounts_free(counts);
53+
qk_opcounts_clear(&counts);
5454
cleanup:
5555
qk_circuit_free(qc);
5656
return result;
@@ -107,7 +107,7 @@ int test_split_2q_unitaries_x_y_unitary(void) {
107107
}
108108

109109
ops_cleanup:
110-
qk_opcounts_free(counts);
110+
qk_opcounts_clear(&counts);
111111
cleanup:
112112
qk_circuit_free(qc);
113113
return result;
@@ -174,7 +174,7 @@ int test_split_2q_unitaries_swap_x_y_unitary(void) {
174174
}
175175

176176
ops_cleanup:
177-
qk_opcounts_free(counts);
177+
qk_opcounts_clear(&counts);
178178
cleanup:
179179
qk_transpile_layout_free(split_result);
180180
qk_circuit_free(qc);

0 commit comments

Comments
 (0)