Index: pkg/checked_mirrors/lib/src/checker.dart |
diff --git a/pkg/checked_mirrors/lib/src/checker.dart b/pkg/checked_mirrors/lib/src/checker.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..885949d52ae64ba74a81333129c440a13f1ff4b8 |
--- /dev/null |
+++ b/pkg/checked_mirrors/lib/src/checker.dart |
@@ -0,0 +1,229 @@ |
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
+// for details. All rights reserved. Use of this source code is governed by a |
+// BSD-style license that can be found in the LICENSE file. |
+ |
+/// Defines rules and a chacker to determine if MirrorsUsed was used correctly. |
+library checked_mirrors.src.checker; |
+ |
+import "dart:mirrors"; |
+import "dart:async"; |
+ |
+import 'package:checked_mirrors/control.dart'; |
+import 'package:logging/logging.dart'; |
+ |
+import 'utils.dart'; |
+ |
+/// Controller used to log warnings detected by this checker. |
+var _warnings = new StreamController<MirrorsUsedWarning>.broadcast(sync: true); |
+var _logger = new Logger('checked_mirrors'); |
+ |
+/// Stream of warnings detected by this checker. |
+Stream<MirrorsUsedWarning> get onWarning => _warnings.stream; |
+ |
+/// A singleton checker used by the checked_mirrors system. |
+MirrorsUsedChecker checker = new MirrorsUsedChecker(); |
+ |
+/// Symbol used to search for the MirrorUsed annotation. One dartbug.com/10360 |
+/// is fixed, we can just put the annotation directly in the checked_mirrors |
+/// import. |
+const _MIRROR_USED_LOCATION = #checked_mirrors_workaround_for_issue_10360; |
+ |
+ |
+/// A warning generated by the checked_mirrors system. |
+class MirrorsUsedWarning { |
+ |
+ /// The kind of warning (undeclared symbol, target not marked). |
+ final MirrorsUsedWarningKind kind; |
+ |
+ /// The target of the mirror system that was not marked correctly. Or the |
+ /// object containing the target (if symbol is not null). |
+ final object; |
+ |
+ final symbol; |
+ |
+ MirrorsUsedWarning(this.kind, this.object, this.symbol); |
+} |
+ |
+/// Enumeration of warning kinds. |
+class MirrorsUsedWarningKind { |
+ final int _value; |
+ const MirrorsUsedWarningKind(this._value); |
+ |
+ /// Warning when reading a symbol that was not declared via |
+ /// @MirrorUsed(symbols: ...) |
+ static const UNDECLARED_SYMBOL = const MirrorsUsedWarningKind(1); |
+ |
+ /// Warning when accesing a target that was not declared via |
+ /// @MirrorUsed(targets: ...) or @MirrorUsed(metaTargets: ...) |
+ static const TARGET_NOT_MARKED = const MirrorsUsedWarningKind(2); |
+} |
+ |
+/// Contains rules to check when mirrors are used, but they are not annotated |
+/// appropriately. |
+class MirrorsUsedChecker { |
+ bool isInitialized = false; |
+ bool _throwOnWarning = false; |
+ |
+ Set symbols = new Set(); |
+ List globalRules = []; |
+ |
+ Map<Uri, List> libraryRules = new Map<Uri, List>(); |
+ |
+ void init({throwOnWarning}) { |
+ if (isInitialized) return; |
+ _throwOnWarning = throwOnWarning; |
+ var system = currentMirrorSystem(); |
+ system.libraries.forEach((uri, lib) { |
+ if (uri.scheme == 'dart') return; |
+ // TODO(sigmund): this should lookup the import instead. dartbug.com/10360 |
+ var target = lib.declarations[_MIRROR_USED_LOCATION]; |
+ if (target == null) return; |
+ for (var metaMirror in target.metadata) { |
+ var meta = metaMirror.reflectee; |
+ if (meta is MirrorsUsed) { |
+ addRule(uri, meta); |
+ } |
+ } |
+ }); |
+ isInitialized = true; |
+ } |
+ |
+ /// Add a [rule] that was seen in [location]. |
+ void addRule(Uri location, MirrorsUsed annotation) { |
+ |
+ // Symbols are handled separately since they are always global. |
+ _loadSymbols(annotation.symbols); |
+ |
+ // Direct target rules to specific libraries, if that's how they are |
+ // declared. |
+ var rule = new _Rule(annotation); |
+ if (annotation.override == '*') { |
+ globalRules.add(rule); |
+ return; |
+ } |
+ |
+ if (annotation.override == null) { |
+ _addToLibrary(location, rule); |
+ return; |
+ } |
+ |
+ for (var name in annotation.override.split(',')) { |
+ var symbol = MirrorSystem.getSymbol(name); |
+ for (var lib in currentMirrorSystem().libraries.values) { |
+ if (lib.simpleName == symbol) _addToLibrary(lib.uri, rule); |
+ } |
+ } |
+ } |
+ |
+ void _loadSymbols(value) { |
+ if (value == null) return; |
+ if (value is String) { |
+ for (var s in value.split(',')) { |
+ symbols.add(MirrorSystem.getSymbol(s)); |
+ } |
+ } else if (value is Symbol) { |
+ symbols.add(value); |
+ } else if (value is List) { |
+ for (var s in value) _loadSymbols(s); |
+ } else { |
+ _logger.warning('MirrorsUsed symbol not understood: $value'); |
+ } |
+ } |
+ |
+ |
+ void _addToLibrary(uri, rule) { |
+ var rules = libraryRules[uri]; |
+ if (rules == null) { |
+ rules = []; |
+ libraryRules[uri] = rules; |
+ } |
+ rules.add(rule); |
+ } |
+ |
+ void useSymbol(Symbol symbol) { |
+ if (!isInitialized || symbols.contains(symbol)) return; |
+ var warning = new MirrorsUsedWarning( |
+ MirrorsUsedWarningKind.UNDECLARED_SYMBOL, null, symbol); |
+ _warnings.add(warning); |
+ if (_throwOnWarning) throw warning; |
+ } |
+ |
+ void access(Mirror object, Symbol symbol) { |
+ if (!isInitialized) return; |
+ |
+ for (var rule in globalRules) { |
+ if (rule.access(object, symbol)) return; |
+ } |
+ |
+ var uri = getLibraryUriOf(object); |
+ var rules = libraryRules[uri]; |
+ if (rules != null) { |
+ for (var rule in rules) { |
+ if (rule.access(object, symbol)) return; |
+ } |
+ } |
+ |
+ // No rule determined that it's ok to access [symbol]. Report an error. |
+ var warning = new MirrorsUsedWarning( |
+ MirrorsUsedWarningKind.TARGET_NOT_MARKED, object, symbol); |
+ _warnings.add(warning); |
+ if (_throwOnWarning) throw warning; |
+ } |
+} |
+ |
+/// Contains a single rule, which is derived from a [MirrorsUsed] annotation. |
+/// The information here is very similar to that of [MirrorsUsed] except it's |
+/// normalized to make it easy to use and apply in the checker logic. |
+class _Rule { |
+ List<Mirror> targets = []; |
+ List<Type> metaTargets = []; |
+ |
+ _Rule(MirrorsUsed annotation) { |
+ _loadTargets(annotation.targets); |
+ _loadMetaTargets(annotation.metaTargets); |
+ } |
+ |
+ void _loadTargets(value) { |
+ if (value == null) return; |
+ if (value is Type) { |
+ targets.add(value); |
+ } else if (value is List) { |
+ for (var t in value) _loadTargets(t); |
+ } else { |
+ _logger.warning('MirrorsUsed target not understood: $value'); |
+ } |
+ } |
+ void _loadMetaTargets(value) { |
+ if (value == null) return; |
+ if (value is Type) { |
+ metaTargets.add(value); |
+ } else if (value is List) { |
+ for (var t in value) _loadMetaTargets(t); |
+ } else { |
+ _logger.warning('MirrorsUsed metaTarget not understood: $value'); |
+ } |
+ } |
+ |
+ bool access(object, Symbol symbol) { |
+ // Explicitly declared targets/types |
+ var declaration = getDeclarationOf(object); |
+ if (declaration == null) return false; |
+ if (targets.contains(declaration.reflectedType)) return true; |
+ |
+ // Meta-targets on classes |
+ for (var meta in declaration.metadata) { |
+ var type = meta.type.reflectedType; |
+ if (metaTargets.contains(type)) return true; |
+ } |
+ |
+ if (symbol == null) return false; |
+ |
+ // Meta-targets on fields |
+ var symbolDeclaration = declaration.declarations[symbol]; |
+ if (symbolDeclaration == null) return false; |
+ for (var meta in symbolDeclaration.metadata) { |
+ var type = meta.type.reflectedType; |
+ if (metaTargets.contains(type)) return true; |
+ } |
+ } |
+} |