Skip to content

Commit 7bc8e68

Browse files
committed
additional error checks for swap routine.
Added a new routine is_child_of to check if one json_value is a child of another. Fixed a bug in the traverse routine, where the finished output flag was not being correctly checked. Added additional check for malformed linked list.
1 parent 1ba58a8 commit 7bc8e68

File tree

2 files changed

+154
-93
lines changed

2 files changed

+154
-93
lines changed

src/json_value_module.F90

Lines changed: 103 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,11 @@ module json_value_module
497497
generic,public :: swap => json_value_swap
498498
procedure :: json_value_swap
499499

500+
!>
501+
! Check if a [[json_value]] is a child of another.
502+
generic,public :: is_child_of => json_value_is_child_of
503+
procedure :: json_value_is_child_of
504+
500505
!>
501506
! Throw an exception.
502507
generic,public :: throw_exception => MAYBEWRAP(json_throw_exception)
@@ -1299,19 +1304,15 @@ end subroutine json_value_remove
12991304
! [[json_value]] linked list (so the normal `parent`, `previous`,
13001305
! `next`, etc. pointers are properly associated if necessary).
13011306
!
1302-
!@warning This should not be used to swap an element with one of its
1303-
! direct children (along the first in the lists), since that would
1304-
! produce a circular linkage. A check should be added for this...
1305-
! Only the simple cases where p1/p2 or p2/p1 are parent/child
1306-
! are currently checked.
1307+
!@warning This cannot be used to swap a parent/child pair, since that
1308+
! could lead to a circular linkage. An exception is thrown if
1309+
! this is tried.
13071310
!
1308-
!@warning There are also other situations where using this routine would
1309-
! produce a malformed JSON structure, such as swapping an array
1310-
! with one of its children. This is not checked for.
1311+
!@warning There are also other situations where using this routine may
1312+
! produce a malformed JSON structure, such as moving an array
1313+
! element outside of an array. This is not checked for.
13111314
!
13121315
!@note If `p1` and `p2` have a common parent, it is always safe to swap them.
1313-
!
1314-
!@warning This is a work-in-progress and has not yet been fully validated.
13151316

13161317
subroutine json_value_swap(json,p1,p2)
13171318

@@ -1333,18 +1334,18 @@ subroutine json_value_swap(json,p1,p2)
13331334
!aren't pointing to the same thing:
13341335
if (.not. associated(p1,p2)) then
13351336

1336-
!TODO Need to check *all* the `children` pointers, so make sure
1337-
! cases like p1%child%...%child => p2 don't occur...
1338-
if (associated(p1%parent,p2) .or. associated(p2%parent,p1)) then
1337+
!we will not allow swapping an item with one of its descendants:
1338+
if (json%is_child_of(p1,p2) .or. json%is_child_of(p2,p1)) then
13391339
call json%throw_exception('Error in json_value_swap: '//&
1340-
'cannot swap a parent/child pair')
1340+
'cannot swap an item with one of its descendants')
13411341
else
13421342

13431343
same_parent = ( associated(p1%parent) .and. &
13441344
associated(p2%parent) .and. &
13451345
associated(p1%parent,p2%parent) )
13461346
if (same_parent) then
1347-
!if p1,p2 are the first,last or last,first children of a common parent
1347+
!if p1,p2 are the first,last or last,first
1348+
!children of a common parent
13481349
first_last = (associated(p1%parent%children,p1) .and. &
13491350
associated(p2%parent%tail,p2)) .or. &
13501351
(associated(p1%parent%tail,p1) .and. &
@@ -1447,6 +1448,48 @@ end subroutine swap_pointers
14471448
end subroutine json_value_swap
14481449
!*****************************************************************************************
14491450

1451+
!*****************************************************************************************
1452+
!> author: Jacob Williams
1453+
! date: 4/28/2016
1454+
!
1455+
! Returns True if `p2` is a descendant of `p1`
1456+
! (i.e, a child, or a child of child, etc.)
1457+
1458+
function json_value_is_child_of(json,p1,p2) result(is_child_of)
1459+
1460+
implicit none
1461+
1462+
class(json_core),intent(inout) :: json
1463+
type(json_value),pointer :: p1
1464+
type(json_value),pointer :: p2
1465+
logical :: is_child_of
1466+
1467+
is_child_of = .false.
1468+
1469+
if (associated(p1) .and. associated(p2)) then
1470+
if (associated(p1%children)) then
1471+
call json%traverse(p1%children,is_child_of_callback)
1472+
end if
1473+
end if
1474+
1475+
contains
1476+
1477+
subroutine is_child_of_callback(json,p,finished)
1478+
!! Traverse until `p` is `p2`.
1479+
implicit none
1480+
1481+
class(json_core),intent(inout) :: json
1482+
type(json_value),pointer,intent(in) :: p
1483+
logical(LK),intent(out) :: finished
1484+
1485+
is_child_of = associated(p,p2)
1486+
finished = is_child_of ! stop searching if found
1487+
1488+
end subroutine is_child_of_callback
1489+
1490+
end function json_value_is_child_of
1491+
!*****************************************************************************************
1492+
14501493
!*****************************************************************************************
14511494
!> author: Jacob Williams
14521495
! date: 12/6/2014
@@ -4155,6 +4198,11 @@ subroutine json_get_array(json, me, array_callback)
41554198
count = json%count(me)
41564199
element => me%children
41574200
do i = 1, count ! callback for each child
4201+
if (.not. associated(element)) then
4202+
call json%throw_exception('Error in json_get_array: '//&
4203+
'Malformed JSON linked list')
4204+
return
4205+
end if
41584206
call array_callback(json, element, i, count)
41594207
element => element%next
41604208
end do
@@ -4173,45 +4221,64 @@ end subroutine json_get_array
41734221

41744222
!*****************************************************************************************
41754223
!> author: Jacob Williams
4176-
! date: 09/02/2015
4224+
! date: 4/28/2016
41774225
!
41784226
! Traverse a JSON structure.
41794227
! This routine calls the user-specified [[traverse_callback_func]]
41804228
! for each element of the structure.
4181-
!
4182-
recursive subroutine json_traverse(json,me,traverse_callback)
4229+
4230+
subroutine json_traverse(json,me,traverse_callback)
41834231

41844232
implicit none
41854233

41864234
class(json_core),intent(inout) :: json
41874235
type(json_value),pointer,intent(in) :: me
41884236
procedure(traverse_callback_func) :: traverse_callback
41894237

4190-
type(json_value),pointer :: element !! a child element
4191-
integer(IK) :: i !! counter
4192-
integer(IK) :: icount !! number of children
41934238
logical(LK) :: finished !! can be used to stop the process
41944239

4195-
if (json%exception_thrown) return
4240+
if (.not. json%exception_thrown) call traverse(me)
41964241

4197-
call traverse_callback(json,me,finished) ! first call for this object
4198-
if (finished) return
4242+
contains
41994243

4200-
!for arrays and objects, have to also call for all children:
4201-
if (me%var_type==json_array .or. me%var_type==json_object) then
4244+
recursive subroutine traverse(p)
4245+
4246+
!! recursive [[json_value]] traversal.
4247+
4248+
implicit none
4249+
4250+
type(json_value),pointer,intent(in) :: p
4251+
4252+
type(json_value),pointer :: element !! a child element
4253+
integer(IK) :: i !! counter
4254+
integer(IK) :: icount !! number of children
4255+
4256+
if (json%exception_thrown) return
4257+
call traverse_callback(json,p,finished) ! first call for this object
4258+
if (finished) return
4259+
4260+
!for arrays and objects, have to also call for all children:
4261+
if (p%var_type==json_array .or. p%var_type==json_object) then
4262+
4263+
icount = json%count(p) ! number of children
4264+
if (icount>0) then
4265+
element => p%children ! first one
4266+
do i = 1, icount ! call for each child
4267+
if (.not. associated(element)) then
4268+
call json%throw_exception('Error in json_traverse: '//&
4269+
'Malformed JSON linked list')
4270+
return
4271+
end if
4272+
call traverse(element)
4273+
if (finished) exit
4274+
element => element%next
4275+
end do
4276+
end if
4277+
nullify(element)
42024278

4203-
icount = json%count(me) ! number of children
4204-
if (icount>0) then
4205-
element => me%children ! first one
4206-
do i = 1, icount ! call for each child
4207-
call json%traverse(element,traverse_callback)
4208-
if (finished) exit
4209-
element => element%next
4210-
end do
42114279
end if
4212-
nullify(element)
42134280

4214-
end if
4281+
end subroutine traverse
42154282

42164283
end subroutine json_traverse
42174284
!*****************************************************************************************

src/tests/jf_test_16.f90

Lines changed: 51 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -36,20 +36,18 @@ subroutine test_16(error_cnt)
3636
write(error_unit,'(A)') ''
3737
write(error_unit,'(A)') 'Original:'
3838
call json%parse(p, '{"cities": ["New York","Los Angeles","Chicago"], '//&
39-
'"value": 1, "flag": true, "struct":{"vec":[1,2,3]}}')
39+
'"value": 1, "iflag": true, "struct":{"vec":[1,2,3]}}')
4040
if (json%failed()) then
4141
call json%print_error_message(error_unit)
4242
error_cnt = error_cnt + 1
4343
end if
44-
!call print_tree(json,p)
4544
call json%print(p,error_unit)
4645

4746
write(error_unit,'(A)') ''
48-
write(error_unit,'(A)') 'Swap: cities <-> flag'
47+
write(error_unit,'(A)') 'Swap: cities <-> iflag'
4948
call json%get(p,'cities',p1)
50-
call json%get(p,'flag',p2)
49+
call json%get(p,'iflag',p2)
5150
call json%swap(p1,p2)
52-
!call print_tree(json,p)
5351
call json%print(p,output_unit)
5452
if (json%failed()) then
5553
call json%print_error_message(error_unit)
@@ -59,11 +57,10 @@ subroutine test_16(error_cnt)
5957
nullify(p2)
6058

6159
write(error_unit,'(A)') ''
62-
write(error_unit,'(A)') 'Swap: flag <-> value'
63-
call json%get(p,'flag',p1)
60+
write(error_unit,'(A)') 'Swap: iflag <-> value'
61+
call json%get(p,'iflag',p1)
6462
call json%get(p,'value',p2)
6563
call json%swap(p1,p2)
66-
!call print_tree(json,p)
6764
call json%print(p,output_unit)
6865
if (json%failed()) then
6966
call json%print_error_message(error_unit)
@@ -73,11 +70,10 @@ subroutine test_16(error_cnt)
7370
nullify(p2)
7471

7572
write(error_unit,'(A)') ''
76-
write(error_unit,'(A)') 'Swap: flag <-> struct.vec'
77-
call json%get(p,'flag',p1)
73+
write(error_unit,'(A)') 'Swap: iflag <-> struct.vec'
74+
call json%get(p,'iflag',p1)
7875
call json%get(p,'struct.vec',p2)
7976
call json%swap(p1,p2)
80-
!call print_tree(json,p)
8177
call json%print(p,output_unit)
8278
if (json%failed()) then
8379
call json%print_error_message(error_unit)
@@ -88,57 +84,55 @@ subroutine test_16(error_cnt)
8884

8985
call json%destroy(p)
9086

91-
end subroutine test_16
92-
93-
subroutine print_tree(json,p)
94-
95-
!! just for debugging. Print some info about the structure.
96-
97-
implicit none
98-
99-
type(json_core),intent(inout) :: json
100-
type(json_value),pointer,intent(in) :: p
87+
!...........................................................................
88+
! another case
10189

102-
character(kind=CK,len=:),allocatable :: p_name, name
103-
type(json_value),pointer :: q,r
104-
integer :: n_children,i
90+
write(error_unit,'(A)') ''
91+
write(error_unit,'(A)') '.....................................'
92+
write(error_unit,'(A)') ''
93+
write(error_unit,'(A)') 'Original:'
94+
call json%parse(p, '{ "stats": { "iflag": 0, "str": "ok" },'//&
95+
'"vars": [{ "label": "r", "value": 0.0 }, '//&
96+
'{ "label": "v", "value": 0.0 }],'//&
97+
'"empty": { } }')
98+
if (json%failed()) then
99+
call json%print_error_message(error_unit)
100+
error_cnt = error_cnt + 1
101+
end if
102+
call json%print(p,error_unit)
105103

106-
write(*,*) ''
107-
write(*,*) '------------'
108-
call json%info(p,name=p_name,n_children=n_children)
109-
write(*,*) 'name: '//p_name
110-
write(*,*) 'n_children: ',n_children
104+
!this one is not allowed, and should fail:
105+
write(error_unit,'(A)') ''
106+
write(error_unit,'(A)') 'Swap: vars(1).label <-> vars'
107+
call json%get(p,'vars(1).label',p1)
108+
call json%get(p,'vars',p2)
109+
call json%swap(p1,p2)
110+
call json%print(p,output_unit)
111+
if (.not. json%failed()) then
112+
write(error_unit,'(A)') 'Error: this should have failed.'
113+
error_cnt = error_cnt + 1
114+
else
115+
write(error_unit,'(A)') 'Success: This operation is not allowed.'
116+
call json%clear_exceptions()
117+
end if
118+
nullify(p1)
119+
nullify(p2)
111120

112-
call json%get_parent(p,q)
113-
if (associated(q)) then
114-
call json%info(q,name=name)
115-
write(*,*) 'root parent: '//name
121+
!this one should work:
122+
write(error_unit,'(A)') ''
123+
write(error_unit,'(A)') 'Swap: empty <-> stat.str'
124+
call json%get(p,'empty',p1)
125+
call json%get(p,'stat.str',p2)
126+
call json%swap(p1,p2)
127+
call json%print(p,output_unit)
128+
if (json%failed()) then
129+
call json%print_error_message(error_unit)
130+
error_cnt = error_cnt + 1
116131
end if
132+
nullify(p1)
133+
nullify(p2)
117134

118-
do i=1,n_children
119-
call json%get_child(p, i, q)
120-
if (associated(q)) then
121-
call json%info(q,name=name)
122-
write(*,*) 'child ',i,name
123-
call json%get_previous(q,r)
124-
if (associated(r)) then
125-
call json%info(r,name=name)
126-
write(*,*) ' prev ',i,name
127-
end if
128-
call json%get_next(q,r)
129-
if (associated(r)) then
130-
call json%info(r,name=name)
131-
write(*,*) ' next ',i,name
132-
end if
133-
else
134-
call json%print_error_message(error_unit)
135-
exit
136-
end if
137-
end do
138-
write(*,*) '------------'
139-
write(*,*) ''
140-
141-
end subroutine print_tree
135+
end subroutine test_16
142136

143137
end module jf_test_16_mod
144138
!*****************************************************************************************

0 commit comments

Comments
 (0)