Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add isValid to nix::Value #10555

Merged
merged 6 commits into from May 1, 2024
Merged

Conversation

jlesquembre
Copy link
Member

Motivation

Add a method to check if a value has been initialized. This helps avoid segfaults when calling type().
Useful in the context of the new C API.

Context

Closes #10524

Priorities and Process

Add 馃憤 to pull requests you find important.

The Nix maintainer team uses a GitHub project board to schedule and track reviews.

Copy link
Member

@roberth roberth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a valid problem, but "unset" could be part of the language, whereas this is decidedly about the implementation of the evaluator+bindings.

We have some non-null checks for Value *; perhaps we should also check against uninitialized values in those call sites, and have a helper for that? We could name the helpers after the in and out documented in the parameters.
We might even check that out is uninitialized. We don't want callers to create mutable variables; that would lead to horribly unpredictable outcomes.

@@ -23,6 +23,7 @@ class BindingsBuilder;


typedef enum {
tUnset,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
tUnset,
tInvalid = 0,

This suggests being part of the implementation more so than the language itself.
For instance, an attribute can be unset, but in that case we return NULL and set NIX_ERR_KEY.
(Also make the 0 explicit)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually this might be better

Suggested change
tUnset,
tUninitialized,

@@ -294,6 +296,11 @@ public:
internalType = newType;
}

inline bool isInitialized()
Copy link
Member

@roberth roberth Apr 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
inline bool isInitialized()
/** A value becomes valid when it is initialized. We don't use this in the evaluator; only in the bindings, where the slight extra cost is warranted because of inexperienced callers. */
inline bool isValid()

This comment was marked as resolved.

@roberth roberth added language The Nix expression language; parser, interpreter, primops, evaluation, etc c api Nix as a C library with a stable interface labels Apr 19, 2024
@jlesquembre
Copy link
Member Author

@roberth thanks for your suggestions, I made those changes.

Regarding the extra checks, see: 7835d55
Is that what you had in mind?

@jlesquembre jlesquembre changed the title Add isInitialized to nix::Value Add isValid to nix::Value Apr 20, 2024
Copy link
Member

@roberth roberth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Close to ready for merge, but also have suggestions to make it a bit more ergonomic.

@@ -20,7 +20,7 @@
# include "gc_cpp.h"
#endif

// Helper function to throw an exception if value is null
// Helper function to throw an exception if value is null or in an invalid state
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This wasn't actually changed.

Suggested change
// Helper function to throw an exception if value is null or in an invalid state
// Helper function to throw an exception if value is null


TEST_F(nix_api_expr_test, nix_value_set_get_float)
{
float myDouble = 1.0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated, but this freaked me out for a sec. It doesn't seem to serve a purpose.

Suggested change
float myDouble = 1.0;
double myDouble = 1.0;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops, good catch, addressed it here: f7da544

Comment on lines -98 to -100
ASSERT_DEATH(nix_get_list_byidx(ctx, value, state, 0), "");
ASSERT_DEATH(nix_get_list_size(ctx, value), "");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

馃帀
Was considering to get rid of these, because ASSERT_DEATH is slow.

@@ -33,5 +35,14 @@ protected:
std::string msg(p, n);
FAIL() << "nix_err_code(ctx) != NIX_OK, message: " << msg;
}

inline void assert_ctx_err()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not immediately necessary, but it would be good to check the kind of error, and return the message (for when that's relevant to check).

Suggested change
inline void assert_ctx_err()
inline std::string assert_ctx_err(nix_err expected_err)

or const char *

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, but I'd prefer to open a new PR, this one is getting big enough.

Comment on lines 128 to 129
auto & v = check_value_not_null(value);
check_value_initialized(v);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check_value_initialized is only used after check_value_not_null, which suggests that we should perhaps have a helper function that does both?
We can then also rephrase it in higher level terms:
check_value_in: check_value_not_null + check_value_initialized
check_value_out: check_value_not_null + check_value_uninitialized

The only other cases I can think of now, where we might want to use check_value_initialized separately is

  • nullable parameters. For those we could have check_value_in_or_null.
  • checking multiple values in a buffer, e.g. list or attrset builder. I'm not entirely sure we should do that, because it makes constructing cyclic structures difficult.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, I added check_value_in and check_value_out helpers. I'm not happy about having separate versions for const and non-const data, but I can't think of a good solution. link to the code


nullable parameters. For those we could have check_value_in_or_null.

Good point. However, I don't believe we currently have any functions that accept nullable values. If necessary, we can add a helper function for that in the future.


checking multiple values in a buffer, e.g. list or attrset builder. I'm not entirely sure we should do that, because it makes constructing cyclic structures difficult.

In addition, the order of calling nix_init_* and nix_[list,builder]_builder_insert shouldn't matter. I've updated these functions to only check for a null value.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@roberth Kindly pinging you, since I think you might have missed the latest changes. (If not, no worries, just wanted to make sure you're aware, and take your time to review it)

@roberth roberth merged commit e17aad2 into NixOS:master May 1, 2024
9 checks passed
@jlesquembre jlesquembre deleted the jl/c-api_check-init branch May 2, 2024 11:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c api Nix as a C library with a stable interface language The Nix expression language; parser, interpreter, primops, evaluation, etc
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add helper function to check if a value has been initialized
3 participants