Skip to content

Commit

Permalink
♻️ change behavior for null FILE*
Browse files Browse the repository at this point in the history
  • Loading branch information
nlohmann committed Jul 22, 2022
1 parent 313e8eb commit 5a97913
Show file tree
Hide file tree
Showing 7 changed files with 53 additions and 54 deletions.
11 changes: 6 additions & 5 deletions docs/mkdocs/docs/api/basic_json/accept.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand Down
8 changes: 6 additions & 2 deletions docs/mkdocs/docs/api/basic_json/parse.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -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
Expand All @@ -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"
Expand Down
27 changes: 27 additions & 0 deletions docs/mkdocs/docs/features/assertions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
```
10 changes: 0 additions & 10 deletions docs/mkdocs/docs/home/exceptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 7 additions & 6 deletions include/nlohmann/detail/input/input_adapters.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down
13 changes: 7 additions & 6 deletions single_include/nlohmann/json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down
25 changes: 0 additions & 25 deletions tests/src/unit-deserialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand Down

0 comments on commit 5a97913

Please sign in to comment.