Skip to content

Commit

Permalink
Merge pull request #151 from tlsa/tlsa/float-underflow
Browse files Browse the repository at this point in the history
Permissive floating point value loading.
  • Loading branch information
tlsa authored Mar 14, 2021
2 parents f9a76de + 2343075 commit 9fbfaa9
Show file tree
Hide file tree
Showing 4 changed files with 351 additions and 10 deletions.
5 changes: 4 additions & 1 deletion include/cyaml/cyaml.h
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,10 @@ typedef enum cyaml_flag {
* * For \ref CYAML_ENUM, the value becomes the value of the enum.
* The numerical value is treated as signed.
* * For \ref CYAML_FLAGS, the values are bitwise ORed together.
* The numerical values are treated as unsigned,
* The numerical values are treated as unsigned.
*
* For \ref CYAML_FLOAT types, in strict mode floating point values
* that would cause overflow or underflow are not permitted.
*/
CYAML_FLAG_STRICT = (1 << 4),
/**
Expand Down
41 changes: 39 additions & 2 deletions src/load.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
#include <assert.h>
#include <limits.h>
#include <errno.h>
#include <float.h>
#include <math.h>

#include <yaml.h>

Expand Down Expand Up @@ -1490,10 +1492,31 @@ static cyaml_err_t cyaml__read_float_f(
errno = 0;
temp = strtof(value, &end);

if (end == value || errno == ERANGE) {
if (end == value) {
cyaml__log(ctx->config, CYAML_LOG_ERROR,
"Load: Invalid FLOAT value: %s\n", value);
return CYAML_ERR_INVALID_VALUE;

} else if (errno == ERANGE) {
cyaml_log_t level = CYAML_LOG_ERROR;

if (!cyaml__flag_check_all(schema->flags, CYAML_FLAG_STRICT)) {
level = CYAML_LOG_NOTICE;
}

if (temp == HUGE_VALF || temp == -HUGE_VALF) {
cyaml__log(ctx->config, level,
"Load: FLOAT overflow: %s\n", value);

} else {
assert(temp < FLT_MIN || temp > FLT_MAX);
cyaml__log(ctx->config, level,
"Load: FLOAT underflow: %s\n", value);
}

if (cyaml__flag_check_all(schema->flags, CYAML_FLAG_STRICT)) {
return CYAML_ERR_INVALID_VALUE;
}
}

memcpy(data, &temp, sizeof(temp));
Expand Down Expand Up @@ -1527,10 +1550,24 @@ static cyaml_err_t cyaml__read_float_d(
errno = 0;
temp = strtod(value, &end);

if (end == value || errno == ERANGE) {
if (end == value) {
cyaml__log(ctx->config, CYAML_LOG_ERROR,
"Load: Invalid FLOAT value: %s\n", value);
return CYAML_ERR_INVALID_VALUE;

} else if (errno == ERANGE) {
cyaml_log_t level = CYAML_LOG_ERROR;

if (!cyaml__flag_check_all(schema->flags, CYAML_FLAG_STRICT)) {
level = CYAML_LOG_NOTICE;
}

cyaml__log(ctx->config, level,
"Load: FLOAT overflow/overflow: %s\n", value);

if (cyaml__flag_check_all(schema->flags, CYAML_FLAG_STRICT)) {
return CYAML_ERR_INVALID_VALUE;
}
}

memcpy(data, &temp, sizeof(temp));
Expand Down
212 changes: 205 additions & 7 deletions test/units/errs.c
Original file line number Diff line number Diff line change
Expand Up @@ -2885,7 +2885,7 @@ static bool test_err_save_schema_bad_bitfield(
* \param[in] config The CYAML config to use for the test.
* \return true if test passes, false otherwise.
*/
static bool test_err_load_schema_invalid_value_float_range(
static bool test_err_load_schema_invalid_value_float_range1(
ttest_report_ctx_t *report,
const cyaml_config_t *config)
{
Expand All @@ -2895,7 +2895,152 @@ static bool test_err_load_schema_invalid_value_float_range(
float a;
} *data_tgt = NULL;
static const struct cyaml_schema_field mapping_schema[] = {
CYAML_FIELD_FLOAT("a", CYAML_FLAG_DEFAULT,
CYAML_FIELD_FLOAT("a",
CYAML_FLAG_DEFAULT | CYAML_FLAG_STRICT,
struct target_struct, a),
CYAML_FIELD_END
};
static const struct cyaml_schema_value top_schema = {
CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER,
struct target_struct, mapping_schema),
};
test_data_t td = {
.data = (cyaml_data_t **) &data_tgt,
.config = config,
.schema = &top_schema,
};
cyaml_err_t err;

ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td);

err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema,
(cyaml_data_t **) &data_tgt, NULL);
if (err != CYAML_ERR_INVALID_VALUE) {
return ttest_fail(&tc, cyaml_strerror(err));
}

if (data_tgt != NULL) {
return ttest_fail(&tc, "Data non-NULL on error.");
}

return ttest_pass(&tc);
}

/**
* Test loading when schema expects float but value is out of range.
*
* \param[in] report The test report context.
* \param[in] config The CYAML config to use for the test.
* \return true if test passes, false otherwise.
*/
static bool test_err_load_schema_invalid_value_float_range2(
ttest_report_ctx_t *report,
const cyaml_config_t *config)
{
static const unsigned char yaml[] =
"a: -3.5e+38\n";
struct target_struct {
float a;
} *data_tgt = NULL;
static const struct cyaml_schema_field mapping_schema[] = {
CYAML_FIELD_FLOAT("a",
CYAML_FLAG_DEFAULT | CYAML_FLAG_STRICT,
struct target_struct, a),
CYAML_FIELD_END
};
static const struct cyaml_schema_value top_schema = {
CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER,
struct target_struct, mapping_schema),
};
test_data_t td = {
.data = (cyaml_data_t **) &data_tgt,
.config = config,
.schema = &top_schema,
};
cyaml_err_t err;

ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td);

err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema,
(cyaml_data_t **) &data_tgt, NULL);
if (err != CYAML_ERR_INVALID_VALUE) {
return ttest_fail(&tc, cyaml_strerror(err));
}

if (data_tgt != NULL) {
return ttest_fail(&tc, "Data non-NULL on error.");
}

return ttest_pass(&tc);
}

/**
* Test loading when schema expects float but value is out of range.
*
* \param[in] report The test report context.
* \param[in] config The CYAML config to use for the test.
* \return true if test passes, false otherwise.
*/
static bool test_err_load_schema_invalid_value_float_range3(
ttest_report_ctx_t *report,
const cyaml_config_t *config)
{
static const unsigned char yaml[] =
"a: 1.55331e-40f\n";
struct target_struct {
float a;
} *data_tgt = NULL;
static const struct cyaml_schema_field mapping_schema[] = {
CYAML_FIELD_FLOAT("a",
CYAML_FLAG_DEFAULT | CYAML_FLAG_STRICT,
struct target_struct, a),
CYAML_FIELD_END
};
static const struct cyaml_schema_value top_schema = {
CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER,
struct target_struct, mapping_schema),
};
test_data_t td = {
.data = (cyaml_data_t **) &data_tgt,
.config = config,
.schema = &top_schema,
};
cyaml_err_t err;

ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td);

err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema,
(cyaml_data_t **) &data_tgt, NULL);
if (err != CYAML_ERR_INVALID_VALUE) {
return ttest_fail(&tc, cyaml_strerror(err));
}

if (data_tgt != NULL) {
return ttest_fail(&tc, "Data non-NULL on error.");
}

return ttest_pass(&tc);
}

/**
* Test loading when schema expects float but value is out of range.
*
* \param[in] report The test report context.
* \param[in] config The CYAML config to use for the test.
* \return true if test passes, false otherwise.
*/
static bool test_err_load_schema_invalid_value_float_range4(
ttest_report_ctx_t *report,
const cyaml_config_t *config)
{
static const unsigned char yaml[] =
"a: -1.55331e-40f\n";
struct target_struct {
float a;
} *data_tgt = NULL;
static const struct cyaml_schema_field mapping_schema[] = {
CYAML_FIELD_FLOAT("a",
CYAML_FLAG_DEFAULT | CYAML_FLAG_STRICT,
struct target_struct, a),
CYAML_FIELD_END
};
Expand Down Expand Up @@ -2979,17 +3124,66 @@ static bool test_err_load_schema_invalid_value_float_invalid(
* \param[in] config The CYAML config to use for the test.
* \return true if test passes, false otherwise.
*/
static bool test_err_load_schema_invalid_value_double_range(
static bool test_err_load_schema_invalid_value_double_range1(
ttest_report_ctx_t *report,
const cyaml_config_t *config)
{
static const unsigned char yaml[] =
"a: 1.8e+308\n";
"a: 1.8e+4999\n";
struct target_struct {
double a;
} *data_tgt = NULL;
static const struct cyaml_schema_field mapping_schema[] = {
CYAML_FIELD_FLOAT("a", CYAML_FLAG_DEFAULT,
CYAML_FIELD_FLOAT("a",
CYAML_FLAG_DEFAULT | CYAML_FLAG_STRICT,
struct target_struct, a),
CYAML_FIELD_END
};
static const struct cyaml_schema_value top_schema = {
CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER,
struct target_struct, mapping_schema),
};
test_data_t td = {
.data = (cyaml_data_t **) &data_tgt,
.config = config,
.schema = &top_schema,
};
cyaml_err_t err;

ttest_ctx_t tc = ttest_start(report, __func__, cyaml_cleanup, &td);

err = cyaml_load_data(yaml, YAML_LEN(yaml), config, &top_schema,
(cyaml_data_t **) &data_tgt, NULL);
if (err != CYAML_ERR_INVALID_VALUE) {
return ttest_fail(&tc, cyaml_strerror(err));
}

if (data_tgt != NULL) {
return ttest_fail(&tc, "Data non-NULL on error.");
}

return ttest_pass(&tc);
}

/**
* Test loading when schema expects double but value is out of range.
*
* \param[in] report The test report context.
* \param[in] config The CYAML config to use for the test.
* \return true if test passes, false otherwise.
*/
static bool test_err_load_schema_invalid_value_double_range2(
ttest_report_ctx_t *report,
const cyaml_config_t *config)
{
static const unsigned char yaml[] =
"a: -1.8e+4999\n";
struct target_struct {
double a;
} *data_tgt = NULL;
static const struct cyaml_schema_field mapping_schema[] = {
CYAML_FIELD_FLOAT("a",
CYAML_FLAG_DEFAULT | CYAML_FLAG_STRICT,
struct target_struct, a),
CYAML_FIELD_END
};
Expand Down Expand Up @@ -5909,13 +6103,17 @@ bool errs_tests(
pass &= test_err_load_schema_invalid_value_int_range_3(rc, &config);
pass &= test_err_load_schema_invalid_value_int_range_4(rc, &config);
pass &= test_err_load_schema_invalid_value_int_range_5(rc, &config);
pass &= test_err_load_schema_invalid_value_float_range(rc, &config);
pass &= test_err_load_schema_invalid_value_double_range(rc, &config);
pass &= test_err_load_schema_invalid_value_uint_range_1(rc, &config);
pass &= test_err_load_schema_invalid_value_uint_range_2(rc, &config);
pass &= test_err_load_schema_invalid_value_uint_range_3(rc, &config);
pass &= test_err_load_schema_invalid_value_uint_range_4(rc, &config);
pass &= test_err_load_schema_invalid_value_uint_range_5(rc, &config);
pass &= test_err_load_schema_invalid_value_float_range1(rc, &config);
pass &= test_err_load_schema_invalid_value_float_range2(rc, &config);
pass &= test_err_load_schema_invalid_value_float_range3(rc, &config);
pass &= test_err_load_schema_invalid_value_float_range4(rc, &config);
pass &= test_err_load_schema_invalid_value_double_range1(rc, &config);
pass &= test_err_load_schema_invalid_value_double_range2(rc, &config);
pass &= test_err_load_schema_invalid_value_float_invalid(rc, &config);
pass &= test_err_load_schema_invalid_value_double_invalid(rc, &config);

Expand Down
Loading

0 comments on commit 9fbfaa9

Please sign in to comment.