Skip to content

Commit 84081c3

Browse files
krystophnyclaude
andcommitted
fix: resolve CI segfaults in contour plotting functions
Replace unsafe merge() function usage in matplotlib wrapper with conditional calls to prevent memory access violations. The merge() function was causing segfaults when optional parameters were not present, particularly in colored_contours and ascii_heatmap_demo examples. Memory safety improvements: - Replace merge() with explicit conditional parameter forwarding - Fix both contour_filled() and add_contour_filled() functions - Prevent passing uninitialized zero-size arrays to underlying methods - Add comprehensive regression tests to prevent future occurrences Fixes critical CI blocking issue where examples crashed with SIGSEGV. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 8b50e0e commit 84081c3

File tree

2 files changed

+185
-12
lines changed

2 files changed

+185
-12
lines changed

src/fortplot_matplotlib.f90

Lines changed: 72 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -108,12 +108,42 @@ subroutine contour_filled(x, y, z, levels, colormap, show_colorbar, label)
108108
allocate(wp_levels(0))
109109
end if
110110

111-
! Forward ALL parameters to underlying method using single call pattern
112-
call fig%add_contour_filled(wp_x, wp_y, wp_z, &
113-
levels=merge(wp_levels, wp_levels, present(levels)), &
114-
colormap=merge(colormap, "", present(colormap)), &
115-
show_colorbar=merge(show_colorbar, .false., present(show_colorbar)), &
116-
label=merge(label, "", present(label)))
111+
! Forward parameters to underlying method using conditional calls for memory safety
112+
if (present(levels) .and. present(colormap) .and. present(show_colorbar) .and. present(label)) then
113+
call fig%add_contour_filled(wp_x, wp_y, wp_z, levels=wp_levels, &
114+
colormap=colormap, show_colorbar=show_colorbar, label=label)
115+
else if (present(levels) .and. present(colormap) .and. present(show_colorbar)) then
116+
call fig%add_contour_filled(wp_x, wp_y, wp_z, levels=wp_levels, &
117+
colormap=colormap, show_colorbar=show_colorbar)
118+
else if (present(levels) .and. present(colormap) .and. present(label)) then
119+
call fig%add_contour_filled(wp_x, wp_y, wp_z, levels=wp_levels, &
120+
colormap=colormap, label=label)
121+
else if (present(colormap) .and. present(show_colorbar) .and. present(label)) then
122+
call fig%add_contour_filled(wp_x, wp_y, wp_z, colormap=colormap, &
123+
show_colorbar=show_colorbar, label=label)
124+
else if (present(levels) .and. present(colormap)) then
125+
call fig%add_contour_filled(wp_x, wp_y, wp_z, levels=wp_levels, colormap=colormap)
126+
else if (present(levels) .and. present(show_colorbar)) then
127+
call fig%add_contour_filled(wp_x, wp_y, wp_z, levels=wp_levels, show_colorbar=show_colorbar)
128+
else if (present(levels) .and. present(label)) then
129+
call fig%add_contour_filled(wp_x, wp_y, wp_z, levels=wp_levels, label=label)
130+
else if (present(colormap) .and. present(show_colorbar)) then
131+
call fig%add_contour_filled(wp_x, wp_y, wp_z, colormap=colormap, show_colorbar=show_colorbar)
132+
else if (present(colormap) .and. present(label)) then
133+
call fig%add_contour_filled(wp_x, wp_y, wp_z, colormap=colormap, label=label)
134+
else if (present(show_colorbar) .and. present(label)) then
135+
call fig%add_contour_filled(wp_x, wp_y, wp_z, show_colorbar=show_colorbar, label=label)
136+
else if (present(levels)) then
137+
call fig%add_contour_filled(wp_x, wp_y, wp_z, levels=wp_levels)
138+
else if (present(colormap)) then
139+
call fig%add_contour_filled(wp_x, wp_y, wp_z, colormap=colormap)
140+
else if (present(show_colorbar)) then
141+
call fig%add_contour_filled(wp_x, wp_y, wp_z, show_colorbar=show_colorbar)
142+
else if (present(label)) then
143+
call fig%add_contour_filled(wp_x, wp_y, wp_z, label=label)
144+
else
145+
call fig%add_contour_filled(wp_x, wp_y, wp_z)
146+
end if
117147

118148
deallocate(wp_x, wp_y, wp_z)
119149
if (allocated(wp_levels)) deallocate(wp_levels)
@@ -433,12 +463,42 @@ subroutine add_contour_filled(x, y, z, levels, colormap, show_colorbar, label)
433463
allocate(wp_levels(0))
434464
end if
435465

436-
! Forward ALL parameters to underlying method using single call pattern
437-
call fig%add_contour_filled(wp_x, wp_y, wp_z, &
438-
levels=merge(wp_levels, wp_levels, present(levels)), &
439-
colormap=merge(colormap, "", present(colormap)), &
440-
show_colorbar=merge(show_colorbar, .false., present(show_colorbar)), &
441-
label=merge(label, "", present(label)))
466+
! Forward parameters to underlying method using conditional calls for memory safety
467+
if (present(levels) .and. present(colormap) .and. present(show_colorbar) .and. present(label)) then
468+
call fig%add_contour_filled(wp_x, wp_y, wp_z, levels=wp_levels, &
469+
colormap=colormap, show_colorbar=show_colorbar, label=label)
470+
else if (present(levels) .and. present(colormap) .and. present(show_colorbar)) then
471+
call fig%add_contour_filled(wp_x, wp_y, wp_z, levels=wp_levels, &
472+
colormap=colormap, show_colorbar=show_colorbar)
473+
else if (present(levels) .and. present(colormap) .and. present(label)) then
474+
call fig%add_contour_filled(wp_x, wp_y, wp_z, levels=wp_levels, &
475+
colormap=colormap, label=label)
476+
else if (present(colormap) .and. present(show_colorbar) .and. present(label)) then
477+
call fig%add_contour_filled(wp_x, wp_y, wp_z, colormap=colormap, &
478+
show_colorbar=show_colorbar, label=label)
479+
else if (present(levels) .and. present(colormap)) then
480+
call fig%add_contour_filled(wp_x, wp_y, wp_z, levels=wp_levels, colormap=colormap)
481+
else if (present(levels) .and. present(show_colorbar)) then
482+
call fig%add_contour_filled(wp_x, wp_y, wp_z, levels=wp_levels, show_colorbar=show_colorbar)
483+
else if (present(levels) .and. present(label)) then
484+
call fig%add_contour_filled(wp_x, wp_y, wp_z, levels=wp_levels, label=label)
485+
else if (present(colormap) .and. present(show_colorbar)) then
486+
call fig%add_contour_filled(wp_x, wp_y, wp_z, colormap=colormap, show_colorbar=show_colorbar)
487+
else if (present(colormap) .and. present(label)) then
488+
call fig%add_contour_filled(wp_x, wp_y, wp_z, colormap=colormap, label=label)
489+
else if (present(show_colorbar) .and. present(label)) then
490+
call fig%add_contour_filled(wp_x, wp_y, wp_z, show_colorbar=show_colorbar, label=label)
491+
else if (present(levels)) then
492+
call fig%add_contour_filled(wp_x, wp_y, wp_z, levels=wp_levels)
493+
else if (present(colormap)) then
494+
call fig%add_contour_filled(wp_x, wp_y, wp_z, colormap=colormap)
495+
else if (present(show_colorbar)) then
496+
call fig%add_contour_filled(wp_x, wp_y, wp_z, show_colorbar=show_colorbar)
497+
else if (present(label)) then
498+
call fig%add_contour_filled(wp_x, wp_y, wp_z, label=label)
499+
else
500+
call fig%add_contour_filled(wp_x, wp_y, wp_z)
501+
end if
442502

443503
deallocate(wp_x, wp_y, wp_z)
444504
if (allocated(wp_levels)) deallocate(wp_levels)
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
program test_contour_memory_safety_regression
2+
!! Test to prevent memory safety regressions in contour plotting
3+
!! Specifically tests the segfault issue fixed in Issue #401 related to merge() function
4+
use fortplot
5+
implicit none
6+
7+
call test_contour_filled_no_optional_args()
8+
call test_contour_filled_with_colormap_only()
9+
call test_add_contour_filled_no_optional_args()
10+
call test_add_contour_filled_with_colormap_only()
11+
12+
print *, "All contour memory safety regression tests passed!"
13+
14+
contains
15+
16+
subroutine test_contour_filled_no_optional_args()
17+
!! Test contour_filled with no optional arguments - this caused segfault in Issue #401
18+
real(wp), dimension(10) :: x_grid, y_grid
19+
real(wp), dimension(10,10) :: z_grid
20+
integer :: i, j
21+
22+
! Generate simple test data
23+
do i = 1, 10
24+
x_grid(i) = (i-1) * 0.5_wp
25+
y_grid(i) = (i-1) * 0.5_wp
26+
end do
27+
28+
do i = 1, 10
29+
do j = 1, 10
30+
z_grid(i,j) = sin(x_grid(i)) * cos(y_grid(j))
31+
end do
32+
end do
33+
34+
! This should NOT segfault - the merge() issue was here
35+
call figure()
36+
call contour_filled(x_grid, y_grid, z_grid)
37+
38+
print *, "✓ contour_filled with no optional args - PASSED"
39+
end subroutine test_contour_filled_no_optional_args
40+
41+
subroutine test_contour_filled_with_colormap_only()
42+
!! Test contour_filled with only colormap argument
43+
real(wp), dimension(5) :: x_grid, y_grid
44+
real(wp), dimension(5,5) :: z_grid
45+
integer :: i, j
46+
47+
! Generate simple test data
48+
do i = 1, 5
49+
x_grid(i) = (i-1) * 1.0_wp
50+
y_grid(i) = (i-1) * 1.0_wp
51+
end do
52+
53+
do i = 1, 5
54+
do j = 1, 5
55+
z_grid(i,j) = x_grid(i) + y_grid(j)
56+
end do
57+
end do
58+
59+
call figure()
60+
call contour_filled(x_grid, y_grid, z_grid, colormap="plasma")
61+
62+
print *, "✓ contour_filled with colormap only - PASSED"
63+
end subroutine test_contour_filled_with_colormap_only
64+
65+
subroutine test_add_contour_filled_no_optional_args()
66+
!! Test add_contour_filled with no optional arguments
67+
real(wp), dimension(8) :: x_grid, y_grid
68+
real(wp), dimension(8,8) :: z_grid
69+
integer :: i, j
70+
71+
! Generate simple test data
72+
do i = 1, 8
73+
x_grid(i) = (i-1) * 0.25_wp
74+
y_grid(i) = (i-1) * 0.25_wp
75+
end do
76+
77+
do i = 1, 8
78+
do j = 1, 8
79+
z_grid(i,j) = exp(-(x_grid(i)**2 + y_grid(j)**2))
80+
end do
81+
end do
82+
83+
call figure()
84+
call add_contour_filled(x_grid, y_grid, z_grid)
85+
86+
print *, "✓ add_contour_filled with no optional args - PASSED"
87+
end subroutine test_add_contour_filled_no_optional_args
88+
89+
subroutine test_add_contour_filled_with_colormap_only()
90+
!! Test add_contour_filled with only colormap argument
91+
real(wp), dimension(6) :: x_grid, y_grid
92+
real(wp), dimension(6,6) :: z_grid
93+
integer :: i, j
94+
95+
! Generate simple test data
96+
do i = 1, 6
97+
x_grid(i) = (i-1) * 0.4_wp
98+
y_grid(i) = (i-1) * 0.4_wp
99+
end do
100+
101+
do i = 1, 6
102+
do j = 1, 6
103+
z_grid(i,j) = x_grid(i)**2 - y_grid(j)**2
104+
end do
105+
end do
106+
107+
call figure()
108+
call add_contour_filled(x_grid, y_grid, z_grid, colormap="inferno")
109+
110+
print *, "✓ add_contour_filled with colormap only - PASSED"
111+
end subroutine test_add_contour_filled_with_colormap_only
112+
113+
end program test_contour_memory_safety_regression

0 commit comments

Comments
 (0)