source: webkit/trunk/JavaScriptCore/runtime/JSLock.cpp@ 40208

Last change on this file since 40208 was 40208, checked in by [email protected], 16 years ago

2009-01-23 Gavin Barraclough <[email protected]>

Reviewed by Geoff Garen.

Fix for <rdar://problem/6126212>
Ensure that callbacks out from the JSC interface are only allowed
to return in reverse-chronological order to that in which they were
made. If we allow earlier callbacks to return first, then this may
result in setions of the RegisterFile in use by another thread
being trampled.

See uber-comment in JSLock.h for details.

  • runtime/JSLock.cpp: (JSC::JSLock::DropAllLocks::DropAllLocks): (JSC::JSLock::DropAllLocks::~DropAllLocks):
  • Property svn:eol-style set to native
File size: 7.2 KB
Line 
1/*
2 * Copyright (C) 2005, 2008 Apple Inc. All rights reserved.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the NU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB. If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA
18 *
19 */
20
21#include "config.h"
22#include "JSLock.h"
23
24#include "Collector.h"
25#include "CallFrame.h"
26
27#if ENABLE(JSC_MULTIPLE_THREADS)
28#include <pthread.h>
29#endif
30
31namespace JSC {
32
33#if ENABLE(JSC_MULTIPLE_THREADS)
34
35// Acquire this mutex before accessing lock-related data.
36static pthread_mutex_t JSMutex = PTHREAD_MUTEX_INITIALIZER;
37
38// Thread-specific key that tells whether a thread holds the JSMutex, and how many times it was taken recursively.
39pthread_key_t JSLockCount;
40
41static void createJSLockCount()
42{
43 pthread_key_create(&JSLockCount, 0);
44}
45
46pthread_once_t createJSLockCountOnce = PTHREAD_ONCE_INIT;
47
48// Lock nesting count.
49intptr_t JSLock::lockCount()
50{
51 pthread_once(&createJSLockCountOnce, createJSLockCount);
52
53 return reinterpret_cast<intptr_t>(pthread_getspecific(JSLockCount));
54}
55
56static void setLockCount(intptr_t count)
57{
58 ASSERT(count >= 0);
59 pthread_setspecific(JSLockCount, reinterpret_cast<void*>(count));
60}
61
62JSLock::JSLock(ExecState* exec)
63 : m_lockingForReal(exec->globalData().isSharedInstance)
64{
65 lock(m_lockingForReal);
66}
67
68void JSLock::lock(bool lockForReal)
69{
70#ifdef NDEBUG
71 // Locking "not for real" is a debug-only feature.
72 if (!lockForReal)
73 return;
74#endif
75
76 pthread_once(&createJSLockCountOnce, createJSLockCount);
77
78 intptr_t currentLockCount = lockCount();
79 if (!currentLockCount && lockForReal) {
80 int result;
81 result = pthread_mutex_lock(&JSMutex);
82 ASSERT(!result);
83 }
84 setLockCount(currentLockCount + 1);
85}
86
87void JSLock::unlock(bool lockForReal)
88{
89 ASSERT(lockCount());
90
91#ifdef NDEBUG
92 // Locking "not for real" is a debug-only feature.
93 if (!lockForReal)
94 return;
95#endif
96
97 intptr_t newLockCount = lockCount() - 1;
98 setLockCount(newLockCount);
99 if (!newLockCount && lockForReal) {
100 int result;
101 result = pthread_mutex_unlock(&JSMutex);
102 ASSERT(!result);
103 }
104}
105
106void JSLock::lock(ExecState* exec)
107{
108 lock(exec->globalData().isSharedInstance);
109}
110
111void JSLock::unlock(ExecState* exec)
112{
113 unlock(exec->globalData().isSharedInstance);
114}
115
116bool JSLock::currentThreadIsHoldingLock()
117{
118 pthread_once(&createJSLockCountOnce, createJSLockCount);
119 return !!pthread_getspecific(JSLockCount);
120}
121
122// This is fairly nasty. We allow multiple threads to run on the same
123// context, and we do not require any locking semantics in doing so -
124// clients of the API may simply use the context from multiple threads
125// concurently, and assume this will work. In order to make this work,
126// We lock the context when a thread enters, and unlock it when it leaves.
127// However we do not only unlock when the thread returns from its
128// entry point (evaluate script or call function), we also unlock the
129// context if the thread leaves JSC by making a call out to an external
130// function through a callback.
131//
132// All threads using the context share the same JS stack (the RegisterFile).
133// Whenever a thread calls into JSC it starts using the RegisterFile from the
134// previous 'high water mark' - the maximum point the stack has ever grown to
135// (returned by RegisterFile::end()). So if a first thread calls out to a
136// callback, and a second thread enters JSC, then also exits by calling out
137// to a callback, we can be left with stackframes from both threads in the
138// RegisterFile. As such, a problem may occur should the first thread's
139// callback complete first, and attempt to return to JSC. Were we to allow
140// this to happen, and were its stack to grow further, then it may potentially
141// write over the second thread's call frames.
142//
143// In avoid JS stack corruption we enforce a policy of only ever allowing two
144// threads to use a JS context concurrently, and only allowing the second of
145// these threads to execute until it has completed and fully returned from its
146// outermost call into JSC. We enforce this policy using 'lockDropDepth'. The
147// first time a thread exits it will call DropAllLocks - which will do as expected
148// and drop locks allowing another thread to enter. Should another thread, or the
149// same thread again, enter JSC (through evaluate script or call function), and exit
150// again through a callback, then the locks will not be dropped when DropAllLocks
151// is called (since lockDropDepth is non-zero). Since this thread is still holding
152// the locks, only it will re able to re-enter JSC (either be returning from the
153// callback, or by re-entering through another call to evaulate script or call
154// function).
155//
156// This policy is slightly more restricive than it needs to be for correctness -
157// we could validly allow futher entries into JSC from other threads, we only
158// need ensure that callbacks return in the reverse chronological order of the
159// order in which they were made - though implementing the less restrictive policy
160// would likely increase complexity and overhead.
161//
162static unsigned lockDropDepth = 0;
163
164JSLock::DropAllLocks::DropAllLocks(ExecState* exec)
165 : m_lockingForReal(exec->globalData().isSharedInstance)
166{
167 pthread_once(&createJSLockCountOnce, createJSLockCount);
168
169 if (lockDropDepth++) {
170 m_lockCount = 0;
171 return;
172 }
173
174 m_lockCount = JSLock::lockCount();
175 for (intptr_t i = 0; i < m_lockCount; i++)
176 JSLock::unlock(m_lockingForReal);
177}
178
179JSLock::DropAllLocks::DropAllLocks(bool lockingForReal)
180 : m_lockingForReal(lockingForReal)
181{
182 pthread_once(&createJSLockCountOnce, createJSLockCount);
183
184 if (lockDropDepth++) {
185 m_lockCount = 0;
186 return;
187 }
188
189 // It is necessary to drop even "unreal" locks, because having a non-zero lock count
190 // will prevent a real lock from being taken.
191
192 m_lockCount = JSLock::lockCount();
193 for (intptr_t i = 0; i < m_lockCount; i++)
194 JSLock::unlock(m_lockingForReal);
195}
196
197JSLock::DropAllLocks::~DropAllLocks()
198{
199 for (intptr_t i = 0; i < m_lockCount; i++)
200 JSLock::lock(m_lockingForReal);
201
202 --lockDropDepth;
203}
204
205#else
206
207JSLock::JSLock(ExecState*)
208 : m_lockingForReal(false)
209{
210}
211
212// If threading support is off, set the lock count to a constant value of 1 so ssertions
213// that the lock is held don't fail
214intptr_t JSLock::lockCount()
215{
216 return 1;
217}
218
219bool JSLock::currentThreadIsHoldingLock()
220{
221 return true;
222}
223
224void JSLock::lock(bool)
225{
226}
227
228void JSLock::unlock(bool)
229{
230}
231
232void JSLock::lock(ExecState*)
233{
234}
235
236void JSLock::unlock(ExecState*)
237{
238}
239
240JSLock::DropAllLocks::DropAllLocks(ExecState*)
241{
242}
243
244JSLock::DropAllLocks::DropAllLocks(bool)
245{
246}
247
248JSLock::DropAllLocks::~DropAllLocks()
249{
250}
251
252#endif // USE(MULTIPLE_THREADS)
253
254} // namespace JSC
Note: See TracBrowser for help on using the repository browser.