♻️ change behavior for null FILE*

This commit is contained in:
Niels Lohmann 2022-07-22 15:34:35 +02:00
parent 313e8eb0e7
commit 5a97913e93
No known key found for this signature in database
GPG Key ID: 7F3CEA63AE251B69
7 changed files with 53 additions and 54 deletions

View File

@ -29,7 +29,7 @@ Unlike the [`parse`](parse.md) function, this function neither throws an excepti
: A compatible input, for instance:
- an `std::istream` object
- a `FILE` pointer
- a `FILE` pointer (must not be null)
- a C-style array of characters
- a pointer to a null-terminated string of single byte characters
- a `std::string`
@ -64,10 +64,6 @@ Whether the input is valid JSON.
Strong guarantee: if an exception is thrown, there are no changes in the JSON value.
## Exceptions
Throws [`parse_error.116`](../../home/exceptions.md#jsonexceptionparse_error116) if passed `#!cpp FILE` pointer is `#!cpp nullptr`.
## Complexity
Linear in the length of the input. The parser is a predictive LL(1) parser.
@ -76,6 +72,11 @@ Linear in the length of the input. The parser is a predictive LL(1) parser.
(1) A UTF-8 byte order mark is silently ignored.
!!! danger "Runtime assertion"
The precondition that a passed `#!cpp FILE` pointer must not be null is enforced with a
[runtime assertion](../../features/assertions.md).
## Examples
??? example

View File

@ -28,7 +28,7 @@ static basic_json parse(IteratorType first, IteratorType last,
: A compatible input, for instance:
- an `std::istream` object
- a `FILE` pointer
- a `FILE` pointer (must not be null)
- a C-style array of characters
- a pointer to a null-terminated string of single byte characters
- a `std::string`
@ -77,7 +77,6 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
- Throws [`parse_error.102`](../../home/exceptions.md#jsonexceptionparse_error102) if to_unicode fails or surrogate
error.
- Throws [`parse_error.103`](../../home/exceptions.md#jsonexceptionparse_error103) if to_unicode fails.
- Throws [`parse_error.116`](../../home/exceptions.md#jsonexceptionparse_error116) if passed `#!cpp FILE` pointer is `#!cpp nullptr`.
## Complexity
@ -89,6 +88,11 @@ super-linear complexity.
(1) A UTF-8 byte order mark is silently ignored.
!!! danger "Runtime assertion"
The precondition that a passed `#!cpp FILE` pointer must not be null is enforced with a
[runtime assertion](../../features/assertions.md).
## Examples
??? example "Parsing from a character array"

View File

@ -102,3 +102,30 @@ behavior and yields a runtime assertion.
```
Assertion failed: (m_object != nullptr), function operator++, file iter_impl.hpp, line 368.
```
### Reading from a null `FILE` pointer
Reading from a null `#!cpp FILE` pointer is undefined behavior and yields a runtime assertion. This can happen when
calling `#!cpp std::fopen` on a nonexisting file.
??? example "Example 4: Uninitialized iterator"
The following code will trigger an assertion at runtime:
```cpp
#include <nlohmann/json.hpp>
using json = nlohmann::json;
int main()
{
std::FILE* f = std::fopen("nonexisting_file.json", "r");
json j = json::parse(f);
}
```
Output:
```
Assertion failed: (m_file != nullptr), function file_input_adapter, file input_adapters.hpp, line 55.
```

View File

@ -354,16 +354,6 @@ A UBJSON high-precision number could not be parsed.
[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing UBJSON high-precision number: invalid number text: 1A
```
### json.exception.parse_error.116
A `#!cpp FILE` pointer passed to the [parse](../api/basic_json/parse.md) function is `#!cpp nullptr`; that is, a previous call to `#!cpp std::fopen` failed.
!!! failure "Example message"
```
[json.exception.parse_error.116] parse error: input file is invalid
```
## Iterator errors
This exception is thrown if iterators passed to a library function do not match

View File

@ -49,13 +49,10 @@ class file_input_adapter
using char_type = char;
JSON_HEDLEY_NON_NULL(2)
explicit file_input_adapter(std::FILE* f)
explicit file_input_adapter(std::FILE* f) noexcept
: m_file(f)
{
if (m_file == nullptr)
{
JSON_THROW(parse_error::create(116, 0, "input file is invalid", nullptr));
}
JSON_ASSERT(m_file != nullptr);
}
// make class move-only
@ -67,7 +64,11 @@ class file_input_adapter
std::char_traits<char>::int_type get_character() noexcept
{
return std::fgetc(m_file);
if (JSON_HEDLEY_LIKELY(m_file != nullptr))
{
return std::fgetc(m_file);
}
return std::char_traits<char>::eof(); // LCOV_EXCL_LINE
}
private:

View File

@ -5936,13 +5936,10 @@ class file_input_adapter
using char_type = char;
JSON_HEDLEY_NON_NULL(2)
explicit file_input_adapter(std::FILE* f)
explicit file_input_adapter(std::FILE* f) noexcept
: m_file(f)
{
if (m_file == nullptr)
{
JSON_THROW(parse_error::create(116, 0, "input file is invalid", nullptr));
}
JSON_ASSERT(m_file != nullptr);
}
// make class move-only
@ -5954,7 +5951,11 @@ class file_input_adapter
std::char_traits<char>::int_type get_character() noexcept
{
return std::fgetc(m_file);
if (JSON_HEDLEY_LIKELY(m_file != nullptr))
{
return std::fgetc(m_file);
}
return std::char_traits<char>::eof(); // LCOV_EXCL_LINE
}
private:

View File

@ -166,26 +166,6 @@ struct SaxEventLoggerExitAfterStartArray : public SaxEventLogger
};
} // namespace
// Passing a NULL pointer to the input adapter violates its NON_NULL attribute which is detected by UBSAN.
// To still test whether exceptions are thrown, we need to exclude these tests from UBSAN which can only
// be done with a function attribute. See
// https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html#disabling-instrumentation-with-attribute-no-sanitize-undefined
#if defined(__clang__)
__attribute__((no_sanitize("undefined")))
#endif
void test_file_exception();
#if defined(__clang__)
__attribute__((no_sanitize("undefined")))
#endif
void test_file_exception()
{
std::FILE* f = std::fopen("nonexisting_file", "r"); // NOLINT(cppcoreguidelines-owning-memory)
json _;
CHECK_THROWS_WITH_AS(_ = json::parse(f), "[json.exception.parse_error.116] parse error: input file is invalid", json::parse_error&);
CHECK_THROWS_WITH_AS(_ = json::accept(f), "[json.exception.parse_error.116] parse error: input file is invalid", json::parse_error&);
}
TEST_CASE("deserialization")
{
SECTION("successful deserialization")
@ -352,11 +332,6 @@ TEST_CASE("deserialization")
{
CHECK_THROWS_WITH_AS("[\"foo\",1,2,3,false,{\"one\":1}"_json, "[json.exception.parse_error.101] parse error at line 1, column 29: syntax error while parsing array - unexpected end of input; expected ']'", json::parse_error&);
}
SECTION("FILE*")
{
test_file_exception();
}
}
SECTION("contiguous containers")