| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2014, 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 /// Implementation of the smoke services using mirrors. | |
| 6 library smoke.mirrors; | |
| 7 | |
| 8 import 'dart:mirrors'; | |
| 9 import 'package:smoke/smoke.dart'; | |
| 10 import 'package:logging/logging.dart'; | |
| 11 import 'src/common.dart'; | |
| 12 | |
| 13 /// Set up the smoke package to use a mirror-based implementation. To tune what | |
| 14 /// is preserved by `dart:mirrors`, use a @MirrorsUsed annotation and include | |
| 15 /// 'smoke.mirrors' in your override arguments. | |
| 16 useMirrors() { | |
| 17 configure(new ReflectiveObjectAccessorService(), | |
| 18 new ReflectiveTypeInspectorService(), | |
| 19 new ReflectiveSymbolConverterService()); | |
| 20 } | |
| 21 | |
| 22 var _logger = new Logger('smoke.mirrors'); | |
| 23 | |
| 24 /// Implements [ObjectAccessorService] using mirrors. | |
| 25 class ReflectiveObjectAccessorService implements ObjectAccessorService { | |
| 26 read(Object object, Symbol name) => reflect(object).getField(name).reflectee; | |
| 27 | |
| 28 void write(Object object, Symbol name, value) { | |
| 29 reflect(object).setField(name, value); | |
| 30 } | |
| 31 | |
| 32 invoke(receiver, Symbol methodName, List args, | |
| 33 {Map namedArgs, bool adjust: false}) { | |
| 34 var receiverMirror; | |
| 35 var method; | |
| 36 if (receiver is Type && methodName != #toString) { | |
| 37 receiverMirror = reflectType(receiver); | |
| 38 method = receiverMirror.declarations[methodName]; | |
| 39 } else { | |
| 40 receiverMirror = reflect(receiver); | |
| 41 method = _findMethod(receiverMirror.type, methodName); | |
| 42 } | |
| 43 if (method != null && adjust) { | |
| 44 var required = 0; | |
| 45 var optional = 0; | |
| 46 for (var p in method.parameters) { | |
| 47 if (p.isOptional) { | |
| 48 if (!p.isNamed) optional++; | |
| 49 } else { | |
| 50 required++; | |
| 51 } | |
| 52 } | |
| 53 args = adjustList(args, required, required + optional); | |
| 54 } | |
| 55 return receiverMirror.invoke(methodName, args, namedArgs).reflectee; | |
| 56 } | |
| 57 } | |
| 58 | |
| 59 /// Implements [TypeInspectorService] using mirrors. | |
| 60 class ReflectiveTypeInspectorService implements TypeInspectorService { | |
| 61 bool isSubclassOf(Type type, Type supertype) { | |
| 62 if (type == supertype || supertype == Object) return true; | |
| 63 // TODO(sigmund): change to mirror.isSubclassOf when it gets implemented in | |
| 64 // dart2js. (dartbug.com/12439) | |
| 65 var mirror = reflectClass(type); | |
| 66 var top = reflectClass(supertype); | |
| 67 while (mirror != _objectType) { | |
| 68 mirror = _safeSuperclass(mirror); | |
| 69 if (mirror == top) return true; | |
| 70 } | |
| 71 return false; | |
| 72 } | |
| 73 | |
| 74 bool hasGetter(Type type, Symbol name) { | |
| 75 var mirror = reflectType(type); | |
| 76 if (mirror is! ClassMirror) return false; | |
| 77 while (mirror != _objectType) { | |
| 78 final members = mirror.declarations; | |
| 79 if (members.containsKey(name)) return true; | |
| 80 mirror = _safeSuperclass(mirror); | |
| 81 } | |
| 82 return false; | |
| 83 } | |
| 84 | |
| 85 bool hasSetter(Type type, Symbol name) { | |
| 86 var mirror = reflectType(type); | |
| 87 if (mirror is! ClassMirror) return false; | |
| 88 var setterName = _setterName(name); | |
| 89 while (mirror != _objectType) { | |
| 90 final members = mirror.declarations; | |
| 91 var declaration = members[name]; | |
| 92 if (declaration is VariableMirror && !declaration.isFinal) return true; | |
| 93 if (members.containsKey(setterName)) return true; | |
| 94 mirror = _safeSuperclass(mirror); | |
| 95 } | |
| 96 return false; | |
| 97 } | |
| 98 | |
| 99 bool hasInstanceMethod(Type type, Symbol name) { | |
| 100 var mirror = reflectType(type); | |
| 101 if (mirror is! ClassMirror) return false; | |
| 102 while (mirror != _objectType) { | |
| 103 final m = mirror.declarations[name]; | |
| 104 if (m is MethodMirror && m.isRegularMethod && !m.isStatic) return true; | |
| 105 mirror = _safeSuperclass(mirror); | |
| 106 } | |
| 107 return false; | |
| 108 } | |
| 109 | |
| 110 bool hasStaticMethod(Type type, Symbol name) { | |
| 111 var mirror = reflectType(type); | |
| 112 if (mirror is! ClassMirror) return false; | |
| 113 final m = mirror.declarations[name]; | |
| 114 return m is MethodMirror && m.isRegularMethod && m.isStatic; | |
| 115 } | |
| 116 | |
| 117 Declaration getDeclaration(Type type, Symbol name) { | |
| 118 var mirror = reflectType(type); | |
| 119 if (mirror is! ClassMirror) return null; | |
| 120 | |
| 121 var declaration; | |
| 122 while (mirror != _objectType) { | |
| 123 final members = mirror.declarations; | |
| 124 if (members.containsKey(name)) { | |
| 125 declaration = members[name]; | |
| 126 break; | |
| 127 } | |
| 128 mirror = _safeSuperclass(mirror); | |
| 129 } | |
| 130 if (declaration == null) { | |
| 131 _logger.severe("declaration doesn't exists ($type.$name)."); | |
| 132 return null; | |
| 133 } | |
| 134 return new _MirrorDeclaration(mirror, declaration); | |
| 135 } | |
| 136 | |
| 137 List<Declaration> query(Type type, QueryOptions options) { | |
| 138 var mirror = reflectType(type); | |
| 139 if (mirror is! ClassMirror) return null; | |
| 140 return _query(mirror, options); | |
| 141 } | |
| 142 | |
| 143 List<Declaration> _query(ClassMirror cls, QueryOptions options) { | |
| 144 final visitParent = options.includeInherited && cls.superclass != null && | |
| 145 // TODO(sigmund): use _toType(cls.superclass) != options.includeUpTo | |
| 146 // when dartbug.com/16925 gets fixed (_toType fails in dart2js if | |
| 147 // applied to classes with type-arguments). | |
| 148 cls.superclass != reflectClass(options.includeUpTo); | |
| 149 var result = visitParent ? _query(cls.superclass, options) : []; | |
| 150 for (var member in cls.declarations.values) { | |
| 151 if (member is! VariableMirror && member is! MethodMirror) continue; | |
| 152 if (member.isStatic || member.isPrivate) continue; | |
| 153 var name = member.simpleName; | |
| 154 if (member is VariableMirror) { | |
| 155 if (!options.includeFields) continue; | |
| 156 if (options.excludeFinal && member.isFinal) continue; | |
| 157 } | |
| 158 | |
| 159 // TODO(sigmund): what if we have a setter but no getter? | |
| 160 if (member is MethodMirror && member.isSetter) continue; | |
| 161 if (member is MethodMirror && member.isConstructor) continue; | |
| 162 | |
| 163 if (member is MethodMirror && member.isGetter) { | |
| 164 if (!options.includeProperties) continue; | |
| 165 if (options.excludeFinal && !_hasSetter(cls, member)) continue; | |
| 166 } | |
| 167 | |
| 168 if (member is MethodMirror && member.isRegularMethod) { | |
| 169 if (!options.includeMethods) continue; | |
| 170 } | |
| 171 | |
| 172 if (options.matches != null && !options.matches(name)) continue; | |
| 173 | |
| 174 var annotations = member.metadata.map((m) => m.reflectee).toList(); | |
| 175 if (options.withAnnotations != null && | |
| 176 !matchesAnnotation(annotations, options.withAnnotations)) { | |
| 177 continue; | |
| 178 } | |
| 179 | |
| 180 var declaration = new _MirrorDeclaration(cls, member); | |
| 181 | |
| 182 if (options.excludeOverriden) { | |
| 183 result.retainWhere((value) => declaration.name != value.name); | |
| 184 } | |
| 185 | |
| 186 // TODO(sigmund): should we cache parts of this declaration so we don't | |
| 187 // compute them twice? For example, this chould be `new Declaration(name, | |
| 188 // type, ...)` and we could reuse what we computed above to implement the | |
| 189 // query filtering. Note, when I tried to eagerly compute everything, I | |
| 190 // run into trouble with type (`type = _toType(member.type)`), dart2js | |
| 191 // failed when the underlying types had type-arguments (see | |
| 192 // dartbug.com/16925). | |
| 193 result.add(declaration); | |
| 194 } | |
| 195 | |
| 196 return result; | |
| 197 } | |
| 198 } | |
| 199 | |
| 200 /// Implements [SymbolConverterService] using mirrors. | |
| 201 class ReflectiveSymbolConverterService implements SymbolConverterService { | |
| 202 String symbolToName(Symbol symbol) => MirrorSystem.getName(symbol); | |
| 203 Symbol nameToSymbol(String name) => new Symbol(name); | |
| 204 } | |
| 205 | |
| 206 // TODO(jmesserly): workaround for: | |
| 207 // https://code.google.com/p/dart/issues/detail?id=10029 | |
| 208 Symbol _setterName(Symbol getter) => | |
| 209 new Symbol('${MirrorSystem.getName(getter)}='); | |
| 210 | |
| 211 ClassMirror _safeSuperclass(ClassMirror type) { | |
| 212 try { | |
| 213 var t = type.superclass; | |
| 214 // TODO(sigmund): workaround for darbug.com/17779. | |
| 215 // Interceptor is leaked by dart2js. It has the same methods as Object | |
| 216 // (including noSuchMethod), and our code above assumes that it doesn't | |
| 217 // exist. Most queries exclude Object, so they should exclude Interceptor | |
| 218 // too. We don't check for t.simpleName == #Interceptor because depending on | |
| 219 // dart2js optimizations it may be #Interceptor or #num/Interceptor. | |
| 220 // Checking for a private library seems to reliably filter this out. | |
| 221 if (t != null && t.owner != null && t.owner.isPrivate) { | |
| 222 t = _objectType; | |
| 223 } | |
| 224 return t; | |
| 225 } on UnsupportedError catch (e) { | |
| 226 // Note: dart2js throws UnsupportedError when the type is not reflectable. | |
| 227 return _objectType; | |
| 228 } | |
| 229 } | |
| 230 | |
| 231 MethodMirror _findMethod(ClassMirror type, Symbol name) { | |
| 232 do { | |
| 233 var member = type.declarations[name]; | |
| 234 if (member is MethodMirror) return member; | |
| 235 type = type.superclass; | |
| 236 } while (type != null); | |
| 237 return null; | |
| 238 } | |
| 239 | |
| 240 // When recursively looking for symbols up the type-hierarchy it's generally a | |
| 241 // good idea to stop at Object, since we know it doesn't have what we want. | |
| 242 // TODO(jmesserly): This is also a workaround for what appears to be a V8 | |
| 243 // bug introduced between Chrome 31 and 32. After 32 | |
| 244 // JsClassMirror.declarations on Object calls | |
| 245 // JsClassMirror.typeVariables, which tries to get the _jsConstructor's | |
| 246 // .prototype["<>"]. This ends up getting the "" property instead, maybe | |
| 247 // because "<>" doesn't exist, and gets ";" which then blows up because | |
| 248 // the code later on expects a List of ints. | |
| 249 final _objectType = reflectClass(Object); | |
| 250 | |
| 251 bool _hasSetter(ClassMirror cls, MethodMirror getter) { | |
| 252 var mirror = cls.declarations[_setterName(getter.simpleName)]; | |
| 253 return mirror is MethodMirror && mirror.isSetter; | |
| 254 } | |
| 255 | |
| 256 Type _toType(TypeMirror t) { | |
| 257 // TODO(sigmund): this line can go away after dartbug.com/16962 | |
| 258 if (t == _objectType) return Object; | |
| 259 if (t is ClassMirror) return t.reflectedType; | |
| 260 if (t == null || t.qualifiedName != #dynamic) { | |
| 261 _logger.warning('unknown type ($t).'); | |
| 262 } | |
| 263 return dynamic; | |
| 264 } | |
| 265 | |
| 266 class _MirrorDeclaration implements Declaration { | |
| 267 final ClassMirror _cls; | |
| 268 final _original; | |
| 269 | |
| 270 _MirrorDeclaration(this._cls, DeclarationMirror this._original); | |
| 271 | |
| 272 Symbol get name => _original.simpleName; | |
| 273 | |
| 274 DeclarationKind get kind => isField ? FIELD : isProperty ? PROPERTY : METHOD; | |
| 275 | |
| 276 bool get isField => _original is VariableMirror; | |
| 277 | |
| 278 bool get isProperty => | |
| 279 _original is MethodMirror && !_original.isRegularMethod; | |
| 280 | |
| 281 bool get isMethod => !isField && !isProperty; | |
| 282 | |
| 283 /// If this is a property, whether it's read only (final fields or properties | |
| 284 /// with no setter). | |
| 285 bool get isFinal => (_original is VariableMirror && _original.isFinal) || | |
| 286 (_original is MethodMirror && | |
| 287 _original.isGetter && | |
| 288 !_hasSetter(_cls, _original)); | |
| 289 | |
| 290 /// If this is a property, it's declared type (including dynamic if it's not | |
| 291 /// declared). For methods, the returned type. | |
| 292 Type get type { | |
| 293 if (_original is MethodMirror && _original.isRegularMethod) { | |
| 294 return Function; | |
| 295 } | |
| 296 var typeMirror = | |
| 297 _original is VariableMirror ? _original.type : _original.returnType; | |
| 298 return _toType(typeMirror); | |
| 299 } | |
| 300 | |
| 301 /// Whether this symbol is static. | |
| 302 bool get isStatic => _original.isStatic; | |
| 303 | |
| 304 /// List of annotations in this declaration. | |
| 305 List get annotations => _original.metadata.map((a) => a.reflectee).toList(); | |
| 306 | |
| 307 int get hashCode => name.hashCode; | |
| 308 operator ==(other) => other is Declaration && | |
| 309 name == other.name && | |
| 310 kind == other.kind && | |
| 311 isFinal == other.isFinal && | |
| 312 type == other.type && | |
| 313 isStatic == other.isStatic && | |
| 314 compareLists(annotations, other.annotations); | |
| 315 String toString() => (new StringBuffer() | |
| 316 ..write('(mirror-based-declaration ') | |
| 317 ..write(name) | |
| 318 ..write( | |
| 319 isField ? ' (field) ' : (isProperty ? ' (property) ' : ' (method) ')) | |
| 320 ..write(isFinal ? 'final ' : '') | |
| 321 ..write(isStatic ? 'static ' : '') | |
| 322 ..write(annotations) | |
| 323 ..write(')')).toString(); | |
| 324 } | |
| OLD | NEW |