Skip to content

Commit ef5d18b

Browse files
committed
[[ Bug 20090 ]] Catch obj-c exceptions when called through LCB
This patch wraps the obj-c ffi call to objc_msgSend (and friends) in a @try/@catch block. Any obj-c exception which is thrown through this mechanism is caught and thrown as an MCError object containing the 'reason' for the error as the message.
1 parent 4c4fb21 commit ef5d18b

File tree

7 files changed

+103
-7
lines changed

7 files changed

+103
-7
lines changed

docs/lcb/notes/20090.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# LiveCode Builder Language
2+
3+
## Foreign Function Interface
4+
5+
* Obj-C exceptions thrown from calls to obj-c foreign handlers will now be caught
6+
and rethrown as an LCB error.

libscript/libscript.gyp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
'src/script-object.cpp',
3030
'src/script-package.cpp',
3131
'src/script-execute.cpp',
32+
'src/script-execute-objc.mm',
3233
'src/script-error.cpp',
3334
],
3435
},
@@ -73,6 +74,15 @@
7374

7475
'conditions':
7576
[
77+
[
78+
'OS != "mac" and OS != "ios"',
79+
{
80+
'sources!':
81+
[
82+
'src/script-execute-objc.mm',
83+
],
84+
},
85+
],
7686
[
7787
'OS == "linux" or OS == "android"',
7888
{

libscript/src/script-error.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,15 @@ MCScriptThrowJavaBindingNotSupported(void)
369369
nil);
370370
}
371371

372+
bool
373+
MCScriptThrowForeignExceptionError(MCStringRef p_reason)
374+
{
375+
return MCErrorCreateAndThrow(kMCGenericErrorTypeInfo,
376+
"reason",
377+
p_reason,
378+
nil);
379+
}
380+
372381
bool
373382
MCScriptThrowObjCBindingNotSupported(void)
374383
{
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/* Copyright (C) 2017 LiveCode Ltd.
2+
3+
This file is part of LiveCode.
4+
5+
LiveCode is free software; you can redistribute it and/or modify it under
6+
the terms of the GNU General Public License v3 as published by the Free
7+
Software Foundation.
8+
9+
LiveCode is distributed in the hope that it will be useful, but WITHOUT ANY
10+
WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
for more details.
13+
14+
You should have received a copy of the GNU General Public License
15+
along with LiveCode. If not see <http://www.gnu.org/licenses/>. */
16+
17+
#include "libscript/script.h"
18+
#include "script-private.h"
19+
20+
#include "ffi.h"
21+
22+
#include "foundation-auto.h"
23+
24+
#include <Foundation/Foundation.h>
25+
26+
#include "script-execute.hpp"
27+
28+
bool MCScriptCallObjCCatchingErrors(ffi_cif *p_cif, void (*p_function)(), void *p_result_ptr, void **p_arg_ptrs)
29+
{
30+
@try
31+
{
32+
ffi_call(p_cif, p_function, p_result_ptr, p_arg_ptrs);
33+
return true;
34+
}
35+
@catch (NSException *exception)
36+
{
37+
MCAutoStringRef t_reason;
38+
if (!MCStringCreateWithCFString((CFStringRef)[exception reason], &t_reason))
39+
{
40+
return false;
41+
}
42+
43+
return MCScriptThrowForeignExceptionError(*t_reason);
44+
}
45+
46+
return false;
47+
}

libscript/src/script-execute.cpp

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -311,12 +311,13 @@ class MCScriptForeignInvocation
311311
else
312312
t_objc_msgSend = (void(*)())objc_msgSend;
313313
#endif
314-
ffi_call(t_cif,
315-
(void(*)())objc_msgSend,
316-
p_result_slot_ptr,
317-
t_objc_values);
318-
319-
return true;
314+
/* We must use a wrapper function written in an obj-c++ source file so that
315+
* we can capture any obj-c exceptions. */
316+
extern bool MCScriptCallObjCCatchingErrors(ffi_cif*, void (*)(), void *, void **);
317+
return MCScriptCallObjCCatchingErrors(t_cif,
318+
(void(*)())objc_msgSend,
319+
p_result_slot_ptr,
320+
t_objc_values);
320321
#else
321322
return true;
322323
#endif

libscript/src/script-private.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,9 @@ MCScriptThrowMissingFunctionInForeignBindingError(void);
682682
bool
683683
MCScriptThrowUnableToLoadForiegnLibraryError(void);
684684

685+
bool
686+
MCScriptThrowForeignExceptionError(MCStringRef p_reason);
687+
685688
bool
686689
MCScriptThrowObjCBindingNotSupported(void);
687690

tests/lcb/vm/interop-objc.lcb

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ end handler
7575

7676
---------
7777

78-
7978
public handler TestObjInterop_RoundtripNumber()
8079
if not the operating system is in ["mac", "ios"] then
8180
skip test "objc binding succeeds" because "not implemented on" && the operating system
@@ -101,4 +100,25 @@ end handler
101100

102101
--------
103102

103+
__safe foreign handler MCHandlerTryToInvokeWithList(in Handler as any, inout Arguments as optional List, out Result as optional any) returns optional any binds to "<builtin>"
104+
__safe foreign handler NSArrayFirstObject(in pObj as ObjcId) returns ObjcId binds to "objc:NSArray.firstObject"
105+
106+
private handler __CallNSNumberMethodOnNSString(in pString as ObjcObject) returns optional any
107+
return NSArrayFirstObject(pString)
108+
end handler
109+
110+
public handler TestObjcInterop_CatchObjcException()
111+
if not the operating system is in ["mac", "ios"] then
112+
skip test "objc exception handler succeeds" because "not implemented on" && the operating system
113+
return
114+
end if
115+
116+
variable tArguments as List
117+
variable tResult as optional any
118+
put [ StringToNSString("foo") ] into tArguments
119+
test "objc exception is caught" when MCHandlerTryToInvokeWithList(__CallNSNumberMethodOnNSString, tArguments, tResult) is not nothing
120+
end handler
121+
122+
---------
123+
104124
end module

0 commit comments

Comments
 (0)