Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(400)

Side by Side Diff: pkg/analyzer/lib/src/task/driver.dart

Issue 1147853002: Properly handle circular references among constants in the task model. (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 library analyzer.src.task.driver; 5 library analyzer.src.task.driver;
6 6
7 import 'dart:async'; 7 import 'dart:async';
8 import 'dart:collection'; 8 import 'dart:collection';
9 9
10 import 'package:analyzer/src/context/cache.dart'; 10 import 'package:analyzer/src/context/cache.dart';
(...skipping 131 matching lines...) Expand 10 before | Expand all | Expand 10 after
142 AnalysisTarget target, ResultDescriptor result) { 142 AnalysisTarget target, ResultDescriptor result) {
143 CacheEntry entry = context.getCacheEntry(target); 143 CacheEntry entry = context.getCacheEntry(target);
144 CacheState state = entry.getState(result); 144 CacheState state = entry.getState(result);
145 if (state == CacheState.VALID || 145 if (state == CacheState.VALID ||
146 state == CacheState.ERROR || 146 state == CacheState.ERROR ||
147 state == CacheState.IN_PROCESS) { 147 state == CacheState.IN_PROCESS) {
148 return null; 148 return null;
149 } 149 }
150 try { 150 try {
151 TaskDescriptor taskDescriptor = taskManager.findTask(target, result); 151 TaskDescriptor taskDescriptor = taskManager.findTask(target, result);
152 WorkItem workItem = new WorkItem(context, target, taskDescriptor); 152 WorkItem workItem = new WorkItem(context, target, taskDescriptor, result);
153 return new WorkOrder(taskManager, workItem); 153 return new WorkOrder(taskManager, workItem);
154 } catch (exception, stackTrace) { 154 } catch (exception, stackTrace) {
155 throw new AnalysisException( 155 throw new AnalysisException(
156 'Could not create work order (target = $target; result = $result)', 156 'Could not create work order (target = $target; result = $result)',
157 new CaughtException(exception, stackTrace)); 157 new CaughtException(exception, stackTrace));
158 } 158 }
159 } 159 }
160 160
161 /** 161 /**
162 * Create a work order that will produce the required analysis results for 162 * Create a work order that will produce the required analysis results for
(...skipping 182 matching lines...) Expand 10 before | Expand all | Expand 10 after
345 345
346 /** 346 /**
347 * Determine the next strongly connected component in the graph, and return 347 * Determine the next strongly connected component in the graph, and return
348 * it. The client is expected to evaluate this component before calling 348 * it. The client is expected to evaluate this component before calling
349 * [getNextStronglyConnectedComponent] again. 349 * [getNextStronglyConnectedComponent] again.
350 */ 350 */
351 List<Node> getNextStronglyConnectedComponent() { 351 List<Node> getNextStronglyConnectedComponent() {
352 while (_currentIndices.isNotEmpty) { 352 while (_currentIndices.isNotEmpty) {
353 Node nextUnevaluatedInput = getNextInput(_path[_currentIndices.last], 353 Node nextUnevaluatedInput = getNextInput(_path[_currentIndices.last],
354 _provisionalDependencies[_currentIndices.last]); 354 _provisionalDependencies[_currentIndices.last]);
355 assert(!_provisionalDependencies[_currentIndices.last]
356 .contains(nextUnevaluatedInput));
355 if (nextUnevaluatedInput != null) { 357 if (nextUnevaluatedInput != null) {
356 // TODO(paulberry): the call to _path.indexOf makes the algorithm 358 // TODO(paulberry): the call to _path.indexOf makes the algorithm
357 // O(n^2) in the depth of the dependency graph. If this becomes a 359 // O(n^2) in the depth of the dependency graph. If this becomes a
358 // problem, consider maintaining a map from node to index. 360 // problem, consider maintaining a map from node to index.
359 int previousIndex = _path.indexOf(nextUnevaluatedInput); 361 int previousIndex = _path.indexOf(nextUnevaluatedInput);
360 if (previousIndex != -1) { 362 if (previousIndex != -1) {
361 // Update contractedPath to indicate that all nodes in the path 363 // Update contractedPath to indicate that all nodes in the path
362 // between previousIndex and currentIndex are part of the same 364 // between previousIndex and currentIndex are part of the same
363 // strongly connected component. 365 // strongly connected component.
364 while (_contractedPath.last > previousIndex) { 366 while (_contractedPath.last > previousIndex) {
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after
421 CacheEntry getCacheEntry(AnalysisTarget target); 423 CacheEntry getCacheEntry(AnalysisTarget target);
422 } 424 }
423 425
424 /** 426 /**
425 * An exception indicating that an attempt was made to perform a task on a 427 * An exception indicating that an attempt was made to perform a task on a
426 * target while gathering the inputs to perform the same task for the same 428 * target while gathering the inputs to perform the same task for the same
427 * target. 429 * target.
428 */ 430 */
429 class InfiniteTaskLoopException extends AnalysisException { 431 class InfiniteTaskLoopException extends AnalysisException {
430 /** 432 /**
431 * Initialize a newly created exception to represent an attempt to perform 433 * If a dependency cycle was found while computing the inputs for the task,
432 * the task for the target represented by the given [item]. 434 * the set of [WorkItem]s contained in the cycle (if there are overlapping
435 * cycles, this is the set of all [WorkItem]s in the entire strongly
436 * connected component). Otherwise, `null`.
433 */ 437 */
434 InfiniteTaskLoopException(WorkItem item) : super( 438 final List<WorkItem> dependencyCycle;
435 'Infinite loop while performing task ${item.descriptor.name} for ${ite m.target}'); 439
440 /**
441 * Initialize a newly created exception to represent a failed attempt to
442 * perform the given [task] due to the given [dependencyCycle].
443 */
444 InfiniteTaskLoopException(AnalysisTask task, this.dependencyCycle) : super(
445 'Infinite loop while performing task ${task.descriptor.name} for ${tas k.target}');
436 } 446 }
437 447
438 /** 448 /**
439 * A description of a single anaysis task that can be performed to advance 449 * A description of a single anaysis task that can be performed to advance
440 * analysis. 450 * analysis.
441 */ 451 */
442 class WorkItem { 452 class WorkItem {
443 /** 453 /**
444 * The context in which the task will be performed. 454 * The context in which the task will be performed.
445 */ 455 */
446 final InternalAnalysisContext context; 456 final InternalAnalysisContext context;
447 457
448 /** 458 /**
449 * The target for which a task is to be performed. 459 * The target for which a task is to be performed.
450 */ 460 */
451 final AnalysisTarget target; 461 final AnalysisTarget target;
452 462
453 /** 463 /**
454 * A description of the task to be performed. 464 * A description of the task to be performed.
455 */ 465 */
456 final TaskDescriptor descriptor; 466 final TaskDescriptor descriptor;
457 467
458 /** 468 /**
469 * The [ResultDescriptor] which was led to this work item being spawned.
470 */
471 final ResultDescriptor spawningResult;
472
473 /**
459 * An iterator used to iterate over the descriptors of the inputs to the task, 474 * An iterator used to iterate over the descriptors of the inputs to the task,
460 * or `null` if all of the inputs have been collected and the task can be 475 * or `null` if all of the inputs have been collected and the task can be
461 * created. 476 * created.
462 */ 477 */
463 TaskInputBuilder builder; 478 TaskInputBuilder builder;
464 479
465 /** 480 /**
466 * The [TargetedResult]s outputs of this task depends on. 481 * The [TargetedResult]s outputs of this task depends on.
467 */ 482 */
468 final HashSet<TargetedResult> inputTargetedResults = 483 final HashSet<TargetedResult> inputTargetedResults =
469 new HashSet<TargetedResult>(); 484 new HashSet<TargetedResult>();
470 485
471 /** 486 /**
472 * The inputs to the task that have been computed. 487 * The inputs to the task that have been computed.
473 */ 488 */
474 Map<String, dynamic> inputs; 489 Map<String, dynamic> inputs;
475 490
476 /** 491 /**
477 * The exception that was found while trying to populate the inputs. If this 492 * The exception that was found while trying to populate the inputs. If this
478 * field is non-`null`, then the task cannot be performed and all of the 493 * field is non-`null`, then the task cannot be performed and all of the
479 * results that this task would have computed need to be marked as being in 494 * results that this task would have computed need to be marked as being in
480 * ERROR with this exception. 495 * ERROR with this exception.
481 */ 496 */
482 CaughtException exception = null; 497 CaughtException exception = null;
483 498
484 /** 499 /**
500 * If a dependency cycle was found while computing the inputs for the task,
501 * the set of [WorkItem]s contained in the cycle (if there are overlapping
502 * cycles, this is the set of all [WorkItem]s in the entire strongly
503 * connected component). Otherwise, `null`.
504 */
505 List<WorkItem> dependencyCycle;
506
507 /**
485 * Initialize a newly created work item to compute the inputs for the task 508 * Initialize a newly created work item to compute the inputs for the task
486 * described by the given descriptor. 509 * described by the given descriptor.
487 */ 510 */
488 WorkItem(this.context, this.target, this.descriptor) { 511 WorkItem(this.context, this.target, this.descriptor, this.spawningResult) {
489 AnalysisTarget actualTarget = identical( 512 AnalysisTarget actualTarget = identical(
490 target, AnalysisContextTarget.request) 513 target, AnalysisContextTarget.request)
491 ? new AnalysisContextTarget(context) 514 ? new AnalysisContextTarget(context)
492 : target; 515 : target;
493 Map<String, TaskInput> inputDescriptors = 516 Map<String, TaskInput> inputDescriptors =
494 descriptor.createTaskInputs(actualTarget); 517 descriptor.createTaskInputs(actualTarget);
495 builder = new TopLevelTaskInputBuilder(inputDescriptors); 518 builder = new TopLevelTaskInputBuilder(inputDescriptors);
496 if (!builder.moveNext()) { 519 if (!builder.moveNext()) {
497 builder = null; 520 builder = null;
498 } 521 }
(...skipping 13 matching lines...) Expand all
512 } 535 }
513 } 536 }
514 537
515 /** 538 /**
516 * Build the task represented by this work item. 539 * Build the task represented by this work item.
517 */ 540 */
518 AnalysisTask buildTask() { 541 AnalysisTask buildTask() {
519 if (builder != null) { 542 if (builder != null) {
520 throw new StateError("some inputs have not been computed"); 543 throw new StateError("some inputs have not been computed");
521 } 544 }
522 return descriptor.createTask(context, target, inputs); 545 AnalysisTask task = descriptor.createTask(context, target, inputs);
546 task.dependencyCycle = dependencyCycle;
547 return task;
523 } 548 }
524 549
525 /** 550 /**
526 * Gather all of the inputs needed to perform the task. 551 * Gather all of the inputs needed to perform the task.
527 * 552 *
528 * If at least one of the inputs have not yet been computed, return a work 553 * If at least one of the inputs have not yet been computed, return a work
529 * item that can be used to generate that input to indicate that the caller 554 * item that can be used to generate that input to indicate that the caller
530 * should perform the returned item's task before returning to gathering 555 * should perform the returned item's task before returning to gathering
531 * inputs for this item's task. 556 * inputs for this item's task.
532 * 557 *
533 * If all of the inputs have been gathered, return `null` to indicate that the 558 * If all of the inputs have been gathered, return `null` to indicate that the
534 * client should build and perform the task. A value of `null` will also be 559 * client should build and perform the task. A value of `null` will also be
535 * returned if some of the inputs cannot be computed and the task cannot be 560 * returned if some of the inputs cannot be computed and the task cannot be
536 * performed. Callers can differentiate between these cases by checking the 561 * performed. Callers can differentiate between these cases by checking the
537 * [exception] field. If the field is `null`, then the task can be performed; 562 * [exception] field. If the field is `null`, then the task can be performed;
538 * if the field is non-`null` then the task cannot be performed and all of the 563 * if the field is non-`null` then the task cannot be performed and all of the
539 * tasks' results should be marked as being in ERROR. 564 * tasks' results should be marked as being in ERROR.
540 */ 565 */
541 WorkItem gatherInputs(TaskManager taskManager) { 566 WorkItem gatherInputs(TaskManager taskManager, List<WorkItem> skipInputs) {
542 while (builder != null) { 567 while (builder != null) {
543 AnalysisTarget inputTarget = builder.currentTarget; 568 AnalysisTarget inputTarget = builder.currentTarget;
544 ResultDescriptor inputResult = builder.currentResult; 569 ResultDescriptor inputResult = builder.currentResult;
545 inputTargetedResults.add(new TargetedResult(inputTarget, inputResult)); 570 inputTargetedResults.add(new TargetedResult(inputTarget, inputResult));
546 CacheEntry inputEntry = context.getCacheEntry(inputTarget); 571 CacheEntry inputEntry = context.getCacheEntry(inputTarget);
547 CacheState inputState = inputEntry.getState(inputResult); 572 CacheState inputState = inputEntry.getState(inputResult);
548 if (inputState == CacheState.ERROR) { 573 if (skipInputs.any((WorkItem item) =>
574 item.target == inputTarget && item.spawningResult == inputResult)) {
575 // This input is being skipped due to a circular dependency. Tell the
576 // builder that it's not available so we can move on to other inputs.
577 builder.currentValueNotAvailable();
578 } else if (inputState == CacheState.ERROR) {
549 exception = inputEntry.exception; 579 exception = inputEntry.exception;
550 return null; 580 return null;
551 } else if (inputState == CacheState.IN_PROCESS) { 581 } else if (inputState == CacheState.IN_PROCESS) {
552 // 582 //
553 // TODO(brianwilkerson) Implement this case. 583 // TODO(brianwilkerson) Implement this case.
554 // 584 //
555 // One possibility would be to return a WorkItem that would perform a 585 // One possibility would be to return a WorkItem that would perform a
556 // no-op task in order to cause us to come back to this work item on the 586 // no-op task in order to cause us to come back to this work item on the
557 // next iteration. It would be more efficient, in general, to push this 587 // next iteration. It would be more efficient, in general, to push this
558 // input onto a waiting list and proceed to the next input so that work 588 // input onto a waiting list and proceed to the next input so that work
559 // could proceed, but given that the only result that can currently be 589 // could proceed, but given that the only result that can currently be
560 // IN_PROCESS is CONTENT, I don't know that it's worth the extra effort 590 // IN_PROCESS is CONTENT, I don't know that it's worth the extra effort
561 // to implement the general solution at this point. 591 // to implement the general solution at this point.
562 // 592 //
593 throw new UnimplementedError();
563 } else if (inputState != CacheState.VALID) { 594 } else if (inputState != CacheState.VALID) {
564 try { 595 try {
565 TaskDescriptor descriptor = 596 TaskDescriptor descriptor =
566 taskManager.findTask(inputTarget, inputResult); 597 taskManager.findTask(inputTarget, inputResult);
567 return new WorkItem(context, inputTarget, descriptor); 598 return new WorkItem(context, inputTarget, descriptor, inputResult);
568 } on AnalysisException catch (exception, stackTrace) { 599 } on AnalysisException catch (exception, stackTrace) {
569 this.exception = new CaughtException(exception, stackTrace); 600 this.exception = new CaughtException(exception, stackTrace);
570 return null; 601 return null;
571 } 602 }
603 } else {
604 builder.currentValue = inputEntry.getValue(inputResult);
572 } 605 }
573 builder.currentValue = inputEntry.getValue(inputResult);
574 if (!builder.moveNext()) { 606 if (!builder.moveNext()) {
575 inputs = builder.inputValue; 607 inputs = builder.inputValue;
576 builder = null; 608 builder = null;
577 } 609 }
578 } 610 }
579 return null; 611 return null;
580 } 612 }
581 613
582 @override 614 @override
583 String toString() => 'Run $descriptor on $target'; 615 String toString() => 'Run $descriptor on $target';
584 } 616 }
585 617
586 /** 618 /**
587 * The priorities of work orders returned by [WorkManager]s.
588 */
589 enum WorkOrderPriority {
590 /**
591 * Responding to an user's action.
592 */
593 INTERACTIVE,
594
595 /**
596 * Computing information for priority sources.
597 */
598 PRIORITY,
599
600 /**
601 * A work should be done, but without any special urgency.
602 */
603 NORMAL,
604
605 /**
606 * Nothing to do.
607 */
608 NONE
609 }
610
611 /**
612 * [AnalysisDriver] uses [WorkManager]s to select results to compute. 619 * [AnalysisDriver] uses [WorkManager]s to select results to compute.
613 * 620 *
614 * They know specific of the targets and results they care about, 621 * They know specific of the targets and results they care about,
615 * so they can request analysis results in optimal order. 622 * so they can request analysis results in optimal order.
616 */ 623 */
617 abstract class WorkManager { 624 abstract class WorkManager {
618 /** 625 /**
619 * Notifies the managers that the given set of priority [targets] was set. 626 * Notifies the managers that the given set of priority [targets] was set.
620 */ 627 */
621 void applyPriorityTargets(List<AnalysisTarget> targets); 628 void applyPriorityTargets(List<AnalysisTarget> targets);
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after
688 return true; 695 return true;
689 } else { 696 } else {
690 // Get a new strongly connected component. 697 // Get a new strongly connected component.
691 currentItems = _dependencyWalker.getNextStronglyConnectedComponent(); 698 currentItems = _dependencyWalker.getNextStronglyConnectedComponent();
692 if (currentItems == null) { 699 if (currentItems == null) {
693 return false; 700 return false;
694 } 701 }
695 if (currentItems.length > 1) { 702 if (currentItems.length > 1) {
696 // A cycle has been found. 703 // A cycle has been found.
697 for (WorkItem item in currentItems) { 704 for (WorkItem item in currentItems) {
698 try { 705 item.dependencyCycle = currentItems.toList();
699 throw new InfiniteTaskLoopException(item);
700 } on InfiniteTaskLoopException catch (exception, stackTrace) {
701 item.exception = new CaughtException(exception, stackTrace);
702 }
703 } 706 }
704 } else { 707 } else {
705 assert(currentItems.length == 1); 708 assert(currentItems.length == 1);
706 } 709 }
707 return true; 710 return true;
708 } 711 }
709 } 712 }
710 } 713 }
711 714
712 /** 715 /**
716 * The priorities of work orders returned by [WorkManager]s.
717 */
718 enum WorkOrderPriority {
719 /**
720 * Responding to an user's action.
721 */
722 INTERACTIVE,
723
724 /**
725 * Computing information for priority sources.
726 */
727 PRIORITY,
728
729 /**
730 * A work should be done, but without any special urgency.
731 */
732 NORMAL,
733
734 /**
735 * Nothing to do.
736 */
737 NONE
738 }
739
740 /**
713 * Specilaization of [CycleAwareDependencyWalker] for use by [WorkOrder]. 741 * Specilaization of [CycleAwareDependencyWalker] for use by [WorkOrder].
714 */ 742 */
715 class _WorkOrderDependencyWalker extends CycleAwareDependencyWalker<WorkItem> { 743 class _WorkOrderDependencyWalker extends CycleAwareDependencyWalker<WorkItem> {
716 /** 744 /**
717 * The task manager used to build work items. 745 * The task manager used to build work items.
718 */ 746 */
719 final TaskManager taskManager; 747 final TaskManager taskManager;
720 748
721 _WorkOrderDependencyWalker(this.taskManager, WorkItem startingNode) 749 _WorkOrderDependencyWalker(this.taskManager, WorkItem startingNode)
722 : super(startingNode); 750 : super(startingNode);
723 751
724 @override 752 @override
725 WorkItem getNextInput(WorkItem node, List<WorkItem> skipInputs) { 753 WorkItem getNextInput(WorkItem node, List<WorkItem> skipInputs) {
726 if (skipInputs.isNotEmpty) { 754 return node.gatherInputs(taskManager, skipInputs);
727 // TODO(paulberry): this is a hack. We assume that an analysis loop has
728 // been found, so we don't try to compute anything else.
729 return null;
730 }
731 return node.gatherInputs(taskManager);
732 } 755 }
733 } 756 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698