@@ -26,10 +26,10 @@ function copyProperties (original, wrapped) {
2626
2727function wrapFunction ( original , wrapper ) {
2828 if ( typeof original === 'function' ) assertNotClass ( original )
29- // TODO This needs to be re-done so that this and wrapMethod are distinct.
30- const target = { func : original }
31- wrapMethod ( target , 'func' , wrapper , typeof original !== 'function' )
32- let delegate = target . func
29+
30+ let delegate = safeMode
31+ ? safeWrapper ( original , wrapper )
32+ : wrapper ( original )
3333
3434 const shim = function shim ( ) {
3535 return delegate . apply ( this , arguments )
@@ -85,98 +85,10 @@ function wrapMethod (target, name, wrapper, noAssert) {
8585 }
8686
8787 const original = target [ name ]
88- let wrapped
89-
90- if ( safeMode && original ) {
91- // In this mode, we make a best-effort attempt to handle errors that are thrown
92- // by us, rather than wrapped code. With such errors, we log them, and then attempt
93- // to return the result as if no wrapping was done at all.
94- //
95- // Caveats:
96- // * If the original function is called in a later iteration of the event loop,
97- // and we throw _then_, then it won't be caught by this. In practice, we always call
98- // the original function synchronously, so this is not a problem.
99- // * While async errors are dealt with here, errors in callbacks are not. This
100- // is because we don't necessarily know _for sure_ that any function arguments
101- // are wrapped by us. We could wrap them all anyway and just make that assumption,
102- // or just assume that the last argument is always a callback set by us if it's a
103- // function, but those don't seem like things we can rely on. We could add a
104- // `shimmer.markCallbackAsWrapped()` function that's a no-op outside safe-mode,
105- // but that means modifying every instrumentation. Even then, the complexity of
106- // this code increases because then we'd need to effectively do the reverse of
107- // what we're doing for synchronous functions. This is a TODO.
108-
109- // We're going to hold on to current callState in this variable in this scope,
110- // which is fine because any time we reference it, we're referencing it synchronously.
111- // We'll use it in the our wrapper (which, again, is called syncrhonously), and in the
112- // errorHandler, which will already have been bound to this callState.
113- let currentCallState
114-
115- // Rather than calling the original function directly from the shim wrapper, we wrap
116- // it again so that we can track if it was called and if it returned. This is because
117- // we need to know if an error was thrown by the original function, or by us.
118- // We could do this inside the `wrapper` function defined below, which would simplify
119- // managing the callState, but then we'd be calling `wrapper` on each invocation, so
120- // instead we do it here, once.
121- const innerWrapped = wrapper ( function ( ...args ) {
122- // We need to stash the callState here because of recursion.
123- const callState = currentCallState
124- callState . startCall ( )
125- const retVal = original . apply ( this , args )
126- if ( isPromise ( retVal ) ) {
127- retVal . then ( callState . endCall . bind ( callState ) )
128- } else {
129- callState . endCall ( retVal )
130- }
131- return retVal
132- } )
133-
134- // This is the crux of what we're doing in safe mode. It handles errors
135- // that _we_ cause, by logging them, and transparently providing results
136- // as if no wrapping was done at all. That means detecting (via callState)
137- // whether the function has already run or not, and if it has, returning
138- // the result, and otherwise calling the original function unwrapped.
139- const handleError = function ( args , callState , e ) {
140- if ( callState . completed ) {
141- // error was thrown after original function returned/resolved, so
142- // it was us. log it.
143- log . error ( 'Shimmer error was thrown after original function returned/resolved' , e )
144- // original ran and returned something. return it.
145- return callState . retVal
146- }
147-
148- if ( ! callState . called ) {
149- // error was thrown before original function was called, so
150- // it was us. log it.
151- log . error ( 'Shimmer error was thrown before original function was called' , e )
152- // original never ran. call it unwrapped.
153- return original . apply ( this , args )
154- }
155-
156- // error was thrown during original function execution, so
157- // it was them. throw.
158- throw e
159- }
88+ const wrapped = safeMode && original
89+ ? safeWrapper ( original , wrapper )
90+ : wrapper ( original )
16091
161- // The wrapped function is the one that will be called by the user.
162- // It calls our version of the original function, which manages the
163- // callState. That way when we use the errorHandler, it can tell where
164- // the error originated.
165- wrapped = function ( ...args ) {
166- currentCallState = new CallState ( )
167- const errorHandler = handleError . bind ( this , args , currentCallState )
168-
169- try {
170- const retVal = innerWrapped . apply ( this , args )
171- return isPromise ( retVal ) ? retVal . catch ( errorHandler ) : retVal
172- } catch ( e ) {
173- return errorHandler ( e )
174- }
175- }
176- } else {
177- // In non-safe mode, we just wrap the original function directly.
178- wrapped = wrapper ( original )
179- }
18092 const descriptor = Object . getOwnPropertyDescriptor ( target , name )
18193
18294 const attributes = {
@@ -212,6 +124,94 @@ function wrapMethod (target, name, wrapper, noAssert) {
212124 return target
213125}
214126
127+ function safeWrapper ( original , wrapper ) {
128+ // In this mode, we make a best-effort attempt to handle errors that are thrown
129+ // by us, rather than wrapped code. With such errors, we log them, and then attempt
130+ // to return the result as if no wrapping was done at all.
131+ //
132+ // Caveats:
133+ // * If the original function is called in a later iteration of the event loop,
134+ // and we throw _then_, then it won't be caught by this. In practice, we always call
135+ // the original function synchronously, so this is not a problem.
136+ // * While async errors are dealt with here, errors in callbacks are not. This
137+ // is because we don't necessarily know _for sure_ that any function arguments
138+ // are wrapped by us. We could wrap them all anyway and just make that assumption,
139+ // or just assume that the last argument is always a callback set by us if it's a
140+ // function, but those don't seem like things we can rely on. We could add a
141+ // `shimmer.markCallbackAsWrapped()` function that's a no-op outside safe-mode,
142+ // but that means modifying every instrumentation. Even then, the complexity of
143+ // this code increases because then we'd need to effectively do the reverse of
144+ // what we're doing for synchronous functions. This is a TODO.
145+
146+ // We're going to hold on to current callState in this variable in this scope,
147+ // which is fine because any time we reference it, we're referencing it synchronously.
148+ // We'll use it in the our wrapper (which, again, is called syncrhonously), and in the
149+ // errorHandler, which will already have been bound to this callState.
150+ let currentCallState
151+
152+ // Rather than calling the original function directly from the shim wrapper, we wrap
153+ // it again so that we can track if it was called and if it returned. This is because
154+ // we need to know if an error was thrown by the original function, or by us.
155+ // We could do this inside the `wrapper` function defined below, which would simplify
156+ // managing the callState, but then we'd be calling `wrapper` on each invocation, so
157+ // instead we do it here, once.
158+ const innerWrapped = wrapper ( function ( ...args ) {
159+ // We need to stash the callState here because of recursion.
160+ const callState = currentCallState
161+ callState . startCall ( )
162+ const retVal = original . apply ( this , args )
163+ if ( isPromise ( retVal ) ) {
164+ retVal . then ( callState . endCall . bind ( callState ) )
165+ } else {
166+ callState . endCall ( retVal )
167+ }
168+ return retVal
169+ } )
170+
171+ // This is the crux of what we're doing in safe mode. It handles errors
172+ // that _we_ cause, by logging them, and transparently providing results
173+ // as if no wrapping was done at all. That means detecting (via callState)
174+ // whether the function has already run or not, and if it has, returning
175+ // the result, and otherwise calling the original function unwrapped.
176+ const handleError = function ( args , callState , e ) {
177+ if ( callState . completed ) {
178+ // error was thrown after original function returned/resolved, so
179+ // it was us. log it.
180+ log . error ( 'Shimmer error was thrown after original function returned/resolved' , e )
181+ // original ran and returned something. return it.
182+ return callState . retVal
183+ }
184+
185+ if ( ! callState . called ) {
186+ // error was thrown before original function was called, so
187+ // it was us. log it.
188+ log . error ( 'Shimmer error was thrown before original function was called' , e )
189+ // original never ran. call it unwrapped.
190+ return original . apply ( this , args )
191+ }
192+
193+ // error was thrown during original function execution, so
194+ // it was them. throw.
195+ throw e
196+ }
197+
198+ // The wrapped function is the one that will be called by the user.
199+ // It calls our version of the original function, which manages the
200+ // callState. That way when we use the errorHandler, it can tell where
201+ // the error originated.
202+ return function ( ...args ) {
203+ currentCallState = new CallState ( )
204+ const errorHandler = handleError . bind ( this , args , currentCallState )
205+
206+ try {
207+ const retVal = innerWrapped . apply ( this , args )
208+ return isPromise ( retVal ) ? retVal . catch ( errorHandler ) : retVal
209+ } catch ( e ) {
210+ return errorHandler ( e )
211+ }
212+ }
213+ }
214+
215215function wrap ( target , name , wrapper ) {
216216 return typeof name === 'function'
217217 ? wrapFn ( target , name )
0 commit comments