Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions BACKLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@


**🚨 CRITICAL: CI test suite failures - user-visible rendering broken**
- [ ] #401: Fix - CI test-coverage failing with segfaults in colored_contours and ascii_heatmap_demo (blocks all PR merges)

**🚨 CRITICAL: User-visible PNG/PDF rendering regressions**

Expand All @@ -13,9 +12,10 @@

**Infrastructure & Documentation Issues (Lower Priority)**
- [ ] #388: Fix - investigate test_mpeg_consolidated failure unrelated to ylabel rotation (test infrastructure)
- [ ] #403: Refactor - reduce contour function complexity - functions exceed 50-line target

## DOING (Current Work)
*Empty - ready for next issue*
- [x] #401: Fix - CI test-coverage failing with segfaults in colored_contours and ascii_heatmap_demo (branch: fix-ci-segfaults-401)

## FUTURE SPRINTS - Systematic Restoration

Expand Down
84 changes: 72 additions & 12 deletions src/fortplot_matplotlib.f90
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,42 @@ subroutine contour_filled(x, y, z, levels, colormap, show_colorbar, label)
allocate(wp_levels(0))
end if

! Forward ALL parameters to underlying method using single call pattern
call fig%add_contour_filled(wp_x, wp_y, wp_z, &
levels=merge(wp_levels, wp_levels, present(levels)), &
colormap=merge(colormap, "", present(colormap)), &
show_colorbar=merge(show_colorbar, .false., present(show_colorbar)), &
label=merge(label, "", present(label)))
! Forward parameters to underlying method using conditional calls for memory safety
if (present(levels) .and. present(colormap) .and. present(show_colorbar) .and. present(label)) then
call fig%add_contour_filled(wp_x, wp_y, wp_z, levels=wp_levels, &
colormap=colormap, show_colorbar=show_colorbar, label=label)
else if (present(levels) .and. present(colormap) .and. present(show_colorbar)) then
call fig%add_contour_filled(wp_x, wp_y, wp_z, levels=wp_levels, &
colormap=colormap, show_colorbar=show_colorbar)
else if (present(levels) .and. present(colormap) .and. present(label)) then
call fig%add_contour_filled(wp_x, wp_y, wp_z, levels=wp_levels, &
colormap=colormap, label=label)
else if (present(colormap) .and. present(show_colorbar) .and. present(label)) then
call fig%add_contour_filled(wp_x, wp_y, wp_z, colormap=colormap, &
show_colorbar=show_colorbar, label=label)
else if (present(levels) .and. present(colormap)) then
call fig%add_contour_filled(wp_x, wp_y, wp_z, levels=wp_levels, colormap=colormap)
else if (present(levels) .and. present(show_colorbar)) then
call fig%add_contour_filled(wp_x, wp_y, wp_z, levels=wp_levels, show_colorbar=show_colorbar)
else if (present(levels) .and. present(label)) then
call fig%add_contour_filled(wp_x, wp_y, wp_z, levels=wp_levels, label=label)
else if (present(colormap) .and. present(show_colorbar)) then
call fig%add_contour_filled(wp_x, wp_y, wp_z, colormap=colormap, show_colorbar=show_colorbar)
else if (present(colormap) .and. present(label)) then
call fig%add_contour_filled(wp_x, wp_y, wp_z, colormap=colormap, label=label)
else if (present(show_colorbar) .and. present(label)) then
call fig%add_contour_filled(wp_x, wp_y, wp_z, show_colorbar=show_colorbar, label=label)
else if (present(levels)) then
call fig%add_contour_filled(wp_x, wp_y, wp_z, levels=wp_levels)
else if (present(colormap)) then
call fig%add_contour_filled(wp_x, wp_y, wp_z, colormap=colormap)
else if (present(show_colorbar)) then
call fig%add_contour_filled(wp_x, wp_y, wp_z, show_colorbar=show_colorbar)
else if (present(label)) then
call fig%add_contour_filled(wp_x, wp_y, wp_z, label=label)
else
call fig%add_contour_filled(wp_x, wp_y, wp_z)
end if

deallocate(wp_x, wp_y, wp_z)
if (allocated(wp_levels)) deallocate(wp_levels)
Expand Down Expand Up @@ -433,12 +463,42 @@ subroutine add_contour_filled(x, y, z, levels, colormap, show_colorbar, label)
allocate(wp_levels(0))
end if

! Forward ALL parameters to underlying method using single call pattern
call fig%add_contour_filled(wp_x, wp_y, wp_z, &
levels=merge(wp_levels, wp_levels, present(levels)), &
colormap=merge(colormap, "", present(colormap)), &
show_colorbar=merge(show_colorbar, .false., present(show_colorbar)), &
label=merge(label, "", present(label)))
! Forward parameters to underlying method using conditional calls for memory safety
if (present(levels) .and. present(colormap) .and. present(show_colorbar) .and. present(label)) then
call fig%add_contour_filled(wp_x, wp_y, wp_z, levels=wp_levels, &
colormap=colormap, show_colorbar=show_colorbar, label=label)
else if (present(levels) .and. present(colormap) .and. present(show_colorbar)) then
call fig%add_contour_filled(wp_x, wp_y, wp_z, levels=wp_levels, &
colormap=colormap, show_colorbar=show_colorbar)
else if (present(levels) .and. present(colormap) .and. present(label)) then
call fig%add_contour_filled(wp_x, wp_y, wp_z, levels=wp_levels, &
colormap=colormap, label=label)
else if (present(colormap) .and. present(show_colorbar) .and. present(label)) then
call fig%add_contour_filled(wp_x, wp_y, wp_z, colormap=colormap, &
show_colorbar=show_colorbar, label=label)
else if (present(levels) .and. present(colormap)) then
call fig%add_contour_filled(wp_x, wp_y, wp_z, levels=wp_levels, colormap=colormap)
else if (present(levels) .and. present(show_colorbar)) then
call fig%add_contour_filled(wp_x, wp_y, wp_z, levels=wp_levels, show_colorbar=show_colorbar)
else if (present(levels) .and. present(label)) then
call fig%add_contour_filled(wp_x, wp_y, wp_z, levels=wp_levels, label=label)
else if (present(colormap) .and. present(show_colorbar)) then
call fig%add_contour_filled(wp_x, wp_y, wp_z, colormap=colormap, show_colorbar=show_colorbar)
else if (present(colormap) .and. present(label)) then
call fig%add_contour_filled(wp_x, wp_y, wp_z, colormap=colormap, label=label)
else if (present(show_colorbar) .and. present(label)) then
call fig%add_contour_filled(wp_x, wp_y, wp_z, show_colorbar=show_colorbar, label=label)
else if (present(levels)) then
call fig%add_contour_filled(wp_x, wp_y, wp_z, levels=wp_levels)
else if (present(colormap)) then
call fig%add_contour_filled(wp_x, wp_y, wp_z, colormap=colormap)
else if (present(show_colorbar)) then
call fig%add_contour_filled(wp_x, wp_y, wp_z, show_colorbar=show_colorbar)
else if (present(label)) then
call fig%add_contour_filled(wp_x, wp_y, wp_z, label=label)
else
call fig%add_contour_filled(wp_x, wp_y, wp_z)
end if

deallocate(wp_x, wp_y, wp_z)
if (allocated(wp_levels)) deallocate(wp_levels)
Expand Down
113 changes: 113 additions & 0 deletions test/test_contour_memory_safety_regression.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
program test_contour_memory_safety_regression
!! Test to prevent memory safety regressions in contour plotting
!! Specifically tests the segfault issue fixed in Issue #401 related to merge() function
use fortplot
implicit none

call test_contour_filled_no_optional_args()
call test_contour_filled_with_colormap_only()
call test_add_contour_filled_no_optional_args()
call test_add_contour_filled_with_colormap_only()

print *, "All contour memory safety regression tests passed!"

contains

subroutine test_contour_filled_no_optional_args()
!! Test contour_filled with no optional arguments - this caused segfault in Issue #401
real(wp), dimension(10) :: x_grid, y_grid
real(wp), dimension(10,10) :: z_grid
integer :: i, j

! Generate simple test data
do i = 1, 10
x_grid(i) = (i-1) * 0.5_wp
y_grid(i) = (i-1) * 0.5_wp
end do

do i = 1, 10
do j = 1, 10
z_grid(i,j) = sin(x_grid(i)) * cos(y_grid(j))
end do
end do

! This should NOT segfault - the merge() issue was here
call figure()
call contour_filled(x_grid, y_grid, z_grid)

print *, "✓ contour_filled with no optional args - PASSED"
end subroutine test_contour_filled_no_optional_args

subroutine test_contour_filled_with_colormap_only()
!! Test contour_filled with only colormap argument
real(wp), dimension(5) :: x_grid, y_grid
real(wp), dimension(5,5) :: z_grid
integer :: i, j

! Generate simple test data
do i = 1, 5
x_grid(i) = (i-1) * 1.0_wp
y_grid(i) = (i-1) * 1.0_wp
end do

do i = 1, 5
do j = 1, 5
z_grid(i,j) = x_grid(i) + y_grid(j)
end do
end do

call figure()
call contour_filled(x_grid, y_grid, z_grid, colormap="plasma")

print *, "✓ contour_filled with colormap only - PASSED"
end subroutine test_contour_filled_with_colormap_only

subroutine test_add_contour_filled_no_optional_args()
!! Test add_contour_filled with no optional arguments
real(wp), dimension(8) :: x_grid, y_grid
real(wp), dimension(8,8) :: z_grid
integer :: i, j

! Generate simple test data
do i = 1, 8
x_grid(i) = (i-1) * 0.25_wp
y_grid(i) = (i-1) * 0.25_wp
end do

do i = 1, 8
do j = 1, 8
z_grid(i,j) = exp(-(x_grid(i)**2 + y_grid(j)**2))
end do
end do

call figure()
call add_contour_filled(x_grid, y_grid, z_grid)

print *, "✓ add_contour_filled with no optional args - PASSED"
end subroutine test_add_contour_filled_no_optional_args

subroutine test_add_contour_filled_with_colormap_only()
!! Test add_contour_filled with only colormap argument
real(wp), dimension(6) :: x_grid, y_grid
real(wp), dimension(6,6) :: z_grid
integer :: i, j

! Generate simple test data
do i = 1, 6
x_grid(i) = (i-1) * 0.4_wp
y_grid(i) = (i-1) * 0.4_wp
end do

do i = 1, 6
do j = 1, 6
z_grid(i,j) = x_grid(i)**2 - y_grid(j)**2
end do
end do

call figure()
call add_contour_filled(x_grid, y_grid, z_grid, colormap="inferno")

print *, "✓ add_contour_filled with colormap only - PASSED"
end subroutine test_add_contour_filled_with_colormap_only

end program test_contour_memory_safety_regression
Loading