-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
test: Check for some unlikely null dereferences in tests #5004
Conversation
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## develop #5004 +/- ##
=========================================
- Coverage 77.9% 77.9% -0.0%
=========================================
Files 784 784
Lines 66681 66682 +1
Branches 8140 8137 -3
=========================================
- Hits 51948 51944 -4
- Misses 14733 14738 +5 |
@@ -51,7 +51,9 @@ void | |||
nflags::operator()(Env& env) const | |||
{ | |||
auto const sle = env.le(account_); | |||
if (sle->isFieldPresent(sfFlags)) | |||
if (!sle) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before line 44 (above), for the sake of consistency, we need to introduce a similar check
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. Fixed.
@@ -51,7 +51,9 @@ void | |||
nflags::operator()(Env& env) const | |||
{ | |||
auto const sle = env.le(account_); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The data members flags::account_
and nflags::account_
always hold a valid instance of the Account
class. These data members are not pointers or std::optional
values.
Hence, env.le(account_)
must always exist, isn't it? Are you envisioning a future modification to these flags
and nflags
classes, due to which a null pointer might be returned here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A valid instance of an Account
does not guarantee that that account has been created / funded on the ledger. So even though the Account
is valid, a null sle
could easily be returned. Consider this test:
Env env{*this};
Account alice("alice");
env.require(flags(alice, asfRequireDest));
env.require(nflags(alice, asfRequireAuth));
That's a perfectly validly written test, but without this change, it'll dereference a null SLE
and crash. With this change, the test will fail in the changed functions, but it won't crash.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
okay, thanks for the clarification.
* upstream/develop: Set version to 2.2.0-rc1
* upstream/develop: Remove flow assert: (5009) Update list of maintainers: (4984)
* upstream/develop: Add external directory to Conan recipe's exports (5006) Add missing includes (5011)
* commit 'c706926': (23 commits) Change order of checks in amm_info: (4924) Add the fixEnforceNFTokenTrustline amendment: (4946) Replaces the usage of boost::string_view with std::string_view (4509) docs: explain how to find a clang-format patch generated by CI (4521) XLS-52d: NFTokenMintOffer (4845) chore: remove repeat words (5041) Expose all amendments known by libxrpl (5026) fixReducedOffersV2: prevent offers from blocking order books: (5032) Additional unit tests for testing deletion of trust lines (4886) Fix conan typo: (5044) Add new command line option to make replaying transactions easier: (5027) Fix compatibility with Conan 2.x: (5001) Set version to 2.2.0 Set version to 2.2.0-rc3 Add xrpl.libpp as an exported lib in conan (5022) Fix Oracle's token pair deterministic order: (5021) Set version to 2.2.0-rc2 Fix last Liquidity Provider withdrawal: Fix offer crossing via single path AMM with transfer fee: Fix adjustAmountsByLPTokens(): ...
* commit 'f6879da': Add bin/physical.sh (4997) Prepare to rearrange sources: (4997)
* upstream/develop: fixInnerObjTemplate2 amendment (5047) Set version to 2.3.0-b1 Ignore restructuring commits (4997) Recompute loops (4997) Rewrite includes (4997) Rearrange sources (4997) Move CMake directory (4997)
* upstream/develop: fix CTID in tx command returns invalidParams on lowercase hex (5049) Invariant: prevent a deleted account from leaving (most) artifacts on the ledger. (4663) Bump codecov plugin version to version 4.5.0 (5055) fix "account_nfts" with unassociated marker returning issue (5045)
* upstream/develop: chore: remove repeat words (5053)
* upstream/develop: Add xrpld build option and Conan package test (5052)
* upstream/develop: Update BUILD.md after PR 5052 (5067)
* Add the same null check in the `flags` class as `nflags`.
* upstream/develop: chore: Add comments to SignerEntries.h (5059) chore: Rename two files from Directory* to Dir*: (5058)
* upstream/develop: Ensure levelization sorting is ASCII-order across platforms (5072) fix: Fix NuDB build error via Conan patch (5061) Disallow filtering account_objects by unsupported types (5056)
* upstream/develop: Update Release Notes for 2.2.1 and 2.2.2 Set version to 2.2.2 Allow only 1 job queue slot for each validation ledger check Allow only 1 job queue slot for acquiring inbound ledger. Track latencies of certain code blocks, and log if they take too long
* upstream/develop: test: Retry RPC commands to try to fix MacOS CI jobs (5120)
* upstream/develop: Set version to 2.3.0-b4 feat(SQLite): allow configurable database pragma values (5135) refactor: re-order PRAGMA statements (5140) fix(book_changes): add "validated" field and reduce RPC latency (5096) chore: fix typos in comments (5094) Set version to 2.2.3 Update SQLite3 max_page_count to match current defaults (5114)
* upstream/develop: Expand Error Message for rpcInternal (4959) docs: clean up API-CHANGELOG.md (5064)
* upstream/develop: Consolidate definitions of fields, objects, transactions, and features (5122) Ignore reformat when blaming Reformat code with clang-format-18 Update pre-commit hook Update clang-format settings Update clang-format workflow
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
* upstream/develop: docs: Add protobuf dependencies to linux setup instructions (5156) fix: reject invalid markers in account_objects RPC calls (5046) Update RELEASENOTES.md (5154) Introduce MPT support (XLS-33d): (5143)
* upstream/develop: Add hubs.xrpkuwait.com to bootstrap (5169)
* upstream/develop: Add AMMClawback Transaction (XLS-0073d) (5142)
* upstream/develop: Fix unity build (5179)
@@ -41,7 +41,7 @@ class AccountSet_test : public beast::unit_test::suite | |||
env.fund(XRP(10000), noripple(alice)); | |||
// ask for the ledger entry - account root, to check its flags | |||
auto const jrr = env.le(alice); | |||
BEAST_EXPECT((*env.le(alice))[sfFlags] == 0u); | |||
BEAST_EXPECT(jrr && jrr->at(sfFlags) == 0u); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this file, there are 22 other dereferences of the *env.le()
function. Visually, I'm convinced that none of them are null-pointers, because all the Account
objects have been funded with some XRP.
But is it necessary to perform this type of null-pointer-check on all of them?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It wouldn't hurt to check. Even though it's been a while, I think it was the double call to env.le()
that caught my attention, and then I just checked for null since I was changing it anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I realize that I did note "Maybe there are more of these?" in the "Future Tasks" section of the description.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
@@ -41,7 +41,9 @@ void | |||
flags::operator()(Env& env) const | |||
{ | |||
auto const sle = env.le(account_); | |||
if (sle->isFieldPresent(sfFlags)) | |||
if (!sle) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add a check inside the flags(Account const& account, Args... args)
constructor, to check for null-pointer in account_
data member? It is not useful to set flags on a non-existent, unfunded account.
However, I've only observed the flags
object passed as an input into env.require()
. If that continues, there's probably no operational difference between the flags::constructor
and the flags::operator()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. In the flags
ctor, account_
is a value, not a pointer. It would be premature to check the ledger via an Env
parameter because the ledger state may change in between the ctor and the call to operator()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That may be true, but why would you ever want to construct a flags
object with an unfunded account?
We can move the validity check up from the operator()
into the constructor.
I can't think of situations where you'd need to create a flags(unfunded_account)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not that you would want to create a flags(unfunded_account)
, it's that the way these helper classes are constructed, there's no way to check whether the account is funded from the constructor.
Here's the definition of the ctor:
template <class... Args>
flags(Account const& account, Args... args)
: flags_helper(args...), account_(account)
{
}
And here's the definition of the operator()
:
void
flags::operator()(Env& env) const
{
auto const sle = env.le(account_);
if (!sle)
env.test.fail();
else if (sle->isFieldPresent(sfFlags))
env.test.expect((sle->getFieldU32(sfFlags) & mask_) == mask_);
else
env.test.expect(mask_ == 0);
}
The difference is the Env
parameter to operator()
. There isn't one of those available to the constructor, and we don't want one, because it would defeat the whole purpose of these helper classes and the env.require
helper wrapper.
In other words, to do the check in the ctor, the callers would have to look something like
env.require(flags(env, alice, flag));
Which is ugly and redundant.
The last problem, IMHO, with trying to do a check like this in the ctor is what do you do if it fails? You could do some variation of a BEAST_EXPECT
that would cause the test suite to fail, but then what? You'd either have an object with a dud account inside it that's going to get operator()
called on it anyway, or you're going to have to throw an exception, which is what I was trying to avoid with this check in the first place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
okay, this is a helpful explanation.
Why do you want to avoid throwing an exception in the constructor? I vaguely remember reading about it, I think it has something to do with the cleanup of the dead object. If you know any resources/blogs, I'd like to read it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you want to avoid throwing an exception in the constructor?
There is nothing intrinsically wrong with throwing an exception in the ctor. It's a good way to handle certain types of errors where you want to prevent an object from being instantiated at all.
The reason I don't want to throw an exception in this ctor is because exceptions need to be caught or they terminate the whole program. The good news is that in the test suite, the tests are all wrapped so that exceptions will be caught, and treated as a test suite error. The bad news is that code is a blunt instrument, which doesn't tell you much about where and why the exception occurred. It also causes the rest of that suite to be skipped, as opposed to a failing BEAST_EXPECT
, which logs a failure including file and line number, and finishes the rest of the suite. That makes debugging a lot easier.
The alternative is to wrap every single instantiation of flags
in a try/catch
block, which just adds unnecessary complication and syntactic garbage that doesn't add anything to the test.
So the short answer is that I don't want to throw in the ctor because it adds unnecessary complication when the check in operator()
is perfectly sufficient.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
okay 👍 makes sense
* upstream/develop: Set version to 2.3.0-rc1 Replace Uint192 with Hash192 in server_definitions response (5177) Fix potential deadlock (5124) Introduce Credentials support (XLS-70d): (5103) Fix token comparison in Payment (5172) Add fixAMMv1_2 amendment (5176)
* upstream/develop: fix: include `index` in `server_definitions` RPC (5190) Fix ledger_entry crash on invalid credentials request (5189)
* upstream/develop: Set version to 2.3.0-rc2
Hold pending 2.3.0 release. |
* upstream/develop: Set version to 2.3.0 refactor(AMMClawback): move tfClawTwoAssets check (5201) Add a new serialized type: STNumber (5121) fix: check for valid ammID field in amm_info RPC (5188)
Manually merged as 8215c60
|
* upstream/develop: test: Check for some unlikely null dereferences in tests (XRPLF#5004) Add Antithesis intrumentation (XRPLF#5042)
High Level Overview of Change
I stumbled upon these couple of places where test code could, but is very unlikely to, dereference a null pointer. I added a simple check to each of them.
Context of Change
This was a random find.
Even though these changes are tiny, I don't think they are insignificant enough to be considered "trivial", so I'm requesting two reviews.
Type of Change
Before / After
There should be no observable change, as tests aren't currently likely / able to get a null value.
Future Tasks
Maybe there are more of these?