Line data Source code
1 : /*
2 : * Copyright (C) 2005 The Android Open Source Project
3 : *
4 : * Licensed under the Apache License, Version 2.0 (the "License");
5 : * you may not use this file except in compliance with the License.
6 : * You may obtain a copy of the License at
7 : *
8 : * http://www.apache.org/licenses/LICENSE-2.0
9 : *
10 : * Unless required by applicable law or agreed to in writing, software
11 : * distributed under the License is distributed on an "AS IS" BASIS,
12 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 : * See the License for the specific language governing permissions and
14 : * limitations under the License.
15 : */
16 :
17 : #define LOG_TAG "RefBase"
18 : // #define LOG_NDEBUG 0
19 :
20 : #include <utils/RefBase.h>
21 :
22 : #include <utils/Atomic.h>
23 : #ifdef _MSC_VER
24 : class CallStack {
25 : public:
26 : CallStack(int x) {}
27 : };
28 : #else
29 : #include <utils/CallStack.h>
30 : #endif
31 : #include <utils/Log.h>
32 : #include <utils/threads.h>
33 :
34 : #include <stdlib.h>
35 : #include <stdio.h>
36 : #include <typeinfo>
37 : #include <sys/types.h>
38 : #include <sys/stat.h>
39 : #include <fcntl.h>
40 : #include <unistd.h>
41 :
42 : // compile with refcounting debugging enabled
43 : #define DEBUG_REFS 0
44 :
45 : // whether ref-tracking is enabled by default, if not, trackMe(true, false)
46 : // needs to be called explicitly
47 : #define DEBUG_REFS_ENABLED_BY_DEFAULT 0
48 :
49 : // whether callstack are collected (significantly slows things down)
50 : #define DEBUG_REFS_CALLSTACK_ENABLED 0
51 :
52 : // folder where stack traces are saved when DEBUG_REFS is enabled
53 : // this folder needs to exist and be writable
54 : #define DEBUG_REFS_CALLSTACK_PATH "/data/debug"
55 :
56 : // log all reference counting operations
57 : #define PRINT_REFS 0
58 :
59 : // ---------------------------------------------------------------------------
60 :
61 : namespace stagefright {
62 :
63 : #define INITIAL_STRONG_VALUE (1<<28)
64 :
65 : // ---------------------------------------------------------------------------
66 :
67 : class RefBase::weakref_impl : public RefBase::weakref_type
68 : {
69 : public:
70 : volatile int32_t mStrong;
71 : volatile int32_t mWeak;
72 : RefBase* const mBase;
73 : volatile int32_t mFlags;
74 :
75 : #if !DEBUG_REFS
76 :
77 0 : weakref_impl(RefBase* base)
78 0 : : mStrong(INITIAL_STRONG_VALUE)
79 : , mWeak(0)
80 : , mBase(base)
81 0 : , mFlags(0)
82 : {
83 0 : }
84 :
85 0 : void addStrongRef(const void* /*id*/) { }
86 0 : void removeStrongRef(const void* /*id*/) { }
87 0 : void renameStrongRefId(const void* /*old_id*/, const void* /*new_id*/) { }
88 0 : void addWeakRef(const void* /*id*/) { }
89 0 : void removeWeakRef(const void* /*id*/) { }
90 0 : void renameWeakRefId(const void* /*old_id*/, const void* /*new_id*/) { }
91 0 : void printRefs() const { }
92 0 : void trackMe(bool, bool) { }
93 :
94 : #else
95 :
96 : weakref_impl(RefBase* base)
97 : : mStrong(INITIAL_STRONG_VALUE)
98 : , mWeak(0)
99 : , mBase(base)
100 : , mFlags(0)
101 : , mStrongRefs(NULL)
102 : , mWeakRefs(NULL)
103 : , mTrackEnabled(!!DEBUG_REFS_ENABLED_BY_DEFAULT)
104 : , mRetain(false)
105 : {
106 : }
107 :
108 : ~weakref_impl()
109 : {
110 : bool dumpStack = false;
111 : if (!mRetain && mStrongRefs != NULL) {
112 : dumpStack = true;
113 : ALOGE("Strong references remain:");
114 : ref_entry* refs = mStrongRefs;
115 : while (refs) {
116 : char inc = refs->ref >= 0 ? '+' : '-';
117 : ALOGD("\t%c ID %p (ref %d):", inc, refs->id, refs->ref);
118 : #if DEBUG_REFS_CALLSTACK_ENABLED
119 : refs->stack.dump(LOG_TAG);
120 : #endif
121 : refs = refs->next;
122 : }
123 : }
124 :
125 : if (!mRetain && mWeakRefs != NULL) {
126 : dumpStack = true;
127 : ALOGE("Weak references remain!");
128 : ref_entry* refs = mWeakRefs;
129 : while (refs) {
130 : char inc = refs->ref >= 0 ? '+' : '-';
131 : ALOGD("\t%c ID %p (ref %d):", inc, refs->id, refs->ref);
132 : #if DEBUG_REFS_CALLSTACK_ENABLED
133 : refs->stack.dump(LOG_TAG);
134 : #endif
135 : refs = refs->next;
136 : }
137 : }
138 : if (dumpStack) {
139 : ALOGE("above errors at:");
140 : CallStack stack(LOG_TAG);
141 : }
142 : }
143 :
144 : void addStrongRef(const void* id) {
145 : //ALOGD_IF(mTrackEnabled,
146 : // "addStrongRef: RefBase=%p, id=%p", mBase, id);
147 : addRef(&mStrongRefs, id, mStrong);
148 : }
149 :
150 : void removeStrongRef(const void* id) {
151 : //ALOGD_IF(mTrackEnabled,
152 : // "removeStrongRef: RefBase=%p, id=%p", mBase, id);
153 : if (!mRetain) {
154 : removeRef(&mStrongRefs, id);
155 : } else {
156 : addRef(&mStrongRefs, id, -mStrong);
157 : }
158 : }
159 :
160 : void renameStrongRefId(const void* old_id, const void* new_id) {
161 : //ALOGD_IF(mTrackEnabled,
162 : // "renameStrongRefId: RefBase=%p, oid=%p, nid=%p",
163 : // mBase, old_id, new_id);
164 : renameRefsId(mStrongRefs, old_id, new_id);
165 : }
166 :
167 : void addWeakRef(const void* id) {
168 : addRef(&mWeakRefs, id, mWeak);
169 : }
170 :
171 : void removeWeakRef(const void* id) {
172 : if (!mRetain) {
173 : removeRef(&mWeakRefs, id);
174 : } else {
175 : addRef(&mWeakRefs, id, -mWeak);
176 : }
177 : }
178 :
179 : void renameWeakRefId(const void* old_id, const void* new_id) {
180 : renameRefsId(mWeakRefs, old_id, new_id);
181 : }
182 :
183 : void trackMe(bool track, bool retain)
184 : {
185 : mTrackEnabled = track;
186 : mRetain = retain;
187 : }
188 :
189 : void printRefs() const
190 : {
191 : String8 text;
192 :
193 : {
194 : Mutex::Autolock _l(mMutex);
195 : char buf[128];
196 : sprintf(buf, "Strong references on RefBase %p (weakref_type %p):\n", mBase, this);
197 : text.append(buf);
198 : printRefsLocked(&text, mStrongRefs);
199 : sprintf(buf, "Weak references on RefBase %p (weakref_type %p):\n", mBase, this);
200 : text.append(buf);
201 : printRefsLocked(&text, mWeakRefs);
202 : }
203 :
204 : {
205 : char name[100];
206 : snprintf(name, 100, DEBUG_REFS_CALLSTACK_PATH "/%p.stack", this);
207 : int rc = open(name, O_RDWR | O_CREAT | O_APPEND, 644);
208 : if (rc >= 0) {
209 : write(rc, text.string(), text.length());
210 : close(rc);
211 : ALOGD("STACK TRACE for %p saved in %s", this, name);
212 : }
213 : else ALOGE("FAILED TO PRINT STACK TRACE for %p in %s: %s", this,
214 : name, strerror(errno));
215 : }
216 : }
217 :
218 : private:
219 : struct ref_entry
220 : {
221 : ref_entry* next;
222 : const void* id;
223 : #if DEBUG_REFS_CALLSTACK_ENABLED
224 : CallStack stack;
225 : #endif
226 : int32_t ref;
227 : };
228 :
229 : void addRef(ref_entry** refs, const void* id, int32_t mRef)
230 : {
231 : if (mTrackEnabled) {
232 : AutoMutex _l(mMutex);
233 :
234 : ref_entry* ref = new ref_entry;
235 : // Reference count at the time of the snapshot, but before the
236 : // update. Positive value means we increment, negative--we
237 : // decrement the reference count.
238 : ref->ref = mRef;
239 : ref->id = id;
240 : #if DEBUG_REFS_CALLSTACK_ENABLED
241 : ref->stack.update(2);
242 : #endif
243 : ref->next = *refs;
244 : *refs = ref;
245 : }
246 : }
247 :
248 : void removeRef(ref_entry** refs, const void* id)
249 : {
250 : if (mTrackEnabled) {
251 : AutoMutex _l(mMutex);
252 :
253 : ref_entry* const head = *refs;
254 : ref_entry* ref = head;
255 : while (ref != NULL) {
256 : if (ref->id == id) {
257 : *refs = ref->next;
258 : delete ref;
259 : return;
260 : }
261 : refs = &ref->next;
262 : ref = *refs;
263 : }
264 :
265 : ALOGE("RefBase: removing id %p on RefBase %p"
266 : "(weakref_type %p) that doesn't exist!",
267 : id, mBase, this);
268 :
269 : ref = head;
270 : while (ref) {
271 : char inc = ref->ref >= 0 ? '+' : '-';
272 : ALOGD("\t%c ID %p (ref %d):", inc, ref->id, ref->ref);
273 : ref = ref->next;
274 : }
275 :
276 : CallStack stack(LOG_TAG);
277 : }
278 : }
279 :
280 : void renameRefsId(ref_entry* r, const void* old_id, const void* new_id)
281 : {
282 : if (mTrackEnabled) {
283 : AutoMutex _l(mMutex);
284 : ref_entry* ref = r;
285 : while (ref != NULL) {
286 : if (ref->id == old_id) {
287 : ref->id = new_id;
288 : }
289 : ref = ref->next;
290 : }
291 : }
292 : }
293 :
294 : void printRefsLocked(String8* out, const ref_entry* refs) const
295 : {
296 : char buf[128];
297 : while (refs) {
298 : char inc = refs->ref >= 0 ? '+' : '-';
299 : sprintf(buf, "\t%c ID %p (ref %d):\n",
300 : inc, refs->id, refs->ref);
301 : out->append(buf);
302 : #if DEBUG_REFS_CALLSTACK_ENABLED
303 : out->append(refs->stack.toString("\t\t"));
304 : #else
305 : out->append("\t\t(call stacks disabled)");
306 : #endif
307 : refs = refs->next;
308 : }
309 : }
310 :
311 : mutable Mutex mMutex;
312 : ref_entry* mStrongRefs;
313 : ref_entry* mWeakRefs;
314 :
315 : bool mTrackEnabled;
316 : // Collect stack traces on addref and removeref, instead of deleting the stack references
317 : // on removeref that match the address ones.
318 : bool mRetain;
319 :
320 : #endif
321 : };
322 :
323 : // ---------------------------------------------------------------------------
324 :
325 0 : void RefBase::incStrong(const void* id) const
326 : {
327 0 : weakref_impl* const refs = mRefs;
328 0 : refs->incWeak(id);
329 :
330 0 : refs->addStrongRef(id);
331 0 : const int32_t c = android_atomic_inc(&refs->mStrong);
332 : ALOG_ASSERT(c > 0, "incStrong() called on %p after last strong ref", refs);
333 : #if PRINT_REFS
334 : ALOGD("incStrong of %p from %p: cnt=%d\n", this, id, c);
335 : #endif
336 0 : if (c != INITIAL_STRONG_VALUE) {
337 0 : return;
338 : }
339 :
340 0 : android_atomic_add(-INITIAL_STRONG_VALUE, &refs->mStrong);
341 0 : refs->mBase->onFirstRef();
342 : }
343 :
344 0 : void RefBase::decStrong(const void* id) const
345 : {
346 0 : weakref_impl* const refs = mRefs;
347 0 : refs->removeStrongRef(id);
348 0 : const int32_t c = android_atomic_dec(&refs->mStrong);
349 : #if PRINT_REFS
350 : ALOGD("decStrong of %p from %p: cnt=%d\n", this, id, c);
351 : #endif
352 : ALOG_ASSERT(c >= 1, "decStrong() called on %p too many times", refs);
353 0 : if (c == 1) {
354 0 : refs->mBase->onLastStrongRef(id);
355 0 : if ((refs->mFlags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_STRONG) {
356 0 : delete this;
357 : }
358 : }
359 0 : refs->decWeak(id);
360 0 : }
361 :
362 0 : void RefBase::forceIncStrong(const void* id) const
363 : {
364 0 : weakref_impl* const refs = mRefs;
365 0 : refs->incWeak(id);
366 :
367 0 : refs->addStrongRef(id);
368 0 : const int32_t c = android_atomic_inc(&refs->mStrong);
369 : ALOG_ASSERT(c >= 0, "forceIncStrong called on %p after ref count underflow",
370 : refs);
371 : #if PRINT_REFS
372 : ALOGD("forceIncStrong of %p from %p: cnt=%d\n", this, id, c);
373 : #endif
374 :
375 0 : switch (c) {
376 : case INITIAL_STRONG_VALUE:
377 0 : android_atomic_add(-INITIAL_STRONG_VALUE, &refs->mStrong);
378 : // fall through...
379 : case 0:
380 0 : refs->mBase->onFirstRef();
381 : }
382 0 : }
383 :
384 0 : int32_t RefBase::getStrongCount() const
385 : {
386 0 : return mRefs->mStrong;
387 : }
388 :
389 0 : RefBase* RefBase::weakref_type::refBase() const
390 : {
391 0 : return static_cast<const weakref_impl*>(this)->mBase;
392 : }
393 :
394 0 : void RefBase::weakref_type::incWeak(const void* id)
395 : {
396 0 : weakref_impl* const impl = static_cast<weakref_impl*>(this);
397 0 : impl->addWeakRef(id);
398 0 : const int32_t c = android_atomic_inc(&impl->mWeak);
399 : ALOG_ASSERT(c >= 0, "incWeak called on %p after last weak ref", this);
400 0 : }
401 :
402 :
403 0 : void RefBase::weakref_type::decWeak(const void* id)
404 : {
405 0 : weakref_impl* const impl = static_cast<weakref_impl*>(this);
406 0 : impl->removeWeakRef(id);
407 0 : const int32_t c = android_atomic_dec(&impl->mWeak);
408 : ALOG_ASSERT(c >= 1, "decWeak called on %p too many times", this);
409 0 : if (c != 1) return;
410 :
411 0 : if ((impl->mFlags&OBJECT_LIFETIME_WEAK) == OBJECT_LIFETIME_STRONG) {
412 : // This is the regular lifetime case. The object is destroyed
413 : // when the last strong reference goes away. Since weakref_impl
414 : // outlive the object, it is not destroyed in the dtor, and
415 : // we'll have to do it here.
416 0 : if (impl->mStrong == INITIAL_STRONG_VALUE) {
417 : // Special case: we never had a strong reference, so we need to
418 : // destroy the object now.
419 0 : delete impl->mBase;
420 : } else {
421 : // ALOGV("Freeing refs %p of old RefBase %p\n", this, impl->mBase);
422 : delete impl;
423 : }
424 : } else {
425 : // less common case: lifetime is OBJECT_LIFETIME_{WEAK|FOREVER}
426 0 : impl->mBase->onLastWeakRef(id);
427 0 : if ((impl->mFlags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_WEAK) {
428 : // this is the OBJECT_LIFETIME_WEAK case. The last weak-reference
429 : // is gone, we can destroy the object.
430 0 : delete impl->mBase;
431 : }
432 : }
433 : }
434 :
435 0 : bool RefBase::weakref_type::attemptIncStrong(const void* id)
436 : {
437 0 : incWeak(id);
438 :
439 0 : weakref_impl* const impl = static_cast<weakref_impl*>(this);
440 0 : int32_t curCount = impl->mStrong;
441 :
442 : ALOG_ASSERT(curCount >= 0,
443 : "attemptIncStrong called on %p after underflow", this);
444 :
445 0 : while (curCount > 0 && curCount != INITIAL_STRONG_VALUE) {
446 : // we're in the easy/common case of promoting a weak-reference
447 : // from an existing strong reference.
448 0 : if (android_atomic_cmpxchg(curCount, curCount+1, &impl->mStrong) == 0) {
449 0 : break;
450 : }
451 : // the strong count has changed on us, we need to re-assert our
452 : // situation.
453 0 : curCount = impl->mStrong;
454 : }
455 :
456 0 : if (curCount <= 0 || curCount == INITIAL_STRONG_VALUE) {
457 : // we're now in the harder case of either:
458 : // - there never was a strong reference on us
459 : // - or, all strong references have been released
460 0 : if ((impl->mFlags&OBJECT_LIFETIME_WEAK) == OBJECT_LIFETIME_STRONG) {
461 : // this object has a "normal" life-time, i.e.: it gets destroyed
462 : // when the last strong reference goes away
463 0 : if (curCount <= 0) {
464 : // the last strong-reference got released, the object cannot
465 : // be revived.
466 0 : decWeak(id);
467 0 : return false;
468 : }
469 :
470 : // here, curCount == INITIAL_STRONG_VALUE, which means
471 : // there never was a strong-reference, so we can try to
472 : // promote this object; we need to do that atomically.
473 0 : while (curCount > 0) {
474 0 : if (android_atomic_cmpxchg(curCount, curCount + 1,
475 : &impl->mStrong) == 0) {
476 0 : break;
477 : }
478 : // the strong count has changed on us, we need to re-assert our
479 : // situation (e.g.: another thread has inc/decStrong'ed us)
480 0 : curCount = impl->mStrong;
481 : }
482 :
483 0 : if (curCount <= 0) {
484 : // promote() failed, some other thread destroyed us in the
485 : // meantime (i.e.: strong count reached zero).
486 0 : decWeak(id);
487 0 : return false;
488 : }
489 : } else {
490 : // this object has an "extended" life-time, i.e.: it can be
491 : // revived from a weak-reference only.
492 : // Ask the object's implementation if it agrees to be revived
493 0 : if (!impl->mBase->onIncStrongAttempted(FIRST_INC_STRONG, id)) {
494 : // it didn't so give-up.
495 0 : decWeak(id);
496 0 : return false;
497 : }
498 : // grab a strong-reference, which is always safe due to the
499 : // extended life-time.
500 0 : curCount = android_atomic_inc(&impl->mStrong);
501 : }
502 :
503 : // If the strong reference count has already been incremented by
504 : // someone else, the implementor of onIncStrongAttempted() is holding
505 : // an unneeded reference. So call onLastStrongRef() here to remove it.
506 : // (No, this is not pretty.) Note that we MUST NOT do this if we
507 : // are in fact acquiring the first reference.
508 0 : if (curCount > 0 && curCount < INITIAL_STRONG_VALUE) {
509 0 : impl->mBase->onLastStrongRef(id);
510 : }
511 : }
512 :
513 0 : impl->addStrongRef(id);
514 :
515 : #if PRINT_REFS
516 : ALOGD("attemptIncStrong of %p from %p: cnt=%d\n", this, id, curCount);
517 : #endif
518 :
519 : // now we need to fix-up the count if it was INITIAL_STRONG_VALUE
520 : // this must be done safely, i.e.: handle the case where several threads
521 : // were here in attemptIncStrong().
522 0 : curCount = impl->mStrong;
523 0 : while (curCount >= INITIAL_STRONG_VALUE) {
524 : ALOG_ASSERT(curCount > INITIAL_STRONG_VALUE,
525 : "attemptIncStrong in %p underflowed to INITIAL_STRONG_VALUE",
526 : this);
527 0 : if (android_atomic_cmpxchg(curCount, curCount-INITIAL_STRONG_VALUE,
528 : &impl->mStrong) == 0) {
529 0 : break;
530 : }
531 : // the strong-count changed on us, we need to re-assert the situation,
532 : // for e.g.: it's possible the fix-up happened in another thread.
533 0 : curCount = impl->mStrong;
534 : }
535 :
536 0 : return true;
537 : }
538 :
539 0 : bool RefBase::weakref_type::attemptIncWeak(const void* id)
540 : {
541 0 : weakref_impl* const impl = static_cast<weakref_impl*>(this);
542 :
543 0 : int32_t curCount = impl->mWeak;
544 : ALOG_ASSERT(curCount >= 0, "attemptIncWeak called on %p after underflow",
545 : this);
546 0 : while (curCount > 0) {
547 0 : if (android_atomic_cmpxchg(curCount, curCount+1, &impl->mWeak) == 0) {
548 0 : break;
549 : }
550 0 : curCount = impl->mWeak;
551 : }
552 :
553 0 : if (curCount > 0) {
554 0 : impl->addWeakRef(id);
555 : }
556 :
557 0 : return curCount > 0;
558 : }
559 :
560 0 : int32_t RefBase::weakref_type::getWeakCount() const
561 : {
562 0 : return static_cast<const weakref_impl*>(this)->mWeak;
563 : }
564 :
565 0 : void RefBase::weakref_type::printRefs() const
566 : {
567 0 : static_cast<const weakref_impl*>(this)->printRefs();
568 0 : }
569 :
570 0 : void RefBase::weakref_type::trackMe(bool enable, bool retain)
571 : {
572 0 : static_cast<weakref_impl*>(this)->trackMe(enable, retain);
573 0 : }
574 :
575 0 : RefBase::weakref_type* RefBase::createWeak(const void* id) const
576 : {
577 0 : mRefs->incWeak(id);
578 0 : return mRefs;
579 : }
580 :
581 0 : RefBase::weakref_type* RefBase::getWeakRefs() const
582 : {
583 0 : return mRefs;
584 : }
585 :
586 0 : RefBase::RefBase()
587 0 : : mRefs(new weakref_impl(this))
588 : {
589 0 : }
590 :
591 0 : RefBase::~RefBase()
592 : {
593 0 : if (mRefs->mStrong == INITIAL_STRONG_VALUE) {
594 : // we never acquired a strong (and/or weak) reference on this object.
595 0 : delete mRefs;
596 : } else {
597 : // life-time of this object is extended to WEAK or FOREVER, in
598 : // which case weakref_impl doesn't out-live the object and we
599 : // can free it now.
600 0 : if ((mRefs->mFlags & OBJECT_LIFETIME_MASK) != OBJECT_LIFETIME_STRONG) {
601 : // It's possible that the weak count is not 0 if the object
602 : // re-acquired a weak reference in its destructor
603 0 : if (mRefs->mWeak == 0) {
604 0 : delete mRefs;
605 : }
606 : }
607 : }
608 : // for debugging purposes, clear this.
609 0 : const_cast<weakref_impl*&>(mRefs) = NULL;
610 0 : }
611 :
612 0 : void RefBase::extendObjectLifetime(int32_t mode)
613 : {
614 0 : android_atomic_or(mode, &mRefs->mFlags);
615 0 : }
616 :
617 0 : void RefBase::onFirstRef()
618 : {
619 0 : }
620 :
621 0 : void RefBase::onLastStrongRef(const void* /*id*/)
622 : {
623 0 : }
624 :
625 0 : bool RefBase::onIncStrongAttempted(uint32_t flags, const void* id)
626 : {
627 0 : return (flags&FIRST_INC_STRONG) ? true : false;
628 : }
629 :
630 0 : void RefBase::onLastWeakRef(const void* /*id*/)
631 : {
632 0 : }
633 :
634 : // ---------------------------------------------------------------------------
635 :
636 0 : void RefBase::renameRefs(size_t n, const ReferenceRenamer& renamer) {
637 : #if DEBUG_REFS
638 : for (size_t i=0 ; i<n ; i++) {
639 : renamer(i);
640 : }
641 : #endif
642 0 : }
643 :
644 0 : void RefBase::renameRefId(weakref_type* ref,
645 : const void* old_id, const void* new_id) {
646 0 : weakref_impl* const impl = static_cast<weakref_impl*>(ref);
647 0 : impl->renameStrongRefId(old_id, new_id);
648 0 : impl->renameWeakRefId(old_id, new_id);
649 0 : }
650 :
651 0 : void RefBase::renameRefId(RefBase* ref,
652 : const void* old_id, const void* new_id) {
653 0 : ref->mRefs->renameStrongRefId(old_id, new_id);
654 0 : ref->mRefs->renameWeakRefId(old_id, new_id);
655 0 : }
656 :
657 : }; // namespace stagefright
|