| 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;
|
| + }
|
| + }
|
| +}
|
|
|