OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2013, 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 /// Defines rules and a chacker to determine if MirrorsUsed was used correctly. |
| 6 library checked_mirrors.src.checker; |
| 7 |
| 8 import "dart:mirrors"; |
| 9 import "dart:async"; |
| 10 |
| 11 import 'package:checked_mirrors/control.dart'; |
| 12 import 'package:logging/logging.dart'; |
| 13 |
| 14 import 'utils.dart'; |
| 15 |
| 16 /// Controller used to log warnings detected by this checker. |
| 17 var _warnings = new StreamController<MirrorsUsedWarning>.broadcast(sync: true); |
| 18 var _logger = new Logger('checked_mirrors'); |
| 19 |
| 20 /// Stream of warnings detected by this checker. |
| 21 Stream<MirrorsUsedWarning> get onWarning => _warnings.stream; |
| 22 |
| 23 /// A singleton checker used by the checked_mirrors system. |
| 24 MirrorsUsedChecker checker = new MirrorsUsedChecker(); |
| 25 |
| 26 /// Symbol used to search for the MirrorUsed annotation. One dartbug.com/10360 |
| 27 /// is fixed, we can just put the annotation directly in the checked_mirrors |
| 28 /// import. |
| 29 const _MIRROR_USED_LOCATION = #checked_mirrors_workaround_for_issue_10360; |
| 30 |
| 31 |
| 32 /// A warning generated by the checked_mirrors system. |
| 33 class MirrorsUsedWarning { |
| 34 |
| 35 /// The kind of warning (undeclared symbol, target not marked). |
| 36 final MirrorsUsedWarningKind kind; |
| 37 |
| 38 /// The target of the mirror system that was not marked correctly. Or the |
| 39 /// object containing the target (if symbol is not null). |
| 40 final object; |
| 41 |
| 42 final symbol; |
| 43 |
| 44 MirrorsUsedWarning(this.kind, this.object, this.symbol); |
| 45 } |
| 46 |
| 47 /// Enumeration of warning kinds. |
| 48 class MirrorsUsedWarningKind { |
| 49 final int _value; |
| 50 const MirrorsUsedWarningKind(this._value); |
| 51 |
| 52 /// Warning when reading a symbol that was not declared via |
| 53 /// @MirrorUsed(symbols: ...) |
| 54 static const UNDECLARED_SYMBOL = const MirrorsUsedWarningKind(1); |
| 55 |
| 56 /// Warning when accesing a target that was not declared via |
| 57 /// @MirrorUsed(targets: ...) or @MirrorUsed(metaTargets: ...) |
| 58 static const TARGET_NOT_MARKED = const MirrorsUsedWarningKind(2); |
| 59 } |
| 60 |
| 61 /// Contains rules to check when mirrors are used, but they are not annotated |
| 62 /// appropriately. |
| 63 class MirrorsUsedChecker { |
| 64 bool isInitialized = false; |
| 65 bool _throwOnWarning = false; |
| 66 |
| 67 Set symbols = new Set(); |
| 68 List globalRules = []; |
| 69 |
| 70 Map<Uri, List> libraryRules = new Map<Uri, List>(); |
| 71 |
| 72 void init({throwOnWarning}) { |
| 73 if (isInitialized) return; |
| 74 _throwOnWarning = throwOnWarning; |
| 75 var system = currentMirrorSystem(); |
| 76 system.libraries.forEach((uri, lib) { |
| 77 if (uri.scheme == 'dart') return; |
| 78 // TODO(sigmund): this should lookup the import instead. dartbug.com/10360 |
| 79 var target = lib.declarations[_MIRROR_USED_LOCATION]; |
| 80 if (target == null) return; |
| 81 for (var metaMirror in target.metadata) { |
| 82 var meta = metaMirror.reflectee; |
| 83 if (meta is MirrorsUsed) { |
| 84 addRule(uri, meta); |
| 85 } |
| 86 } |
| 87 }); |
| 88 isInitialized = true; |
| 89 } |
| 90 |
| 91 /// Add a [rule] that was seen in [location]. |
| 92 void addRule(Uri location, MirrorsUsed annotation) { |
| 93 |
| 94 // Symbols are handled separately since they are always global. |
| 95 _loadSymbols(annotation.symbols); |
| 96 |
| 97 // Direct target rules to specific libraries, if that's how they are |
| 98 // declared. |
| 99 var rule = new _Rule(annotation); |
| 100 if (annotation.override == '*') { |
| 101 globalRules.add(rule); |
| 102 return; |
| 103 } |
| 104 |
| 105 if (annotation.override == null) { |
| 106 _addToLibrary(location, rule); |
| 107 return; |
| 108 } |
| 109 |
| 110 for (var name in annotation.override.split(',')) { |
| 111 var symbol = MirrorSystem.getSymbol(name); |
| 112 for (var lib in currentMirrorSystem().libraries.values) { |
| 113 if (lib.simpleName == symbol) _addToLibrary(lib.uri, rule); |
| 114 } |
| 115 } |
| 116 } |
| 117 |
| 118 void _loadSymbols(value) { |
| 119 if (value == null) return; |
| 120 if (value is String) { |
| 121 for (var s in value.split(',')) { |
| 122 symbols.add(MirrorSystem.getSymbol(s)); |
| 123 } |
| 124 } else if (value is Symbol) { |
| 125 symbols.add(value); |
| 126 } else if (value is List) { |
| 127 for (var s in value) _loadSymbols(s); |
| 128 } else { |
| 129 _logger.warning('MirrorsUsed symbol not understood: $value'); |
| 130 } |
| 131 } |
| 132 |
| 133 |
| 134 void _addToLibrary(uri, rule) { |
| 135 var rules = libraryRules[uri]; |
| 136 if (rules == null) { |
| 137 rules = []; |
| 138 libraryRules[uri] = rules; |
| 139 } |
| 140 rules.add(rule); |
| 141 } |
| 142 |
| 143 void useSymbol(Symbol symbol) { |
| 144 if (!isInitialized || symbols.contains(symbol)) return; |
| 145 var warning = new MirrorsUsedWarning( |
| 146 MirrorsUsedWarningKind.UNDECLARED_SYMBOL, null, symbol); |
| 147 _warnings.add(warning); |
| 148 if (_throwOnWarning) throw warning; |
| 149 } |
| 150 |
| 151 void access(Mirror object, Symbol symbol) { |
| 152 if (!isInitialized) return; |
| 153 |
| 154 for (var rule in globalRules) { |
| 155 if (rule.access(object, symbol)) return; |
| 156 } |
| 157 |
| 158 var uri = getLibraryUriOf(object); |
| 159 var rules = libraryRules[uri]; |
| 160 if (rules != null) { |
| 161 for (var rule in rules) { |
| 162 if (rule.access(object, symbol)) return; |
| 163 } |
| 164 } |
| 165 |
| 166 // No rule determined that it's ok to access [symbol]. Report an error. |
| 167 var warning = new MirrorsUsedWarning( |
| 168 MirrorsUsedWarningKind.TARGET_NOT_MARKED, object, symbol); |
| 169 _warnings.add(warning); |
| 170 if (_throwOnWarning) throw warning; |
| 171 } |
| 172 } |
| 173 |
| 174 /// Contains a single rule, which is derived from a [MirrorsUsed] annotation. |
| 175 /// The information here is very similar to that of [MirrorsUsed] except it's |
| 176 /// normalized to make it easy to use and apply in the checker logic. |
| 177 class _Rule { |
| 178 List<Mirror> targets = []; |
| 179 List<Type> metaTargets = []; |
| 180 |
| 181 _Rule(MirrorsUsed annotation) { |
| 182 _loadTargets(annotation.targets); |
| 183 _loadMetaTargets(annotation.metaTargets); |
| 184 } |
| 185 |
| 186 void _loadTargets(value) { |
| 187 if (value == null) return; |
| 188 if (value is Type) { |
| 189 targets.add(value); |
| 190 } else if (value is List) { |
| 191 for (var t in value) _loadTargets(t); |
| 192 } else { |
| 193 _logger.warning('MirrorsUsed target not understood: $value'); |
| 194 } |
| 195 } |
| 196 void _loadMetaTargets(value) { |
| 197 if (value == null) return; |
| 198 if (value is Type) { |
| 199 metaTargets.add(value); |
| 200 } else if (value is List) { |
| 201 for (var t in value) _loadMetaTargets(t); |
| 202 } else { |
| 203 _logger.warning('MirrorsUsed metaTarget not understood: $value'); |
| 204 } |
| 205 } |
| 206 |
| 207 bool access(object, Symbol symbol) { |
| 208 // Explicitly declared targets/types |
| 209 var declaration = getDeclarationOf(object); |
| 210 if (declaration == null) return false; |
| 211 if (targets.contains(declaration.reflectedType)) return true; |
| 212 |
| 213 // Meta-targets on classes |
| 214 for (var meta in declaration.metadata) { |
| 215 var type = meta.type.reflectedType; |
| 216 if (metaTargets.contains(type)) return true; |
| 217 } |
| 218 |
| 219 if (symbol == null) return false; |
| 220 |
| 221 // Meta-targets on fields |
| 222 var symbolDeclaration = declaration.declarations[symbol]; |
| 223 if (symbolDeclaration == null) return false; |
| 224 for (var meta in symbolDeclaration.metadata) { |
| 225 var type = meta.type.reflectedType; |
| 226 if (metaTargets.contains(type)) return true; |
| 227 } |
| 228 } |
| 229 } |
OLD | NEW |