diff --git a/.gitignore b/.gitignore index aecfd8f76e..20a4920b4f 100644 --- a/.gitignore +++ b/.gitignore @@ -27,7 +27,8 @@ visual_studio/jsonfortranlib/ visual_studio/jsonfortrantest/Debug/ visual_studio/jsonfortrantest/Release/ visual_studio/jsonfortrantest/x64 - +visual_studio/config +My Advisor Results* My Amplifier* My Inspector* x64/ diff --git a/files/inputs/invalid.json b/files/inputs/invalid.json index 097356fa15..60def78a88 100644 --- a/files/inputs/invalid.json +++ b/files/inputs/invalid.json @@ -10,4 +10,4 @@ "..\\path\\to\\files\\file1.txt", "..\\path\\to\\files\\file2.txt", "..\\path\\to\\files\\file3.txt" -} +} \ No newline at end of file diff --git a/files/inputs/invalid3.json b/files/inputs/invalid3.json new file mode 100644 index 0000000000..e7b82d6c74 --- /dev/null +++ b/files/inputs/invalid3.json @@ -0,0 +1,4 @@ +{ +"a": "blah\", +"b": 2 +} diff --git a/files/inputs/test1.json b/files/inputs/test1.json index 4915f36aa4..abb957aecc 100644 --- a/files/inputs/test1.json +++ b/files/inputs/test1.json @@ -12,7 +12,8 @@ "..\\path\\to\\files\\file3.txt", "test \u2FA4 \uABCD \uABCD\uABCDtest", " test \\u \" blah\\\" test test", - "..\\path\\to\\files\\" + "..\\path\\to\\files\\", + "\\" ], "a": { "b": 1 diff --git a/src/json_file_module.F90 b/src/json_file_module.F90 index cc62dbb320..5c45b13d48 100644 --- a/src/json_file_module.F90 +++ b/src/json_file_module.F90 @@ -64,7 +64,8 @@ module json_file_module private - type(json_core) :: core !! The instance of the [[json_core(type)]] factory used for this file. + type(json_core) :: core !! The instance of the [[json_core(type)]] + !! factory used for this file. type(json_value),pointer :: p => null() !! the JSON structure read from the file contains @@ -319,10 +320,17 @@ subroutine json_file_check_for_errors(me,status_ok,error_msg) implicit none class(json_file),intent(inout) :: me - logical(LK),intent(out) :: status_ok !! true if there were no errors - character(kind=CK,len=:),allocatable,intent(out) :: error_msg !! the error message (if there were errors) - + logical(LK),intent(out),optional :: status_ok !! true if there were no errors + character(kind=CK,len=:),allocatable,intent(out),optional :: error_msg !! the error message + !! (if there were errors) + +#if defined __GFORTRAN__ + character(kind=CK,len=:),allocatable :: tmp !! workaround for gfortran bugs + call me%core%check_for_errors(status_ok,tmp) + error_msg = tmp +#else call me%core%check_for_errors(status_ok,error_msg) +#endif end subroutine json_file_check_for_errors !***************************************************************************************** diff --git a/src/json_string_utilities.F90 b/src/json_string_utilities.F90 index e0c52b168a..2c7423fb9f 100644 --- a/src/json_string_utilities.F90 +++ b/src/json_string_utilities.F90 @@ -433,68 +433,74 @@ end subroutine escape_string ! * `\t` - horizontal tab ! * `\uXXXX` - 4 hexadecimal digits - subroutine unescape_string(str_in, str_out, error_message) + subroutine unescape_string(str, error_message) implicit none - character(kind=CK,len=*),intent(in) :: str_in !! string as stored in a [[json_value]] - character(kind=CK,len=:),allocatable,intent(out) :: str_out !! decoded string - character(kind=CK,len=:),allocatable,intent(out) :: error_message !! will be allocated if there was an error + character(kind=CK,len=:),allocatable,intent(inout) :: str !! in: string as stored + !! in a [[json_value]]. + !! out: decoded string. + character(kind=CK,len=:),allocatable,intent(out) :: error_message !! will be allocated if + !! there was an error integer :: i !! counter - integer :: n !! length of str_in - integer :: m !! length of str_out + integer :: n !! length of `str` + integer :: m !! length of `str_tmp` character(kind=CK,len=1) :: c !! for scanning each character in string + character(kind=CK,len=:),allocatable :: str_tmp !! temp decoded string (if the input + !! string contains an escape character + !! and needs to be decoded). -#if defined __GFORTRAN__ - character(kind=CK,len=:),allocatable :: tmp !! for GFortran bug workaround -#endif - - if (scan(str_in,backslash)>0) then + if (scan(str,backslash)>0) then !there is at least one escape character, so process this string: - n = len(str_in) - str_out = repeat(space,n) !size the output string (will be trimmed later) - m = 0 !counter in str_out - i = 0 !counter in str_in + n = len(str) + str_tmp = repeat(space,n) !size the output string (will be trimmed later) + m = 0 !counter in str_tmp + i = 0 !counter in str do i = i + 1 if (i>n) exit ! finished - c = str_in(i:i) ! get next character in the string + c = str(i:i) ! get next character in the string if (c == backslash) then if (i0) then + i = len(line) + if (line(i:i)/=newline) line = line//newline + else + line = line//newline + end if + !create the error message: - if (allocated(json%err_message)) json%err_message = json%err_message//newline - json%err_message = 'line: '//trim(adjustl(line_str))//', '//& + if (allocated(json%err_message)) then + json%err_message = json%err_message//newline + else + json%err_message = '' + end if + json%err_message = json%err_message//& + 'line: '//trim(adjustl(line_str))//', '//& 'character: '//trim(adjustl(char_str))//newline//& - trim(line)//newline//arrow_str + line//arrow_str if (allocated(line)) deallocate(line) @@ -9008,6 +9031,11 @@ subroutine get_current_line_from_file_stream(json,iunit,line) iend = json%ipos do read(iunit,pos=iend,iostat=ios) c + if (IS_IOSTAT_END(ios)) then + ! account for end of file without linebreak + iend=iend-1 + exit + end if if (c==newline .or. ios/=0) exit iend=iend+1 end do @@ -9868,27 +9896,27 @@ end subroutine parse_array ! * Jacob Williams : 6/16/2014 : Added hex validation. ! * Jacob Williams : 12/3/2015 : Fixed some bugs. ! * Jacob Williams : 8/23/2015 : `string` is now returned unescaped. +! * Jacob Williams : 7/21/2018 : moved hex validate to [[unescape_string]]. subroutine parse_string(json, unit, str, string) implicit none class(json_core),intent(inout) :: json - integer(IK),intent(in) :: unit !! file unit number (if parsing from a file) - character(kind=CK,len=*),intent(in) :: str !! JSON string (if parsing from a string) - character(kind=CK,len=:),allocatable,intent(out) :: string !! the string (unescaped if necessary) + integer(IK),intent(in) :: unit !! file unit number (if + !! parsing from a file) + character(kind=CK,len=*),intent(in) :: str !! JSON string (if parsing + !! from a string) + character(kind=CK,len=:),allocatable,intent(out) :: string !! the string (unescaped + !! if necessary) logical(LK) :: eof !! end of file flag - logical(LK) :: is_hex !! it is a hex string logical(LK) :: escape !! for escape string parsing character(kind=CK,len=1) :: c !! character returned by [[pop_char]] - character(kind=CK,len=4) :: hex !! hex string - integer(IK) :: i !! counter integer(IK) :: ip !! index to put next character, !! to speed up by reducing the number !! of character string reallocations. - character(kind=CK,len=:),allocatable :: string_unescaped !! temp variable - character(kind=CK,len=:),allocatable :: error_message !! for string unescaping + character(kind=CK,len=:),allocatable :: error_message !! for string unescaping !at least return a blank string if there is a problem: string = repeat(space, chunk_size) @@ -9896,10 +9924,8 @@ subroutine parse_string(json, unit, str, string) if (.not. json%exception_thrown) then !initialize: - ip = 1 - is_hex = .false. escape = .false. - i = 0 + ip = 1 do @@ -9913,8 +9939,6 @@ subroutine parse_string(json, unit, str, string) else if (c==quotation_mark .and. .not. escape) then !end of string - if (is_hex) call json%throw_exception('Error in parse_string:'//& - ' incomplete hex string: \u'//trim(hex)) exit else @@ -9926,34 +9950,13 @@ subroutine parse_string(json, unit, str, string) string(ip:ip) = c ip = ip + 1 - !hex validation: - if (is_hex) then !accumulate the four characters after '\u' - - i=i+1 - hex(i:i) = c - if (i==4) then - if (valid_json_hex(hex)) then - i = 0 - hex = CK_'' - is_hex = .false. - else - call json%throw_exception('Error in parse_string:'//& - ' invalid hex string: \u'//trim(hex)) - exit - end if - end if - + ! check for escape character, so we don't + ! exit prematurely if escaping a quotation + ! character: + if (escape) then + escape = .false. else - - !when the '\u' string is encountered, then - ! start accumulating the hex string (should be the next 4 characters) - if (escape) then - escape = .false. - is_hex = (c==CK_'u') !the next four characters are the hex string - else - escape = (c==backslash) - end if - + escape = (c==backslash) end if end if @@ -9969,18 +9972,14 @@ subroutine parse_string(json, unit, str, string) end if end if - !string is returned unescaped: - call unescape_string(string,string_unescaped,error_message) + ! string is returned unescaped: + ! (this will also validate any hex strings present) + call unescape_string(string,error_message) if (allocated(error_message)) then call json%throw_exception(error_message) - else - string = string_unescaped + deallocate(error_message) !cleanup end if - !cleanup: - if (allocated(error_message)) deallocate(error_message) - if (allocated(string_unescaped)) deallocate(string_unescaped) - end if end subroutine parse_string diff --git a/src/tests/jf_test_06.F90 b/src/tests/jf_test_06.F90 index def5f64042..23fbab97cb 100644 --- a/src/tests/jf_test_06.F90 +++ b/src/tests/jf_test_06.F90 @@ -7,7 +7,8 @@ module jf_test_6_mod - use json_module + use json_module, CK => json_CK, LK => json_LK + use json_parameters, only: newline use, intrinsic :: iso_fortran_env , only: error_unit, output_unit, wp => real64 implicit none @@ -28,10 +29,14 @@ subroutine test_6(error_cnt) integer,intent(out) :: error_cnt type(json_file) :: json - integer :: i + integer :: i, j + character(kind=CK,len=:),allocatable :: error_msg + character(kind=CK,len=:),allocatable :: expected_error_msg + logical(LK) :: status_ok - character(len=*),dimension(2),parameter :: files = ['invalid.json ',& - 'invalid2.json'] + character(len=*),dimension(3),parameter :: files = ['invalid.json ',& + 'invalid2.json',& + 'invalid3.json'] error_cnt = 0 call json%initialize() @@ -46,7 +51,7 @@ subroutine test_6(error_cnt) write(error_unit,'(A)') '=================================' write(error_unit,'(A)') '' - do i=1,2 + do i=1,size(files) ! parse the json file: write(error_unit,'(A)') '' @@ -54,7 +59,31 @@ subroutine test_6(error_cnt) write(error_unit,'(A)') '' call json%load_file(filename = dir//trim(files(i))) if (json%failed()) then + + if (i==1) then + call json%check_for_errors(status_ok, error_msg=error_msg) + expected_error_msg = CK_'Error in parse_array: Unexpected character encountered when parsing array.'//newline//& + CK_'line: 13, character: 1'//newline//& + CK_'}'//newline//'^' + if (error_msg /= expected_error_msg) then + ! verify that the expected error string is present + write(error_unit,'(A)') 'Error: unexpected error message string: "'//error_msg//'"' + write(error_unit,'(A)') '' + write(error_unit,*) 'len(error_msg) = ', len(error_msg) + write(error_unit,*) 'len(expected_error_msg) = ', len(expected_error_msg) + do j = 1, min(len(error_msg), len(expected_error_msg)) + if (error_msg(j:j) /= expected_error_msg(j:j)) then + write(error_unit,'(I3,1X,A,A,A,A,A)') j, '"', error_msg(j:j), '" /= "', expected_error_msg(j:j), '"' + else + write(error_unit,'(I3,1X,A,A,A,A,A)') j, '"', error_msg(j:j), '" == "', expected_error_msg(j:j), '"' + end if + end do + error_cnt = error_cnt + 1 + end if + end if + call json%print_error_message(error_unit) + else write(error_unit,'(A)') 'An error should have been raised!' error_cnt = error_cnt + 1 diff --git a/src/tests/jf_test_10.F90 b/src/tests/jf_test_10.F90 index fefc1b429c..9723bb0c91 100644 --- a/src/tests/jf_test_10.F90 +++ b/src/tests/jf_test_10.F90 @@ -144,7 +144,7 @@ subroutine test_10(error_cnt) error_cnt = error_cnt + 1 else !also make sure the values are correct: - if (found .and. size(str_vec)==6 .and. & + if (found .and. size(str_vec)==7 .and. & str_vec(1)=='..\path\to\files\file1.txt') then write(error_unit,'(A)') '...success' else @@ -303,7 +303,7 @@ subroutine test_10(error_cnt) error_cnt = error_cnt + 1 else !also make sure the values are correct: - if (found .and. size(str_vec)==6 .and. & + if (found .and. size(str_vec)==7 .and. & str_vec(1)=='..\path\to\files\file1.txt') then write(error_unit,'(A)') '...success' else