Search code examples
memory-leaksuiwebviewwebkitapp-storehtml-input

input element of type range leaks in UIWebView


I was about to submit my app for review when I discovered that the input element of type range in my UIWebView leaks 12 bytes when added to the document. There are no subsequent leaks; not even when the slider is used.

I would be grateful for any advice as to how to proceed with my submission. Should I be worried at all about 12 bytes? Should I find a way around this, say, by not using this element at all? Or, should I make a note of the leak to the reviewers (under Review Notes rubric)?

The leak can be replicated with a minimal UIWebView application:

#import "TjaViewController.h"

@interface TjaViewController ()
@property (weak, nonatomic) IBOutlet UIWebView *webView;
@end

@implementation TjaViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.webView loadHTMLString:@"<input type='range'>" baseURL:nil];
}

@end

Profiling the app with Instruments yields a single leak with the following properties:

Category: Malloc 12 Bytes
Retain Count: 1
Responsible Library: JavaScriptCore
Responsible Caller: WTF::fastMalloc(unsigned long)

Stack trance:

32 libsystem_pthread.dylib thread_start
31 libsystem_pthread.dylib _pthread_start
30 libsystem_pthread.dylib _pthread_body
29 WebCore RunWebThread(void*)
28 CoreFoundation CFRunLoopRunInMode
27 CoreFoundation CFRunLoopRunSpecific
26 CoreFoundation __CFRunLoopRun
25 CoreFoundation __CFRunLoopDoSources0
24 CoreFoundation __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
23 WebCore HandleRunSource
22 WebCore ___ZN7WebCoreL26applicationDidBecomeActiveEP22__CFNotificationCenterPvPK10__CFStringPKvPK14__CFDictionary_block_invoke
21 WebCore WebCore::ThreadTimers::sharedTimerFiredInternal()
20 WebCore WebCore::DocumentLoader::handleSubstituteDataLoadNow(WebCore::Timer<WebCore::DocumentLoader>*)
19 WebCore WebCore::DocumentLoader::responseReceived(WebCore::CachedResource*, WebCore::ResourceResponse const&)
18 WebCore WebCore::DocumentLoader::continueAfterContentPolicy(WebCore::PolicyAction)
17 WebCore WebCore::DocumentLoader::dataReceived(WebCore::CachedResource*, char const*, int)
16 WebCore WebCore::DocumentLoader::commitLoad(char const*, int)
15 WebKit WebFrameLoaderClient::committedLoad(WebCore::DocumentLoader*, char const*, int)
14 WebKit -[WebDataSource(WebInternal) _receivedData:]
13 WebKit -[WebHTMLRepresentation receivedData:withDataSource:]
12 WebCore WebCore::DocumentLoader::commitData(char const*, unsigned long)
11 WebCore WebCore::DecodedDataDocumentParser::appendBytes(WebCore::DocumentWriter*, char const*, unsigned long)
10 WebCore WebCore::HTMLDocumentParser::append(WTF::PassRefPtr<WTF::StringImpl>)
9 WebCore WebCore::HTMLDocumentParser::pumpTokenizer(WebCore::HTMLDocumentParser::SynchronousMode)
8 WebCore WebCore::HTMLDocumentParser::constructTreeFromHTMLToken(WebCore::HTMLToken&)
7 WebCore WebCore::HTMLConstructionSite::executeQueuedTasks()
6 WebCore WebCore::executeTask(WebCore::HTMLConstructionSiteTask&)
5 WebCore WebCore::insert(WebCore::HTMLConstructionSiteTask&, bool)
4 WebCore WebCore::HTMLInputElement::attach(WebCore::Node::AttachContext const&)
3 WebCore WebCore::FeatureObserver::didObserve(WebCore::FeatureObserver::Feature)
2 JavaScriptCore WTF::BitVector::resizeOutOfLine(unsigned long)
1 JavaScriptCore WTF::fastMalloc(unsigned long)
0 JavaScriptCore WTF::MallocHook::recordAllocation(void*, unsigned long)

Solution

  • I think I figured it out

    Seems it's a leak in libWTF

    and here is the original code from https://github.com/leolannenmaki/JavaScriptCore-iOS

    void BitVector::resizeOutOfLine(size_t numBits)
    {
        ASSERT(numBits > maxInlineBits());
        OutOfLineBits* newOutOfLineBits = OutOfLineBits::create(numBits);
        size_t newNumWords = newOutOfLineBits->numWords();
        if (isInline()) {
        // Make sure that all of the bits are zero in case we do a no-op resize.
            *newOutOfLineBits->bits() = m_bitsOrPointer & ~(static_cast<uintptr_t>(1) << maxInlineBits());
            memset(newOutOfLineBits->bits() + 1, 0, (newNumWords - 1) * sizeof(void*));
        } else {
            if (numBits > size()) {
                size_t oldNumWords = outOfLineBits()->numWords();
                memcpy(newOutOfLineBits->bits(), outOfLineBits()->bits(), oldNumWords * sizeof(void*));
                memset(newOutOfLineBits->bits() + oldNumWords, 0, (newNumWords - oldNumWords) * sizeof(void*));
            } else
                memcpy(newOutOfLineBits->bits(), outOfLineBits()->bits(), newOutOfLineBits->numWords() * sizeof(void*));
            OutOfLineBits::destroy(outOfLineBits());
        }
        m_bitsOrPointer = bitwise_cast<uintptr_t>(newOutOfLineBits) >> 1;
    }
    

    And it's obviously the newOutOfLineBits is not destroied when code goes isInline()

    I tried to replace the system JavascriptCore.framework

    /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.1.sdk/System/Library/Frameworks/JavaScriptCore.framework

    but failed, the system framework is compiled from dynamic library

    as far as I know, apple forbids compile dynamic library for iOS...

    so I think the only way is to report this leak to Apple...