OLD | NEW |
| (Empty) |
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 | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 library analyzer.src.context.cache; | |
6 | |
7 import 'dart:async'; | |
8 import 'dart:collection'; | |
9 | |
10 import 'package:analyzer/src/generated/engine.dart' | |
11 show AnalysisEngine, CacheState, InternalAnalysisContext, RetentionPriority; | |
12 import 'package:analyzer/src/generated/java_engine.dart'; | |
13 import 'package:analyzer/src/generated/source.dart'; | |
14 import 'package:analyzer/src/generated/utilities_collection.dart'; | |
15 import 'package:analyzer/src/generated/utilities_general.dart'; | |
16 import 'package:analyzer/src/task/model.dart'; | |
17 import 'package:analyzer/task/model.dart'; | |
18 | |
19 /** | |
20 * Return `true` if the given [target] is a priority one. | |
21 */ | |
22 typedef bool IsPriorityAnalysisTarget(AnalysisTarget target); | |
23 | |
24 /** | |
25 * An LRU cache of results produced by analysis. | |
26 */ | |
27 class AnalysisCache { | |
28 /** | |
29 * A flag used to control whether trace information should be produced when | |
30 * the content of the cache is modified. | |
31 */ | |
32 static bool _TRACE_CHANGES = false; | |
33 | |
34 /** | |
35 * An array containing the partitions of which this cache is comprised. | |
36 */ | |
37 final List<CachePartition> _partitions; | |
38 | |
39 /** | |
40 * The [StreamController] reporting [InvalidatedResult]s. | |
41 */ | |
42 final StreamController<InvalidatedResult> _onResultInvalidated = | |
43 new StreamController<InvalidatedResult>.broadcast(sync: true); | |
44 | |
45 /** | |
46 * Initialize a newly created cache to have the given [partitions]. The | |
47 * partitions will be searched in the order in which they appear in the array, | |
48 * so the most specific partition (usually an [SdkCachePartition]) should be | |
49 * first and the most general (usually a [UniversalCachePartition]) last. | |
50 */ | |
51 AnalysisCache(this._partitions) { | |
52 for (CachePartition partition in _partitions) { | |
53 partition.onResultInvalidated.listen((InvalidatedResult event) { | |
54 _onResultInvalidated.add(event); | |
55 }); | |
56 } | |
57 } | |
58 | |
59 /** | |
60 * Return the stream that is notified when a value is invalidated. | |
61 */ | |
62 Stream<InvalidatedResult> get onResultInvalidated => | |
63 _onResultInvalidated.stream; | |
64 | |
65 // TODO(brianwilkerson) Implement or delete this. | |
66 // /** | |
67 // * Return information about each of the partitions in this cache. | |
68 // */ | |
69 // List<AnalysisContextStatistics_PartitionData> get partitionData { | |
70 // int count = _partitions.length; | |
71 // List<AnalysisContextStatistics_PartitionData> data = | |
72 // new List<AnalysisContextStatistics_PartitionData>(count); | |
73 // for (int i = 0; i < count; i++) { | |
74 // CachePartition partition = _partitions[i]; | |
75 // data[i] = new AnalysisContextStatisticsImpl_PartitionDataImpl( | |
76 // partition.astSize, | |
77 // partition.map.length); | |
78 // } | |
79 // return data; | |
80 // } | |
81 | |
82 /** | |
83 * Return an iterator returning all of the [Source] targets. | |
84 */ | |
85 Iterable<Source> get sources { | |
86 return _partitions | |
87 .map((CachePartition partition) => partition._sources) | |
88 .expand((Iterable<Source> sources) => sources); | |
89 } | |
90 | |
91 /** | |
92 * Return the entry associated with the given [target]. | |
93 */ | |
94 CacheEntry get(AnalysisTarget target) { | |
95 int count = _partitions.length; | |
96 for (int i = 0; i < count; i++) { | |
97 CachePartition partition = _partitions[i]; | |
98 if (partition.isResponsibleFor(target)) { | |
99 return partition.get(target); | |
100 } | |
101 } | |
102 // | |
103 // We should never get to this point because the last partition should | |
104 // always be a universal partition, except in the case of the SDK context, | |
105 // in which case the target should always be part of the SDK. | |
106 // | |
107 return null; | |
108 } | |
109 | |
110 /** | |
111 * Return the context to which the given [target] was explicitly added. | |
112 */ | |
113 InternalAnalysisContext getContextFor(AnalysisTarget target) { | |
114 int count = _partitions.length; | |
115 for (int i = 0; i < count; i++) { | |
116 CachePartition partition = _partitions[i]; | |
117 if (partition.isResponsibleFor(target)) { | |
118 return partition.context; | |
119 } | |
120 } | |
121 // | |
122 // We should never get to this point because the last partition should | |
123 // always be a universal partition, except in the case of the SDK context, | |
124 // in which case the target should always be part of the SDK. | |
125 // | |
126 // TODO(brianwilkerson) Throw an exception here. | |
127 AnalysisEngine.instance.logger.logInformation( | |
128 'Could not find context for $target', | |
129 new CaughtException(new AnalysisException(), null)); | |
130 return null; | |
131 } | |
132 | |
133 /** | |
134 * Return [Source]s whose full path is equal to the given [path]. | |
135 * Maybe empty, but not `null`. | |
136 */ | |
137 List<Source> getSourcesWithFullName(String path) { | |
138 List<Source> sources = <Source>[]; | |
139 for (CachePartition partition in _partitions) { | |
140 List<Source> partitionSources = partition.getSourcesWithFullName(path); | |
141 sources.addAll(partitionSources); | |
142 } | |
143 return sources; | |
144 } | |
145 | |
146 /** | |
147 * Return the state of the given [result] for the given [target]. | |
148 * | |
149 * It does not update the cache, if the corresponding [CacheEntry] does not | |
150 * exist, then [CacheState.INVALID] is returned. | |
151 */ | |
152 CacheState getState(AnalysisTarget target, ResultDescriptor result) { | |
153 CacheEntry entry = get(target); | |
154 if (entry == null) { | |
155 return CacheState.INVALID; | |
156 } | |
157 return entry.getState(result); | |
158 } | |
159 | |
160 /** | |
161 * Return the value of the given [result] for the given [target]. | |
162 * | |
163 * It does not update the cache, if the corresponding [CacheEntry] does not | |
164 * exist, then the default value is returned. | |
165 */ | |
166 Object getValue(AnalysisTarget target, ResultDescriptor result) { | |
167 CacheEntry entry = get(target); | |
168 if (entry == null) { | |
169 return result.defaultValue; | |
170 } | |
171 return entry.getValue(result); | |
172 } | |
173 | |
174 /** | |
175 * Return an iterator returning all of the map entries mapping targets to | |
176 * cache entries. | |
177 */ | |
178 MapIterator<AnalysisTarget, CacheEntry> iterator() { | |
179 int count = _partitions.length; | |
180 List<Map<AnalysisTarget, CacheEntry>> maps = | |
181 new List<Map<AnalysisTarget, CacheEntry>>(count); | |
182 for (int i = 0; i < count; i++) { | |
183 maps[i] = _partitions[i].map; | |
184 } | |
185 return new MultipleMapIterator<AnalysisTarget, CacheEntry>(maps); | |
186 } | |
187 | |
188 /** | |
189 * Puts the given [entry] into the cache. | |
190 */ | |
191 void put(CacheEntry entry) { | |
192 AnalysisTarget target = entry.target; | |
193 entry.fixExceptionState(); | |
194 int count = _partitions.length; | |
195 for (int i = 0; i < count; i++) { | |
196 CachePartition partition = _partitions[i]; | |
197 if (partition.isResponsibleFor(target)) { | |
198 if (_TRACE_CHANGES) { | |
199 CacheEntry oldEntry = partition.get(target); | |
200 if (oldEntry == null) { | |
201 AnalysisEngine.instance.logger | |
202 .logInformation('Added a cache entry for $target.'); | |
203 } else { | |
204 AnalysisEngine.instance.logger | |
205 .logInformation('Modified the cache entry for $target.'); | |
206 // 'Diff = ${entry.getDiff(oldEntry)}'); | |
207 } | |
208 } | |
209 partition.put(entry); | |
210 return; | |
211 } | |
212 } | |
213 // TODO(brianwilkerson) Handle the case where no partition was found, | |
214 // possibly by throwing an exception. | |
215 } | |
216 | |
217 /** | |
218 * Remove all information related to the given [target] from this cache. | |
219 */ | |
220 void remove(AnalysisTarget target) { | |
221 int count = _partitions.length; | |
222 for (int i = 0; i < count; i++) { | |
223 CachePartition partition = _partitions[i]; | |
224 if (partition.isResponsibleFor(target)) { | |
225 if (_TRACE_CHANGES) { | |
226 AnalysisEngine.instance.logger | |
227 .logInformation('Removed the cache entry for $target.'); | |
228 } | |
229 partition.remove(target); | |
230 return; | |
231 } | |
232 } | |
233 } | |
234 | |
235 /** | |
236 * Return the number of targets that are mapped to cache entries. | |
237 */ | |
238 int size() { | |
239 int size = 0; | |
240 int count = _partitions.length; | |
241 for (int i = 0; i < count; i++) { | |
242 size += _partitions[i].size(); | |
243 } | |
244 return size; | |
245 } | |
246 } | |
247 | |
248 /** | |
249 * The information cached by an analysis context about an individual target. | |
250 */ | |
251 class CacheEntry { | |
252 /** | |
253 * The index of the flag indicating whether the source was explicitly added to | |
254 * the context or whether the source was implicitly added because it was | |
255 * referenced by another source. | |
256 */ | |
257 static int _EXPLICITLY_ADDED_FLAG = 0; | |
258 | |
259 /** | |
260 * The target this entry is about. | |
261 */ | |
262 final AnalysisTarget target; | |
263 | |
264 /** | |
265 * The partition that is responsible for this entry. | |
266 */ | |
267 CachePartition _partition; | |
268 | |
269 /** | |
270 * The most recent time at which the state of the target matched the state | |
271 * represented by this entry, `-1` if the target does not exist. | |
272 */ | |
273 int modificationTime = -1; | |
274 | |
275 /** | |
276 * The exception that caused one or more values to have a state of | |
277 * [CacheState.ERROR]. | |
278 */ | |
279 CaughtException _exception; | |
280 | |
281 /** | |
282 * A bit-encoding of boolean flags associated with this entry's target. | |
283 */ | |
284 int _flags = 0; | |
285 | |
286 /** | |
287 * A table mapping result descriptors to the cached values of those results. | |
288 */ | |
289 Map<ResultDescriptor, ResultData> _resultMap = | |
290 new HashMap<ResultDescriptor, ResultData>(); | |
291 | |
292 CacheEntry(this.target); | |
293 | |
294 /** | |
295 * The exception that caused one or more values to have a state of | |
296 * [CacheState.ERROR]. | |
297 */ | |
298 CaughtException get exception => _exception; | |
299 | |
300 /** | |
301 * Return `true` if the source was explicitly added to the context or `false` | |
302 * if the source was implicitly added because it was referenced by another | |
303 * source. | |
304 */ | |
305 bool get explicitlyAdded => _getFlag(_EXPLICITLY_ADDED_FLAG); | |
306 | |
307 /** | |
308 * Set whether the source was explicitly added to the context to match the | |
309 * [explicitlyAdded] flag. | |
310 */ | |
311 void set explicitlyAdded(bool explicitlyAdded) { | |
312 _setFlag(_EXPLICITLY_ADDED_FLAG, explicitlyAdded); | |
313 } | |
314 | |
315 /** | |
316 * Return a list of result descriptors for results whose state is not | |
317 * [CacheState.INVALID]. | |
318 */ | |
319 List<ResultDescriptor> get nonInvalidResults => _resultMap.keys.toList(); | |
320 | |
321 /** | |
322 * Notifies the entry that the client is going to stop using it. | |
323 */ | |
324 void dispose() { | |
325 _resultMap.forEach((descriptor, data) { | |
326 TargetedResult result = new TargetedResult(target, descriptor); | |
327 for (TargetedResult dependedOnResult in data.dependedOnResults) { | |
328 ResultData dependedOnData = _partition._getDataFor(dependedOnResult); | |
329 if (dependedOnData != null) { | |
330 dependedOnData.dependentResults.remove(result); | |
331 } | |
332 } | |
333 }); | |
334 _resultMap.clear(); | |
335 } | |
336 | |
337 /** | |
338 * Fix the state of the [exception] to match the current state of the entry. | |
339 */ | |
340 void fixExceptionState() { | |
341 if (!hasErrorState()) { | |
342 _exception = null; | |
343 } | |
344 } | |
345 | |
346 /** | |
347 * Look up the [ResultData] of [descriptor], or add a new one if it isn't | |
348 * there. | |
349 */ | |
350 ResultData getResultData(ResultDescriptor descriptor) { | |
351 return _resultMap.putIfAbsent(descriptor, () => new ResultData(descriptor)); | |
352 } | |
353 | |
354 /** | |
355 * Return the state of the result represented by the given [descriptor]. | |
356 */ | |
357 CacheState getState(ResultDescriptor descriptor) { | |
358 ResultData data = _resultMap[descriptor]; | |
359 if (data == null) { | |
360 return CacheState.INVALID; | |
361 } | |
362 return data.state; | |
363 } | |
364 | |
365 /** | |
366 * Return the value of the result represented by the given [descriptor], or | |
367 * the default value for the result if this entry does not have a valid value. | |
368 */ | |
369 /*<V>*/ dynamic /*V*/ getValue(ResultDescriptor /*<V>*/ descriptor) { | |
370 ResultData data = _resultMap[descriptor]; | |
371 if (data == null) { | |
372 return descriptor.defaultValue; | |
373 } | |
374 if (_partition != null) { | |
375 _partition.resultAccessed(target, descriptor); | |
376 } | |
377 return data.value; | |
378 } | |
379 | |
380 /** | |
381 * Return `true` if the state of any data value is [CacheState.ERROR]. | |
382 */ | |
383 bool hasErrorState() { | |
384 for (ResultData data in _resultMap.values) { | |
385 if (data.state == CacheState.ERROR) { | |
386 return true; | |
387 } | |
388 } | |
389 return false; | |
390 } | |
391 | |
392 /** | |
393 * Invalidate all of the information associated with this entry's target. | |
394 */ | |
395 void invalidateAllInformation() { | |
396 _resultMap.clear(); | |
397 _exception = null; | |
398 } | |
399 | |
400 /** | |
401 * Return `true` if the state of the result represented by the given | |
402 * [descriptor] is [CacheState.INVALID]. | |
403 */ | |
404 bool isInvalid(ResultDescriptor descriptor) => | |
405 getState(descriptor) == CacheState.INVALID; | |
406 | |
407 /** | |
408 * Return `true` if the state of the result represented by the given | |
409 * [descriptor] is [CacheState.VALID]. | |
410 */ | |
411 bool isValid(ResultDescriptor descriptor) => | |
412 getState(descriptor) == CacheState.VALID; | |
413 | |
414 /** | |
415 * For each of the given [descriptors], set their states to | |
416 * [CacheState.ERROR], their values to the corresponding default values, and | |
417 * remember the [exception] that caused this state. | |
418 */ | |
419 void setErrorState( | |
420 CaughtException exception, List<ResultDescriptor> descriptors) { | |
421 if (descriptors == null || descriptors.isEmpty) { | |
422 throw new ArgumentError('at least one descriptor is expected'); | |
423 } | |
424 if (exception == null) { | |
425 throw new ArgumentError('an exception is expected'); | |
426 } | |
427 this._exception = exception; | |
428 for (ResultDescriptor descriptor in descriptors) { | |
429 _setErrorState(descriptor, exception); | |
430 } | |
431 } | |
432 | |
433 /** | |
434 * Set the state of the result represented by the given [descriptor] to the | |
435 * given [state]. | |
436 */ | |
437 void setState(ResultDescriptor descriptor, CacheState state, {Delta delta}) { | |
438 if (state == CacheState.ERROR) { | |
439 throw new ArgumentError('use setErrorState() to set the state to ERROR'); | |
440 } | |
441 if (state == CacheState.VALID) { | |
442 throw new ArgumentError('use setValue() to set the state to VALID'); | |
443 } | |
444 _validateStateChange(descriptor, state); | |
445 if (state == CacheState.INVALID) { | |
446 ResultData data = _resultMap[descriptor]; | |
447 if (data != null) { | |
448 _invalidate(descriptor, delta); | |
449 } | |
450 } else { | |
451 ResultData data = getResultData(descriptor); | |
452 data.state = state; | |
453 if (state != CacheState.IN_PROCESS) { | |
454 // | |
455 // If the state is in-process, we can leave the current value in the | |
456 // cache for any 'get' methods to access. | |
457 // | |
458 data.value = descriptor.defaultValue; | |
459 } | |
460 } | |
461 } | |
462 | |
463 /** | |
464 * Set the value of the result represented by the given [descriptor] to the | |
465 * given [value]. | |
466 */ | |
467 /*<V>*/ void setValue(ResultDescriptor /*<V>*/ descriptor, dynamic /*V*/ | |
468 value, List<TargetedResult> dependedOn) { | |
469 // { | |
470 // String valueStr = '$value'; | |
471 // if (valueStr.length > 20) { | |
472 // valueStr = valueStr.substring(0, 20) + '...'; | |
473 // } | |
474 // valueStr = valueStr.replaceAll('\n', '\\n'); | |
475 // print( | |
476 // 'setValue $descriptor for $target value=$valueStr deps=$dependedOn')
; | |
477 // } | |
478 _validateStateChange(descriptor, CacheState.VALID); | |
479 TargetedResult thisResult = new TargetedResult(target, descriptor); | |
480 if (_partition != null) { | |
481 _partition.resultStored(thisResult, value); | |
482 } | |
483 ResultData data = getResultData(descriptor); | |
484 _setDependedOnResults(data, thisResult, dependedOn); | |
485 data.state = CacheState.VALID; | |
486 data.value = value == null ? descriptor.defaultValue : value; | |
487 } | |
488 | |
489 /** | |
490 * Set the value of the result represented by the given [descriptor] to the | |
491 * given [value], keep its dependency, invalidate all the dependent result. | |
492 */ | |
493 void setValueIncremental(ResultDescriptor descriptor, dynamic value) { | |
494 ResultData data = getResultData(descriptor); | |
495 List<TargetedResult> dependedOn = data.dependedOnResults; | |
496 _invalidate(descriptor, null); | |
497 setValue(descriptor, value, dependedOn); | |
498 } | |
499 | |
500 @override | |
501 String toString() { | |
502 StringBuffer buffer = new StringBuffer(); | |
503 _writeOn(buffer); | |
504 return buffer.toString(); | |
505 } | |
506 | |
507 /** | |
508 * Return the value of the flag with the given [index]. | |
509 */ | |
510 bool _getFlag(int index) => BooleanArray.get(_flags, index); | |
511 | |
512 /** | |
513 * Invalidate the result represented by the given [descriptor] and propagate | |
514 * invalidation to other results that depend on it. | |
515 */ | |
516 void _invalidate(ResultDescriptor descriptor, Delta delta) { | |
517 DeltaResult deltaResult = null; | |
518 if (delta != null) { | |
519 deltaResult = delta.validate(_partition.context, target, descriptor); | |
520 if (deltaResult == DeltaResult.STOP) { | |
521 // print('not-invalidate $descriptor for $target'); | |
522 return; | |
523 } | |
524 } | |
525 // print('invalidate $descriptor for $target'); | |
526 ResultData thisData; | |
527 if (deltaResult == null || deltaResult == DeltaResult.INVALIDATE) { | |
528 thisData = _resultMap.remove(descriptor); | |
529 } | |
530 if (deltaResult == DeltaResult.KEEP_CONTINUE) { | |
531 thisData = _resultMap[descriptor]; | |
532 } | |
533 if (thisData == null) { | |
534 return; | |
535 } | |
536 // Stop depending on other results. | |
537 TargetedResult thisResult = new TargetedResult(target, descriptor); | |
538 for (TargetedResult dependedOnResult in thisData.dependedOnResults) { | |
539 ResultData data = _partition._getDataFor(dependedOnResult); | |
540 if (data != null) { | |
541 data.dependentResults.remove(thisResult); | |
542 } | |
543 } | |
544 // Invalidate results that depend on this result. | |
545 List<TargetedResult> dependentResults = thisData.dependentResults.toList(); | |
546 for (TargetedResult dependentResult in dependentResults) { | |
547 CacheEntry entry = _partition.get(dependentResult.target); | |
548 if (entry != null) { | |
549 entry._invalidate(dependentResult.result, delta); | |
550 } | |
551 } | |
552 // If empty, remove the entry altogether. | |
553 if (_resultMap.isEmpty) { | |
554 _partition._targetMap.remove(target); | |
555 _partition._removeIfSource(target); | |
556 } | |
557 // Notify controller. | |
558 _partition._onResultInvalidated | |
559 .add(new InvalidatedResult(this, descriptor)); | |
560 } | |
561 | |
562 /** | |
563 * Invalidates all the results of this entry, with propagation. | |
564 */ | |
565 void _invalidateAll() { | |
566 List<ResultDescriptor> results = _resultMap.keys.toList(); | |
567 for (ResultDescriptor result in results) { | |
568 _invalidate(result, null); | |
569 } | |
570 } | |
571 | |
572 /** | |
573 * Set the [dependedOn] on which this result depends. | |
574 */ | |
575 void _setDependedOnResults(ResultData thisData, TargetedResult thisResult, | |
576 List<TargetedResult> dependedOn) { | |
577 thisData.dependedOnResults.forEach((TargetedResult dependedOnResult) { | |
578 ResultData data = _partition._getDataFor(dependedOnResult); | |
579 if (data != null) { | |
580 data.dependentResults.remove(thisResult); | |
581 } | |
582 }); | |
583 thisData.dependedOnResults = dependedOn; | |
584 thisData.dependedOnResults.forEach((TargetedResult dependedOnResult) { | |
585 ResultData data = _partition._getDataFor(dependedOnResult); | |
586 if (data != null) { | |
587 data.dependentResults.add(thisResult); | |
588 } | |
589 }); | |
590 } | |
591 | |
592 /** | |
593 * Set states of the given and dependent results to [CacheState.ERROR] and | |
594 * their values to the corresponding default values | |
595 */ | |
596 void _setErrorState(ResultDescriptor descriptor, CaughtException exception) { | |
597 ResultData thisData = getResultData(descriptor); | |
598 // Set the error state. | |
599 _exception = exception; | |
600 thisData.state = CacheState.ERROR; | |
601 thisData.value = descriptor.defaultValue; | |
602 // Propagate the error state. | |
603 thisData.dependentResults.forEach((TargetedResult dependentResult) { | |
604 CacheEntry entry = _partition.get(dependentResult.target); | |
605 entry._setErrorState(dependentResult.result, exception); | |
606 }); | |
607 } | |
608 | |
609 /** | |
610 * Set the value of the flag with the given [index] to the given [value]. | |
611 */ | |
612 void _setFlag(int index, bool value) { | |
613 _flags = BooleanArray.set(_flags, index, value); | |
614 } | |
615 | |
616 /** | |
617 * If the state of the value described by the given [descriptor] is changing | |
618 * from ERROR to anything else, capture the information. This is an attempt to | |
619 * discover the underlying cause of a long-standing bug. | |
620 */ | |
621 void _validateStateChange(ResultDescriptor descriptor, CacheState newState) { | |
622 // TODO(brianwilkerson) Decide whether we still want to capture this data. | |
623 // if (descriptor != CONTENT) { | |
624 // return; | |
625 // } | |
626 // ResultData data = resultMap[CONTENT]; | |
627 // if (data != null && data.state == CacheState.ERROR) { | |
628 // String message = | |
629 // 'contentState changing from ${data.state} to $newState'; | |
630 // InstrumentationBuilder builder = | |
631 // Instrumentation.builder2('CacheEntry-validateStateChange'); | |
632 // builder.data3('message', message); | |
633 // //builder.data('source', source.getFullName()); | |
634 // builder.record(new CaughtException(new AnalysisException(message), null)
); | |
635 // builder.log(); | |
636 // } | |
637 } | |
638 | |
639 /** | |
640 * Write a textual representation of this entry to the given [buffer]. The | |
641 * result should only be used for debugging purposes. | |
642 */ | |
643 void _writeOn(StringBuffer buffer) { | |
644 buffer.write('time = '); | |
645 buffer.write(modificationTime); | |
646 List<ResultDescriptor> results = _resultMap.keys.toList(); | |
647 results.sort((ResultDescriptor first, ResultDescriptor second) => | |
648 first.toString().compareTo(second.toString())); | |
649 for (ResultDescriptor result in results) { | |
650 ResultData data = _resultMap[result]; | |
651 buffer.write('; '); | |
652 buffer.write(result.toString()); | |
653 buffer.write(' = '); | |
654 buffer.write(data.state); | |
655 } | |
656 } | |
657 } | |
658 | |
659 /** | |
660 * An object that controls flushing of analysis results from the cache. | |
661 */ | |
662 class CacheFlushManager<T> { | |
663 final IsPriorityAnalysisTarget isPriorityAnalysisTarget; | |
664 final ResultCachingPolicy<T> policy; | |
665 final int maxActiveSize; | |
666 final int maxIdleSize; | |
667 | |
668 /** | |
669 * A map of the stored [TargetedResult] to their sizes. | |
670 */ | |
671 final HashMap<TargetedResult, int> resultSizeMap = | |
672 new HashMap<TargetedResult, int>(); | |
673 | |
674 /** | |
675 * A linked set containing the most recently accessed results with the most | |
676 * recently used at the end of the list. When more results are added than the | |
677 * maximum size allowed then the least recently used results will be flushed | |
678 * from the cache. | |
679 */ | |
680 final LinkedHashSet<TargetedResult> recentlyUsed = | |
681 new LinkedHashSet<TargetedResult>(); | |
682 | |
683 /** | |
684 * The current size of stored results. | |
685 */ | |
686 int currentSize = 0; | |
687 | |
688 /** | |
689 * The current maximum cache size. | |
690 */ | |
691 int maxSize; | |
692 | |
693 CacheFlushManager( | |
694 ResultCachingPolicy<T> policy, this.isPriorityAnalysisTarget) | |
695 : policy = policy, | |
696 maxActiveSize = policy.maxActiveSize, | |
697 maxIdleSize = policy.maxIdleSize, | |
698 maxSize = policy.maxIdleSize; | |
699 | |
700 /** | |
701 * If [currentSize] is already less than [maxSize], returns an empty list. | |
702 * Otherwise returns [TargetedResult]s to flush from the cache to make | |
703 * [currentSize] less or equal to [maxSize]. | |
704 * | |
705 * Results for priority files are never flushed, so this method might leave | |
706 * [currentSize] greater than [maxSize]. | |
707 */ | |
708 List<TargetedResult> flushToSize() { | |
709 // If still under the cap, done. | |
710 if (currentSize <= maxSize) { | |
711 return TargetedResult.EMPTY_LIST; | |
712 } | |
713 // Flush results until we are under the cap. | |
714 List<TargetedResult> resultsToFlush = <TargetedResult>[]; | |
715 for (TargetedResult result in recentlyUsed) { | |
716 if (isPriorityAnalysisTarget(result.target)) { | |
717 continue; | |
718 } | |
719 resultsToFlush.add(result); | |
720 int size = resultSizeMap.remove(result); | |
721 assert(size != null); | |
722 currentSize -= size; | |
723 if (currentSize <= maxSize) { | |
724 break; | |
725 } | |
726 } | |
727 recentlyUsed.removeAll(resultsToFlush); | |
728 return resultsToFlush; | |
729 } | |
730 | |
731 /** | |
732 * Notifies this manager that the corresponding analysis context is active. | |
733 */ | |
734 void madeActive() { | |
735 maxSize = maxActiveSize; | |
736 } | |
737 | |
738 /** | |
739 * Notifies this manager that the corresponding analysis context is idle. | |
740 * Returns [TargetedResult]s that should be flushed from the cache. | |
741 */ | |
742 List<TargetedResult> madeIdle() { | |
743 maxSize = maxIdleSize; | |
744 return flushToSize(); | |
745 } | |
746 | |
747 /** | |
748 * Records that the given [result] was just read from the cache. | |
749 */ | |
750 void resultAccessed(TargetedResult result) { | |
751 if (recentlyUsed.remove(result)) { | |
752 recentlyUsed.add(result); | |
753 } | |
754 } | |
755 | |
756 /** | |
757 * Records that the given [newResult] and [newValue] were stored to the cache. | |
758 * Returns [TargetedResult]s that should be flushed from the cache. | |
759 */ | |
760 List<TargetedResult> resultStored(TargetedResult newResult, T newValue) { | |
761 if (!recentlyUsed.remove(newResult)) { | |
762 int size = policy.measure(newValue); | |
763 resultSizeMap[newResult] = size; | |
764 currentSize += size; | |
765 } | |
766 recentlyUsed.add(newResult); | |
767 return flushToSize(); | |
768 } | |
769 | |
770 /** | |
771 * Records that the given [target] was just removed from to the cache. | |
772 */ | |
773 void targetRemoved(AnalysisTarget target) { | |
774 List<TargetedResult> resultsToRemove = <TargetedResult>[]; | |
775 for (TargetedResult result in recentlyUsed) { | |
776 if (result.target == target) { | |
777 resultsToRemove.add(result); | |
778 int size = resultSizeMap.remove(result); | |
779 assert(size != null); | |
780 currentSize -= size; | |
781 } | |
782 } | |
783 recentlyUsed.removeAll(resultsToRemove); | |
784 } | |
785 } | |
786 | |
787 /** | |
788 * A single partition in an LRU cache of information related to analysis. | |
789 */ | |
790 abstract class CachePartition { | |
791 /** | |
792 * The context that owns this partition. Multiple contexts can reference a | |
793 * partition, but only one context can own it. | |
794 */ | |
795 final InternalAnalysisContext context; | |
796 | |
797 /** | |
798 * A table mapping caching policies to the cache flush managers. | |
799 */ | |
800 final HashMap<ResultCachingPolicy, CacheFlushManager> _flushManagerMap = | |
801 new HashMap<ResultCachingPolicy, CacheFlushManager>(); | |
802 | |
803 /** | |
804 * The [StreamController] reporting [InvalidatedResult]s. | |
805 */ | |
806 final StreamController<InvalidatedResult> _onResultInvalidated = | |
807 new StreamController<InvalidatedResult>.broadcast(sync: true); | |
808 | |
809 /** | |
810 * A table mapping the targets belonging to this partition to the information | |
811 * known about those targets. | |
812 */ | |
813 HashMap<AnalysisTarget, CacheEntry> _targetMap = | |
814 new HashMap<AnalysisTarget, CacheEntry>(); | |
815 | |
816 /** | |
817 * A set of the [Source] targets. | |
818 */ | |
819 final HashSet<Source> _sources = new HashSet<Source>(); | |
820 | |
821 /** | |
822 * A table mapping full paths to lists of [Source]s with these full paths. | |
823 */ | |
824 final Map<String, List<Source>> _pathToSources = <String, List<Source>>{}; | |
825 | |
826 /** | |
827 * Initialize a newly created cache partition, belonging to the given | |
828 * [context]. | |
829 */ | |
830 CachePartition(this.context); | |
831 | |
832 /** | |
833 * Return a table mapping the targets known to the context to the information | |
834 * known about the target. | |
835 * | |
836 * <b>Note:</b> This method is only visible for use by [AnalysisCache] and | |
837 * should not be used for any other purpose. | |
838 */ | |
839 Map<AnalysisTarget, CacheEntry> get map => _targetMap; | |
840 | |
841 /** | |
842 * Return the stream that is notified when a value is invalidated. | |
843 */ | |
844 Stream<InvalidatedResult> get onResultInvalidated => | |
845 _onResultInvalidated.stream; | |
846 | |
847 /** | |
848 * Notifies the partition that the client is going to stop using it. | |
849 */ | |
850 void dispose() { | |
851 for (CacheEntry entry in _targetMap.values) { | |
852 entry.dispose(); | |
853 } | |
854 _targetMap.clear(); | |
855 } | |
856 | |
857 /** | |
858 * Return the entry associated with the given [target]. | |
859 */ | |
860 CacheEntry get(AnalysisTarget target) => _targetMap[target]; | |
861 | |
862 /** | |
863 * Return [Source]s whose full path is equal to the given [path]. | |
864 * Maybe empty, but not `null`. | |
865 */ | |
866 List<Source> getSourcesWithFullName(String path) { | |
867 List<Source> sources = _pathToSources[path]; | |
868 return sources != null ? sources : Source.EMPTY_LIST; | |
869 } | |
870 | |
871 /** | |
872 * Return `true` if this partition is responsible for the given [target]. | |
873 */ | |
874 bool isResponsibleFor(AnalysisTarget target); | |
875 | |
876 /** | |
877 * Return an iterator returning all of the map entries mapping targets to | |
878 * cache entries. | |
879 */ | |
880 MapIterator<AnalysisTarget, CacheEntry> iterator() => | |
881 new SingleMapIterator<AnalysisTarget, CacheEntry>(_targetMap); | |
882 | |
883 /** | |
884 * Puts the given [entry] into the partition. | |
885 */ | |
886 void put(CacheEntry entry) { | |
887 AnalysisTarget target = entry.target; | |
888 if (entry._partition != null) { | |
889 throw new StateError( | |
890 'The entry for $target is already in ${entry._partition}'); | |
891 } | |
892 entry._partition = this; | |
893 entry.fixExceptionState(); | |
894 _targetMap[target] = entry; | |
895 _addIfSource(target); | |
896 } | |
897 | |
898 /** | |
899 * Remove all information related to the given [target] from this cache. | |
900 */ | |
901 void remove(AnalysisTarget target) { | |
902 for (CacheFlushManager flushManager in _flushManagerMap.values) { | |
903 flushManager.targetRemoved(target); | |
904 } | |
905 CacheEntry entry = _targetMap.remove(target); | |
906 if (entry != null) { | |
907 entry._invalidateAll(); | |
908 } | |
909 _removeIfSource(target); | |
910 } | |
911 | |
912 /** | |
913 * Records that a value of the result described by the given [descriptor] | |
914 * for the given [target] was just read from the cache. | |
915 */ | |
916 void resultAccessed(AnalysisTarget target, ResultDescriptor descriptor) { | |
917 CacheFlushManager flushManager = _getFlushManager(descriptor); | |
918 TargetedResult result = new TargetedResult(target, descriptor); | |
919 flushManager.resultAccessed(result); | |
920 } | |
921 | |
922 /** | |
923 * Records that the given [result] was just stored into the cache. | |
924 */ | |
925 void resultStored(TargetedResult result, Object value) { | |
926 CacheFlushManager flushManager = _getFlushManager(result.result); | |
927 List<TargetedResult> resultsToFlush = | |
928 flushManager.resultStored(result, value); | |
929 for (TargetedResult result in resultsToFlush) { | |
930 CacheEntry entry = get(result.target); | |
931 if (entry != null) { | |
932 ResultData data = entry._resultMap[result.result]; | |
933 if (data != null) { | |
934 data.flush(); | |
935 } | |
936 } | |
937 } | |
938 } | |
939 | |
940 /** | |
941 * Return the number of targets that are mapped to cache entries. | |
942 */ | |
943 int size() => _targetMap.length; | |
944 | |
945 /** | |
946 * If the given [target] is a [Source], adds it to [_sources]. | |
947 */ | |
948 void _addIfSource(AnalysisTarget target) { | |
949 if (target is Source) { | |
950 _sources.add(target); | |
951 { | |
952 String fullName = target.fullName; | |
953 _pathToSources.putIfAbsent(fullName, () => <Source>[]).add(target); | |
954 } | |
955 } | |
956 } | |
957 | |
958 ResultData _getDataFor(TargetedResult result) { | |
959 CacheEntry entry = context.analysisCache.get(result.target); | |
960 return entry != null ? entry._resultMap[result.result] : null; | |
961 } | |
962 | |
963 /** | |
964 * Return the [CacheFlushManager] for the given [descriptor], not `null`. | |
965 */ | |
966 CacheFlushManager _getFlushManager(ResultDescriptor descriptor) { | |
967 ResultCachingPolicy policy = descriptor.cachingPolicy; | |
968 if (identical(policy, DEFAULT_CACHING_POLICY)) { | |
969 return UnlimitedCacheFlushManager.INSTANCE; | |
970 } | |
971 CacheFlushManager manager = _flushManagerMap[policy]; | |
972 if (manager == null) { | |
973 manager = new CacheFlushManager(policy, _isPriorityAnalysisTarget); | |
974 _flushManagerMap[policy] = manager; | |
975 } | |
976 return manager; | |
977 } | |
978 | |
979 bool _isPriorityAnalysisTarget(AnalysisTarget target) { | |
980 return context.priorityTargets.contains(target); | |
981 } | |
982 | |
983 /** | |
984 * If the given [target] is a [Source], removes it from [_sources]. | |
985 */ | |
986 void _removeIfSource(AnalysisTarget target) { | |
987 if (target is Source) { | |
988 _sources.remove(target); | |
989 { | |
990 String fullName = target.fullName; | |
991 List<Source> sources = _pathToSources[fullName]; | |
992 if (sources != null) { | |
993 sources.remove(target); | |
994 if (sources.isEmpty) { | |
995 _pathToSources.remove(fullName); | |
996 } | |
997 } | |
998 } | |
999 } | |
1000 } | |
1001 } | |
1002 | |
1003 /** | |
1004 * The description for a change. | |
1005 */ | |
1006 class Delta { | |
1007 final Source source; | |
1008 | |
1009 Delta(this.source); | |
1010 | |
1011 /** | |
1012 * Check whether this delta affects the result described by the given | |
1013 * [descriptor] and [target]. | |
1014 */ | |
1015 DeltaResult validate(InternalAnalysisContext context, AnalysisTarget target, | |
1016 ResultDescriptor descriptor) { | |
1017 return DeltaResult.INVALIDATE; | |
1018 } | |
1019 } | |
1020 | |
1021 /** | |
1022 * The possible results of validating analysis results againt a [Delta]. | |
1023 */ | |
1024 enum DeltaResult { INVALIDATE, KEEP_CONTINUE, STOP } | |
1025 | |
1026 /** | |
1027 * [InvalidatedResult] describes an invalidated result. | |
1028 */ | |
1029 class InvalidatedResult { | |
1030 /** | |
1031 * The target in which the result was invalidated. | |
1032 */ | |
1033 final CacheEntry entry; | |
1034 | |
1035 /** | |
1036 * The descriptor of the result which was invalidated. | |
1037 */ | |
1038 final ResultDescriptor descriptor; | |
1039 | |
1040 InvalidatedResult(this.entry, this.descriptor); | |
1041 | |
1042 @override | |
1043 String toString() => '$descriptor of ${entry.target}'; | |
1044 } | |
1045 | |
1046 /** | |
1047 * The data about a single analysis result that is stored in a [CacheEntry]. | |
1048 */ | |
1049 // TODO(brianwilkerson) Consider making this a generic class so that the value | |
1050 // can be typed. | |
1051 class ResultData { | |
1052 /** | |
1053 * The [ResultDescriptor] this result is for. | |
1054 */ | |
1055 final ResultDescriptor descriptor; | |
1056 | |
1057 /** | |
1058 * The state of the cached value. | |
1059 */ | |
1060 CacheState state; | |
1061 | |
1062 /** | |
1063 * The value being cached, or the default value for the result if there is no | |
1064 * value (for example, when the [state] is [CacheState.INVALID]). | |
1065 */ | |
1066 Object value; | |
1067 | |
1068 /** | |
1069 * A list of the results on which this result depends. | |
1070 */ | |
1071 List<TargetedResult> dependedOnResults = <TargetedResult>[]; | |
1072 | |
1073 /** | |
1074 * A list of the results that depend on this result. | |
1075 */ | |
1076 Set<TargetedResult> dependentResults = new Set<TargetedResult>(); | |
1077 | |
1078 /** | |
1079 * Initialize a newly created result holder to represent the value of data | |
1080 * described by the given [descriptor]. | |
1081 */ | |
1082 ResultData(this.descriptor) { | |
1083 state = CacheState.INVALID; | |
1084 value = descriptor.defaultValue; | |
1085 } | |
1086 | |
1087 /** | |
1088 * Flush this value. | |
1089 */ | |
1090 void flush() { | |
1091 state = CacheState.FLUSHED; | |
1092 value = descriptor.defaultValue; | |
1093 } | |
1094 } | |
1095 | |
1096 /** | |
1097 * A cache partition that contains all of the targets in the SDK. | |
1098 */ | |
1099 class SdkCachePartition extends CachePartition { | |
1100 /** | |
1101 * Initialize a newly created cache partition, belonging to the given | |
1102 * [context]. | |
1103 */ | |
1104 SdkCachePartition(InternalAnalysisContext context) : super(context); | |
1105 | |
1106 @override | |
1107 bool isResponsibleFor(AnalysisTarget target) { | |
1108 if (target is AnalysisContextTarget) { | |
1109 return true; | |
1110 } | |
1111 Source source = target.source; | |
1112 return source != null && source.isInSystemLibrary; | |
1113 } | |
1114 } | |
1115 | |
1116 /** | |
1117 * A specification of a specific result computed for a specific target. | |
1118 */ | |
1119 class TargetedResult { | |
1120 /** | |
1121 * An empty list of results. | |
1122 */ | |
1123 static final List<TargetedResult> EMPTY_LIST = const <TargetedResult>[]; | |
1124 | |
1125 /** | |
1126 * The target with which the result is associated. | |
1127 */ | |
1128 final AnalysisTarget target; | |
1129 | |
1130 /** | |
1131 * The result associated with the target. | |
1132 */ | |
1133 final ResultDescriptor result; | |
1134 | |
1135 /** | |
1136 * Initialize a new targeted result. | |
1137 */ | |
1138 TargetedResult(this.target, this.result); | |
1139 | |
1140 @override | |
1141 int get hashCode { | |
1142 return JenkinsSmiHash.combine(target.hashCode, result.hashCode); | |
1143 } | |
1144 | |
1145 @override | |
1146 bool operator ==(other) { | |
1147 return other is TargetedResult && | |
1148 other.target == target && | |
1149 other.result == result; | |
1150 } | |
1151 | |
1152 @override | |
1153 String toString() => '$result for $target'; | |
1154 } | |
1155 | |
1156 /** | |
1157 * A cache partition that contains all targets not contained in other partitions
. | |
1158 */ | |
1159 class UniversalCachePartition extends CachePartition { | |
1160 /** | |
1161 * Initialize a newly created cache partition, belonging to the given | |
1162 * [context]. | |
1163 */ | |
1164 UniversalCachePartition(InternalAnalysisContext context) : super(context); | |
1165 | |
1166 @override | |
1167 bool isResponsibleFor(AnalysisTarget target) => true; | |
1168 } | |
1169 | |
1170 /** | |
1171 * [CacheFlushManager] that does nothing, results are never flushed. | |
1172 */ | |
1173 class UnlimitedCacheFlushManager extends CacheFlushManager { | |
1174 static final CacheFlushManager INSTANCE = new UnlimitedCacheFlushManager(); | |
1175 | |
1176 UnlimitedCacheFlushManager() : super(DEFAULT_CACHING_POLICY, (_) => false); | |
1177 | |
1178 @override | |
1179 void resultAccessed(TargetedResult result) {} | |
1180 | |
1181 @override | |
1182 List<TargetedResult> resultStored(TargetedResult newResult, newValue) { | |
1183 return TargetedResult.EMPTY_LIST; | |
1184 } | |
1185 | |
1186 @override | |
1187 void targetRemoved(AnalysisTarget target) {} | |
1188 } | |
OLD | NEW |