Skip to content

Commit 2938bea

Browse files
committed
Merge branch 'master' of https://github.com/jacobwilliams/json-fortran into 564-nullify-option
2 parents 764f89b + 9299ad5 commit 2938bea

17 files changed

+254
-29
lines changed

.github/workflows/CI.yml

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ jobs:
1010
fail-fast: false
1111
matrix:
1212
os: [ubuntu-20.04]
13-
gcc_v: [7,8,9,10] # Version of GFortran we want to use.
14-
python-version: [3.9]
13+
gcc_v: [7,8,9,10,11] # Version of GFortran we want to use.
14+
python-version: [3.11]
1515
env:
1616
FC: gfortran-${{matrix.gcc_v}}
1717
GCC_V: ${{matrix.gcc_v}}
@@ -46,7 +46,7 @@ jobs:
4646
if: contains( matrix.os, 'ubuntu')
4747
run: |
4848
python -m pip install --upgrade pip
49-
pip install ford FoBiS.py pygooglechart
49+
pip install ford FoBiS.py pygooglechart fpm
5050
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
5151
5252
- name: Install GFortran Linux
@@ -60,14 +60,72 @@ jobs:
6060
--slave /usr/bin/gcov gcov /usr/bin/gcov-${GCC_V}
6161
6262
- name: Compile_with_build
63-
if: matrix.gcc_v != 7
63+
if: matrix.gcc_v != 7 && matrix.gcc_v != 11
6464
run: |
6565
GFORTRAN=gfortran-${{matrix.gcc_v}}
6666
GCOV=gcov-${{matrix.gcc_v}}
6767
# build with build.sh, run unit tests
6868
./build.sh --skip-documentation
6969
./build.sh --skip-documentation --enable-unicode
7070
71+
- name: Compile_with_FPM
72+
if: matrix.gcc_v == 11
73+
run: |
74+
GFORTRAN=gfortran-${{matrix.gcc_v}}
75+
GCOV=gcov-${{matrix.gcc_v}}
76+
sudo apt update && sudo apt install -y valgrind
77+
fpm test jf_test_01 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
78+
fpm test jf_test_02 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
79+
fpm test jf_test_03 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
80+
fpm test jf_test_04 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
81+
fpm test jf_test_05 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
82+
fpm test jf_test_06 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
83+
fpm test jf_test_07 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
84+
fpm test jf_test_08 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
85+
fpm test jf_test_09 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
86+
fpm test jf_test_10 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
87+
fpm test jf_test_11 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
88+
fpm test jf_test_12 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
89+
fpm test jf_test_13 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
90+
fpm test jf_test_14 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
91+
fpm test jf_test_15 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
92+
fpm test jf_test_16 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
93+
fpm test jf_test_17 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
94+
fpm test jf_test_18 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
95+
fpm test jf_test_19 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
96+
fpm test jf_test_20 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
97+
fpm test jf_test_21 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
98+
fpm test jf_test_22 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
99+
fpm test jf_test_23 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
100+
fpm test jf_test_24 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
101+
fpm test jf_test_25 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
102+
fpm test jf_test_26 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
103+
fpm test jf_test_27 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
104+
fpm test jf_test_28 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
105+
fpm test jf_test_29 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
106+
fpm test jf_test_30 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
107+
fpm test jf_test_31 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
108+
fpm test jf_test_32 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
109+
fpm test jf_test_33 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
110+
fpm test jf_test_34 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
111+
fpm test jf_test_35 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
112+
fpm test jf_test_36 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
113+
fpm test jf_test_37 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
114+
fpm test jf_test_38 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
115+
fpm test jf_test_39 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
116+
fpm test jf_test_40 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
117+
fpm test jf_test_41 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
118+
fpm test jf_test_42 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
119+
fpm test jf_test_43 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
120+
fpm test jf_test_44 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
121+
fpm test jf_test_45 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
122+
fpm test jf_test_46 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
123+
fpm test jf_test_47 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
124+
fpm test jf_test_48 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
125+
fpm test jf_test_49 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
126+
fpm test jf_test_50 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
127+
fpm test jf_test_51 --runner "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1"
128+
71129
- name: Compile_with_cmake
72130
# CMake build with unit tests, no documentation, with coverage analysis
73131
# No unicode so that coverage combined with the build script will cover unicode

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ the [json-fortran package](https://anaconda.org/conda-forge/json-fortran)
7878
can be installed.
7979

8080
```bash
81-
conda install json-fortran
81+
conda install --channel conda-forge json-fortran
8282
```
8383

8484
_Note:_ Packages on conda-forge are build with GCC 9.4 which is upwards

fpm.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,4 +261,9 @@ main = "jf_test_49.F90"
261261
[[test]]
262262
name = "jf_test_50"
263263
source-dir = "src/tests"
264-
main = "jf_test_50.F90"
264+
main = "jf_test_50.F90"
265+
266+
[[test]]
267+
name = "jf_test_51"
268+
source-dir = "src/tests"
269+
main = "jf_test_51.F90"

src/json_initialize_arguments.inc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,4 +111,7 @@ logical(LK),intent(in),optional :: strict_integer_type_checking
111111
!! * If true, an exception will be raised if the integer
112112
!! value cannot be read.
113113
!!
114+
!! (default is true)
115+
logical(LK),intent(in),optional :: allow_trailing_comma
116+
!! Allow a single trailing comma in arrays and objects.
114117
!! (default is true)

src/json_initialize_dummy_arguments.inc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,5 @@ stop_on_error,&
2222
null_to_real_mode,&
2323
non_normal_mode,&
2424
use_quiet_nan, &
25-
strict_integer_type_checking &
25+
strict_integer_type_checking, &
26+
allow_trailing_comma &

src/json_value_module.F90

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,9 @@ module json_value_module
287287
!! * If true [default], an exception will be raised if an integer
288288
!! value cannot be read when parsing JSON.
289289

290+
logical(LK) :: allow_trailing_comma = .true.
291+
!! Allow a single trailing comma in arrays and objects.
292+
290293
integer :: ichunk = 0 !! index in `chunk` for [[pop_char]]
291294
!! when `use_unformatted_stream=True`
292295
integer :: filesize = 0 !! the file size when when `use_unformatted_stream=True`
@@ -1140,6 +1143,10 @@ subroutine json_initialize(me,&
11401143
me%strict_integer_type_checking = strict_integer_type_checking
11411144
end if
11421145

1146+
if (present(allow_trailing_comma)) then
1147+
me%allow_trailing_comma = allow_trailing_comma
1148+
end if
1149+
11431150
!Set the format for real numbers:
11441151
! [if not changing it, then it remains the same]
11451152

@@ -10154,7 +10161,7 @@ recursive subroutine parse_value(json, unit, str, value)
1015410161

1015510162
! start object
1015610163
call json%to_object(value) !allocate class
10157-
call json%parse_object(unit, str, value)
10164+
call json%parse_object(unit, str, value, expecting_next_element=.false.)
1015810165

1015910166
case (start_array)
1016010167

@@ -10879,14 +10886,17 @@ end subroutine to_array
1087910886
!>
1088010887
! Core parsing routine.
1088110888

10882-
recursive subroutine parse_object(json, unit, str, parent)
10889+
recursive subroutine parse_object(json, unit, str, parent, expecting_next_element)
1088310890

1088410891
implicit none
1088510892

1088610893
class(json_core),intent(inout) :: json
1088710894
integer(IK),intent(in) :: unit !! file unit number (if parsing from a file)
1088810895
character(kind=CK,len=*),intent(in) :: str !! JSON string (if parsing from a string)
1088910896
type(json_value),pointer :: parent !! the parsed object will be added as a child of this
10897+
logical(LK),intent(in) :: expecting_next_element !! if true, this object is preceeded by a comma, so
10898+
!! we expect a valid object to exist. used to check
10899+
!! for trailing delimiters.
1089010900

1089110901
type(json_value),pointer :: pair !! temp variable
1089210902
logical(LK) :: eof !! end of file flag
@@ -10907,13 +10917,18 @@ recursive subroutine parse_object(json, unit, str, parent)
1090710917

1090810918
! pair name
1090910919
call json%pop_char(unit, str=str, eof=eof, skip_ws=.true., &
10910-
skip_comments=json%allow_comments, popped=c)
10920+
skip_comments=json%allow_comments, popped=c)
1091110921
if (eof) then
1091210922
call json%throw_exception('Error in parse_object:'//&
1091310923
' Unexpected end of file while parsing start of object.')
1091410924
return
1091510925
else if (end_object == c) then
1091610926
! end of an empty object
10927+
if (expecting_next_element .and. .not. json%allow_trailing_comma) then
10928+
! this is a dangling comma.
10929+
call json%throw_exception('Error in parse_object: '//&
10930+
'Dangling comma when parsing an object.')
10931+
end if
1091710932
return
1091810933
else if (quotation_mark == c) then
1091910934
call json_value_create(pair)
@@ -10935,7 +10950,7 @@ recursive subroutine parse_object(json, unit, str, parent)
1093510950

1093610951
! pair value
1093710952
call json%pop_char(unit, str=str, eof=eof, skip_ws=.true., &
10938-
skip_comments=json%allow_comments, popped=c)
10953+
skip_comments=json%allow_comments, popped=c)
1093910954
if (eof) then
1094010955
call json%destroy(pair)
1094110956
call json%throw_exception('Error in parse_object:'//&
@@ -10959,14 +10974,15 @@ recursive subroutine parse_object(json, unit, str, parent)
1095910974

1096010975
! another possible pair
1096110976
call json%pop_char(unit, str=str, eof=eof, skip_ws=.true., &
10962-
skip_comments=json%allow_comments, popped=c)
10977+
skip_comments=json%allow_comments, popped=c)
1096310978
if (eof) then
1096410979
call json%throw_exception('Error in parse_object: '//&
1096510980
'End of file encountered when parsing an object')
1096610981
return
1096710982
else if (delimiter == c) then
1096810983
! read the next member
10969-
call json%parse_object(unit = unit, str=str, parent = parent)
10984+
call json%parse_object(unit = unit, str=str, parent = parent, &
10985+
expecting_next_element=.true.)
1097010986
else if (end_object == c) then
1097110987
! end of object
1097210988
return
@@ -10996,6 +11012,9 @@ recursive subroutine parse_array(json, unit, str, array)
1099611012
type(json_value),pointer :: element !! temp variable for array element
1099711013
logical(LK) :: eof !! end of file flag
1099811014
character(kind=CK,len=1) :: c !! character returned by [[pop_char]]
11015+
logical(LK) :: expecting_next_element !! to check for trailing delimiters
11016+
11017+
expecting_next_element = .false.
1099911018

1100011019
do
1100111020

@@ -11011,7 +11030,10 @@ recursive subroutine parse_array(json, unit, str, array)
1101111030
end if
1101211031

1101311032
! parse value will deallocate an empty array value
11014-
if (associated(element)) call json%add(array, element)
11033+
if (associated(element)) then
11034+
expecting_next_element = .false.
11035+
call json%add(array, element)
11036+
end if
1101511037

1101611038
! popped the next character
1101711039
call json%pop_char(unit, str=str, eof=eof, skip_ws=.true., &
@@ -11024,9 +11046,15 @@ recursive subroutine parse_array(json, unit, str, array)
1102411046
exit
1102511047
else if (delimiter == c) then
1102611048
! parse the next element
11049+
expecting_next_element = .true.
1102711050
cycle
1102811051
else if (end_array == c) then
1102911052
! end of array
11053+
if (expecting_next_element .and. .not. json%allow_trailing_comma) then
11054+
! this is a dangling comma.
11055+
call json%throw_exception('Error in parse_array: '//&
11056+
'Dangling comma when parsing an array.')
11057+
end if
1103011058
exit
1103111059
else
1103211060
call json%throw_exception('Error in parse_array: '//&

src/tests/jf_test_12.F90

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,10 @@ subroutine test_12(error_cnt)
150150
call json%get(me=root,path='array data.data',array_callback=get_3D_from_array)
151151
call check_errors(all(abs(fetched_array - reshape(raw_array,[size(raw_array)])) <= TOL))
152152

153-
my_file = json_file(root,verbose=.true.,real_format='G')
153+
!my_file = json_file(root,verbose=.true.,real_format='G') ! valgrind says this cases a memory leak
154+
call my_file%initialize(verbose=.true.,real_format='G') ! this doesn't have a memmory leak
155+
call my_file%add(root)
156+
nullify(root)
154157

155158
call my_file%update('array data.description',CK_'vector data',found=existed)
156159
call check_file_errors(existed)

src/tests/jf_test_15.F90

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,9 @@ subroutine test_15(error_cnt)
216216

217217
!****************************************
218218

219-
file1 = json_file(p2,json) !constructor
219+
!file1 = json_file(p2,json) ! memory leak with gfortran?
220+
call file1%initialize(json)
221+
call file1%add(p2); nullify(p2)
220222
call file1%destroy(destroy_core=.true.)
221223

222224
!****************************************

src/tests/jf_test_25.F90

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,9 @@ subroutine test_25(error_cnt)
108108
#endif
109109

110110
! test json_file interface
111-
f = json_file(p)
111+
!f = json_file(p) ! memory leak in gfortran?
112+
call f%initialize()
113+
call f%add(p)
112114
nullify(p) ! data is now in f
113115
call f%get('str_array', vec, ilen, found)
114116
if (.not. found) then

src/tests/jf_test_27.F90

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@ subroutine test_27(error_cnt)
6060
call json%print(p,int(output_unit,IK))
6161

6262
! test json_file interface
63-
f = json_file(p)
63+
!f = json_file(p) ! memory leak in gfortran?
64+
call f%initialize()
65+
call f%add(p)
6466
nullify(p) ! data is now in f
6567
call f%initialize(compress_vectors=.true.)
6668
call f%print()

src/tests/jf_test_37.F90

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,26 +39,34 @@ subroutine test_37(error_cnt)
3939
call json%initialize(no_whitespace=.true.)
4040

4141
call json%deserialize(p, CK_'{"a": ["1", "2", "3"]}')
42-
f = json_file(p,no_whitespace=.true.)
42+
!f = json_file(p,no_whitespace=.true.) ! memory leak in gfortran?
43+
call f%initialize(no_whitespace=.true.)
44+
call f%add(p)
4345
call f%print(int(error_unit,IK))
4446
write(error_unit,'(A)') ''
4547
call check_for_error()
4648
call f%destroy()
4749

4850
call json%deserialize(p, CK_'{"b": ["4", "5", "6"]}')
49-
f = json_file(p,json)
51+
!f = json_file(p,json) ! memory leak in gfortran?
52+
call f%initialize(json)
53+
call f%add(p)
5054
call f%print(int(error_unit,IK))
5155
write(error_unit,'(A)') ''
5256
call check_for_error()
5357
call f%destroy()
5458

55-
f = json_file(CK_'{"x": [1,2,3]}',no_whitespace=.true.)
59+
!f = json_file(CK_'{"x": [1,2,3]}',no_whitespace=.true.) ! memory leak in gfortran?
60+
call f%initialize(no_whitespace=.true.)
61+
call f%deserialize(CK_'{"x": [1,2,3]}')
5662
call f%print(int(error_unit,IK))
5763
write(error_unit,'(A)') ''
5864
call check_for_error()
5965
call f%destroy()
6066

61-
f = json_file(CK_'{"y": [4,5,6]}',json)
67+
!f = json_file(CK_'{"y": [4,5,6]}',json) ! memory leak in gfortran?
68+
call f%initialize(json)
69+
call f%deserialize(CK_'{"y": [4,5,6]}')
6270
call f%print(int(error_unit,IK))
6371
write(error_unit,'(A)') ''
6472
call check_for_error()
@@ -69,26 +77,34 @@ subroutine test_37(error_cnt)
6977
! also test default character kind when unicode is enabled:
7078

7179
call json%deserialize(p, CDK_'{"a": ["1", "2", "3"]}')
72-
f = json_file(p,no_whitespace=.true.)
80+
!f = json_file(p,no_whitespace=.true.) ! memory leak in gfortran?
81+
call f%initialize(no_whitespace=.true.)
82+
call f%add(p)
7383
call f%print(int(error_unit,IK))
7484
write(error_unit,'(A)') ''
7585
call check_for_error()
7686
call f%destroy()
7787

7888
call json%deserialize(p, CDK_'{"b": ["4", "5", "6"]}')
79-
f = json_file(p,json)
89+
!f = json_file(p,json) ! memory leak in gfortran?
90+
call f%initialize(json)
91+
call f%add(p)
8092
call f%print(int(error_unit,IK))
8193
write(error_unit,'(A)') ''
8294
call check_for_error()
8395
call f%destroy()
8496

85-
f = json_file(CDK_'{"x": [1,2,3]}',no_whitespace=.true.)
97+
!f = json_file(CDK_'{"x": [1,2,3]}',no_whitespace=.true.) ! memory leak in gfortran?
98+
call f%initialize(no_whitespace=.true.)
99+
call f%deserialize(CDK_'{"x": [1,2,3]}')
86100
call f%print(int(error_unit,IK))
87101
write(error_unit,'(A)') ''
88102
call check_for_error()
89103
call f%destroy()
90104

91-
f = json_file(CDK_'{"y": [4,5,6]}',json)
105+
!f = json_file(CDK_'{"y": [4,5,6]}',json) ! memory leak in gfortran?
106+
call f%initialize(json)
107+
call f%deserialize(CDK_'{"y": [4,5,6]}')
92108
call f%print(int(error_unit,IK))
93109
write(error_unit,'(A)') ''
94110
call check_for_error()

0 commit comments

Comments
 (0)