@@ -14,6 +14,8 @@ struct ConfigDependencyGraph {
14
14
vector<of<ConfigItemRef>> node_stack;
15
15
vector<string> key_stack;
16
16
map<string, vector<of<Dependency>>> deps;
17
+ // paths for checking circular dependencies
18
+ vector<string> resolve_chain;
17
19
18
20
void Add (an<Dependency> dependency);
19
21
@@ -311,12 +313,6 @@ an<ConfigResource> ConfigCompiler::Compile(const string& file_name) {
311
313
return resource;
312
314
}
313
315
314
- static inline an<ConfigItem> if_resolved (ConfigCompiler* compiler,
315
- an<ConfigItem> item,
316
- const string& path) {
317
- return item && compiler->resolved (path) ? item : nullptr ;
318
- }
319
-
320
316
static bool ResolveBlockingDependencies (ConfigCompiler* compiler,
321
317
const string& path) {
322
318
if (!compiler->blocking (path)) {
@@ -335,42 +331,42 @@ static an<ConfigItem> GetResolvedItem(ConfigCompiler* compiler,
335
331
const string& path) {
336
332
DLOG (INFO) << " GetResolvedItem(" << resource->resource_id << " :" << path << " )" ;
337
333
string node_path = resource->resource_id + " :" ;
338
- if (!resource || compiler->blocking (node_path)) {
339
- return nullptr ;
340
- }
341
- an<ConfigItem> result = *resource;
334
+ an<ConfigItemRef> node = resource;
342
335
if (path.empty () || path == " /" ) {
343
- return if_resolved ( compiler, result, node_path);
336
+ return compiler-> ResolveDependencies ( node_path) ? **node : nullptr ;
344
337
}
345
338
vector<string> keys = ConfigData::SplitPath (path);
346
339
for (const auto & key : keys) {
347
- if (Is<ConfigList>(result)) {
340
+ if (!ResolveBlockingDependencies (compiler, node_path)) {
341
+ LOG (WARNING) << " accessing blocking node with unresolved dependencies: "
342
+ << node_path;
343
+ // CAVEAT: continuing accessing subtree with this failure may result in
344
+ // referencing outdated data - sometimes an expected behavior.
345
+ // relaxing this requires checking for circular dependencies.
346
+ // return nullptr;
347
+ }
348
+ an<ConfigItem> item = **node;
349
+ if (Is<ConfigList>(item)) {
348
350
if (ConfigData::IsListItemReference (key)) {
349
- size_t index = ConfigData::ResolveListIndex (result , key, true );
351
+ size_t index = ConfigData::ResolveListIndex (item , key, true );
350
352
(node_path += " /" ) += ConfigData::FormatListIndex (index );
351
- if (!ResolveBlockingDependencies (compiler, node_path)) {
352
- return nullptr ;
353
- }
354
- result = As<ConfigList>(result)->GetAt (index );
353
+ node = New<ConfigListEntryRef>(nullptr , As<ConfigList>(item), index );
355
354
} else {
356
- result .reset ();
355
+ node .reset ();
357
356
}
358
- } else if (Is<ConfigMap>(result )) {
357
+ } else if (Is<ConfigMap>(item )) {
359
358
DLOG (INFO) << " advance with key: " << key;
360
359
(node_path += " /" ) += key;
361
- if (!ResolveBlockingDependencies (compiler, node_path)) {
362
- return nullptr ;
363
- }
364
- result = As<ConfigMap>(result)->Get (key);
360
+ node = New<ConfigMapEntryRef>(nullptr , As<ConfigMap>(item), key);
365
361
} else {
366
- result .reset ();
362
+ node .reset ();
367
363
}
368
- if (!result ) {
369
- LOG (INFO ) << " missing node: " << node_path;
364
+ if (!node ) {
365
+ LOG (WARNING ) << " inaccessible node: " << node_path << " / " << key ;
370
366
return nullptr ;
371
367
}
372
368
}
373
- return if_resolved ( compiler, result, node_path);
369
+ return compiler-> ResolveDependencies ( node_path) ? **node : nullptr ;
374
370
}
375
371
376
372
bool ConfigCompiler::blocking (const string& full_path) const {
@@ -399,11 +395,6 @@ static an<ConfigItem> ResolveReference(ConfigCompiler* compiler,
399
395
if (!resource) {
400
396
DLOG (INFO) << " resource not loaded, compiling: " << reference.resource_id ;
401
397
resource = compiler->Compile (reference.resource_id );
402
- // dependency doesn't require full resolution, this allows non conflicting
403
- // mutual references between config files.
404
- // this call is made even if resource is not loaded because plugins can
405
- // edit the empty config data, adding new dependencies.
406
- ResolveBlockingDependencies (compiler, reference.resource_id + " :" );
407
398
if (!resource->loaded ) {
408
399
if (reference.optional ) {
409
400
LOG (INFO) << " optional resource not loaded: " << reference.resource_id ;
@@ -492,12 +483,27 @@ bool ConfigCompiler::Link(an<ConfigResource> target) {
492
483
(plugin_ ? plugin_->ReviewLinkOutput (this , target) : true );
493
484
}
494
485
486
+ static bool HasCircularDependencies (ConfigDependencyGraph* graph,
487
+ const string& path) {
488
+ for (const auto & x : graph->resolve_chain ) {
489
+ if (boost::starts_with (x, path) &&
490
+ (x.length () == path.length () || x[path.length ()] == ' /' ))
491
+ return true ;
492
+ }
493
+ return false ;
494
+ }
495
+
495
496
bool ConfigCompiler::ResolveDependencies (const string& path) {
496
497
DLOG (INFO) << " ResolveDependencies(" << path << " )" ;
497
498
auto found = graph_->deps .find (path);
498
499
if (found == graph_->deps .end ()) {
499
500
return true ;
500
501
}
502
+ if (HasCircularDependencies (graph_.get (), path)) {
503
+ LOG (WARNING) << " circular dependencies detected in " << path;
504
+ return false ;
505
+ }
506
+ graph_->resolve_chain .push_back (path);
501
507
auto & deps = found->second ;
502
508
for (auto iter = deps.begin (); iter != deps.end (); ) {
503
509
if (!(*iter)->Resolve (this )) {
@@ -507,6 +513,7 @@ bool ConfigCompiler::ResolveDependencies(const string& path) {
507
513
LOG (INFO) << " resolved: " << **iter;
508
514
iter = deps.erase (iter);
509
515
}
516
+ graph_->resolve_chain .pop_back ();
510
517
DLOG (INFO) << " all dependencies resolved." ;
511
518
return true ;
512
519
}
0 commit comments