-
Notifications
You must be signed in to change notification settings - Fork 4
fix: add defensive cycle guard to prerequisite evaluation #582
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
base: main
Are you sure you want to change the base?
Changes from all commits
50a0218
31dc040
5fffbec
f5e3f16
f5acda5
4bc1fe8
540629a
b9e27d5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,3 @@ | ||
| #include "client_impl.hpp" | ||
| #include "data_sources/null_data_source.hpp" | ||
| #include "data_sources/polling_data_source.hpp" | ||
|
|
@@ -211,7 +211,7 @@ | |
| std::unordered_map<Client::FlagKey, Value> result; | ||
| for (auto& [key, descriptor] : flag_manager_.Store().GetAll()) { | ||
| if (descriptor->item) { | ||
| result.try_emplace(key, descriptor->item->Detail().Value()); | ||
| } | ||
| } | ||
| return result; | ||
|
|
@@ -244,10 +244,11 @@ | |
| } | ||
|
|
||
| template <typename T> | ||
| EvaluationDetail<T> ClientImpl::VariationInternal(FlagKey const& key, | ||
|
Check warning on line 247 in libs/client-sdk/src/client_impl.cpp
|
||
| Value default_value, | ||
| bool check_type, | ||
| bool detailed) { | ||
| bool detailed, | ||
| std::unordered_set<std::string>* visited) { | ||
| auto desc = flag_manager_.Store().Get(key); | ||
|
|
||
| events::FeatureEventParams event = { | ||
|
|
@@ -300,7 +301,7 @@ | |
|
|
||
| LD_ASSERT(desc->item); | ||
|
|
||
| auto const& flag = *(desc->item); | ||
| auto const& detail = flag.Detail(); | ||
|
|
||
| // The Prerequisites vector represents the evaluated prerequisites of | ||
|
|
@@ -308,19 +309,35 @@ | |
| // prerequisites (recursively), which is necessary to ensure LaunchDarkly | ||
| // analytics functions properly. | ||
| // | ||
| // We're using JsonVariation because the type of the | ||
| // prerequisite is both unknown and irrelevant to emitting the events. | ||
| // | ||
| // We're passing Value::Null() to match a server-side SDK's behavior when | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The part of this comment about Value::Null() is still valid, right? Maybe we should keep that part?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch — I over-trimmed. Restored the |
||
| // evaluating prerequisites. | ||
| // | ||
| // `visited` tracks the current path so a cyclic prerequisite graph | ||
| // terminates instead of recursing without bound. | ||
| // | ||
| // NOTE: if "hooks" functionality is implemented into this SDK, take care | ||
| // that evaluating prerequisites does not trigger hooks. This may require | ||
| // refactoring the code below to not use JsonVariation. | ||
| if (auto const prereqs = flag.Prerequisites()) { | ||
| // refactoring the code below. | ||
| auto const prereqs = flag.Prerequisites(); | ||
| if (prereqs && !prereqs->empty()) { | ||
| std::unordered_set<std::string> local_visited; | ||
| auto* ancestors = visited ? visited : &local_visited; | ||
| ancestors->insert(key); | ||
| for (auto const& prereq : *prereqs) { | ||
| JsonVariation(prereq, Value::Null()); | ||
| if (ancestors->count(prereq) != 0) { | ||
| // Cyclic edge: skip descent, continue with remaining | ||
| // prerequisites at this level. The requested flag's value | ||
| // and reason (below) are unaffected. | ||
| LD_LOG(logger_, LogLevel::kWarn) | ||
| << "prerequisite cycle detected: skipping edge from '" | ||
| << key << "' to '" << prereq << "'"; | ||
| continue; | ||
| } | ||
| (void)VariationInternal<Value>(prereq, Value::Null(), | ||
| /*check_type=*/false, | ||
| /*detailed=*/false, ancestors); | ||
| } | ||
| ancestors->erase(key); | ||
| } | ||
|
|
||
| if (check_type && default_value.Type() != Value::Type::kNull && | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
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.
Existing lint issues are being ignored by this PR.