diff --git a/lldb/CMakeLists.txt b/lldb/CMakeLists.txt --- a/lldb/CMakeLists.txt +++ b/lldb/CMakeLists.txt @@ -52,21 +52,10 @@ endif () if (LLDB_ENABLE_LUA) - # FIXME: Lua 5.3 is hardcoded but it should support 5.3+! - find_program(Lua_EXECUTABLE lua5.3) - if (NOT Lua_EXECUTABLE) - message(FATAL_ERROR "Lua executable not found") - else () - execute_process( - COMMAND ${Lua_EXECUTABLE} - -e "for w in string.gmatch(package.cpath, ';?([^;]+);?') do \ - if string.match(w, '%?%.so') then print(string.sub(w, 1, #w - 4)) break end end" - OUTPUT_VARIABLE LLDB_LUA_DEFAULT_INSTALL_PATH - OUTPUT_STRIP_TRAILING_WHITESPACE) - file(TO_CMAKE_PATH ${LLDB_LUA_DEFAULT_INSTALL_PATH} LLDB_LUA_DEFAULT_INSTALL_PATH) - set(LLDB_LUA_INSTALL_PATH ${LLDB_LUA_DEFAULT_INSTALL_PATH} - CACHE STRING "Path where Lua modules are installed") - endif () + find_program(Lua_EXECUTABLE lua5.3) + set(LLDB_LUA_DEFAULT_RELATIVE_PATH "lib/lua/5.3") + set(LLDB_LUA_RELATIVE_PATH ${LLDB_LUA_DEFAULT_RELATIVE_PATH} + CACHE STRING "Path where Lua modules are installed, relative to install prefix") endif () if (LLDB_ENABLE_PYTHON OR LLDB_ENABLE_LUA) @@ -113,7 +102,11 @@ endif() if (LLDB_ENABLE_LUA) - set(lldb_lua_target_dir "${CMAKE_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${CMAKE_INSTALL_LIBDIR}/lua") + if(LLDB_BUILD_FRAMEWORK) + set(lldb_lua_target_dir "${LLDB_FRAMEWORK_ABSOLUTE_BUILD_DIR}/LLDB.framework/Resources/Lua") + else() + set(lldb_lua_target_dir "${CMAKE_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${LLDB_LUA_RELATIVE_PATH}") + endif() get_target_property(lldb_lua_bindings_dir swig_wrapper_lua BINARY_DIR) finish_swig_lua("lldb-lua" "${lldb_lua_bindings_dir}" "${lldb_lua_target_dir}") endif() diff --git a/lldb/bindings/lua/CMakeLists.txt b/lldb/bindings/lua/CMakeLists.txt --- a/lldb/bindings/lua/CMakeLists.txt +++ b/lldb/bindings/lua/CMakeLists.txt @@ -29,22 +29,43 @@ add_custom_target(${swig_target} ALL VERBATIM COMMAND ${CMAKE_COMMAND} -E make_directory ${lldb_lua_target_dir} DEPENDS swig_wrapper_lua - COMMENT "Lua LLDB Python API") - create_relative_symlink(${swig_target} - "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/liblldb${CMAKE_SHARED_LIBRARY_SUFFIX}" - ${lldb_lua_target_dir} - "lldb.so") + COMMENT "LLDB Lua API") + if(LLDB_BUILD_FRAMEWORK) + set(LIBLLDB_SYMLINK_DEST "${LLDB_FRAMEWORK_ABSOLUTE_BUILD_DIR}/LLDB.framework/LLDB") + else() + set(LIBLLDB_SYMLINK_DEST "${LLVM_SHLIB_OUTPUT_INTDIR}/liblldb${CMAKE_SHARED_LIBRARY_SUFFIX}") + endif() + if(WIN32) + if(CMAKE_BUILD_TYPE STREQUAL Debug) + set(LIBLLDB_SYMLINK_OUTPUT_FILE "_lldb_d.pyd") + else() + set(LIBLLDB_SYMLINK_OUTPUT_FILE "_lldb.pyd") + endif() + else() + set(LIBLLDB_SYMLINK_OUTPUT_FILE "lldb.so") + endif() + create_relative_symlink(${swig_target} ${LIBLLDB_SYMLINK_DEST} + ${lldb_lua_target_dir} ${LIBLLDB_SYMLINK_OUTPUT_FILE}) set(lldb_lua_library_target "${swig_target}-library") add_custom_target(${lldb_lua_library_target}) add_dependencies(${lldb_lua_library_target} ${swig_target}) + + # Ensure we do the Lua post-build step when building lldb. + add_dependencies(lldb ${swig_target}) + + if(LLDB_BUILD_FRAMEWORK) + set(LLDB_LUA_INSTALL_PATH ${LLDB_FRAMEWORK_INSTALL_DIR}/LLDB.framework/Resources/Python) + else() + set(LLDB_LUA_INSTALL_PATH ${LLDB_LUA_RELATIVE_PATH}) + endif() install(DIRECTORY ${lldb_lua_target_dir}/ DESTINATION ${LLDB_LUA_INSTALL_PATH} COMPONENT ${lldb_lua_library_target}) - # Ensure we do the Lua post-build step when building lldb. - add_dependencies(lldb ${swig_target}) set(lldb_lua_library_install_target "install-${lldb_lua_library_target}") - add_llvm_install_targets(${lldb_lua_library_install_target} - COMPONENT ${lldb_lua_library_target} - DEPENDS ${lldb_lua_library_target}) + if (NOT LLVM_ENABLE_IDE) + add_llvm_install_targets(${lldb_lua_library_install_target} + COMPONENT ${lldb_lua_library_target} + DEPENDS ${lldb_lua_library_target}) + endif() endfunction() diff --git a/lldb/bindings/lua/lua-typemaps.swig b/lldb/bindings/lua/lua-typemaps.swig --- a/lldb/bindings/lua/lua-typemaps.swig +++ b/lldb/bindings/lua/lua-typemaps.swig @@ -192,6 +192,8 @@ // Typemap for handling char ** in SBTarget::LaunchSimple, SBTarget::Launch... +// It should accept a Lua table of strings, for stuff like "argv" and "envp". + %typemap(in) char ** { if (lua_istable(L, $input)) { size_t size = lua_rawlen(L, $input); @@ -200,15 +202,19 @@ while (i++ < size) { lua_rawgeti(L, $input, i); if (!lua_isstring(L, -1)) { + // if current element cannot be converted to string, raise an error lua_pop(L, 1); - return luaL_error(L, "List should contains only strings"); + return luaL_error(L, "List should only contain strings"); } $1[j++] = (char *)lua_tostring(L, -1); lua_pop(L, 1); } $1[j] = 0; } else if (lua_isnil(L, $input)) { + // "nil" is also acceptable, equivalent as an empty table $1 = NULL; + } else { + return luaL_error(L, "A list of strings expected"); } } @@ -261,21 +267,31 @@ (int32_t* array, size_t array_len), (double* array, size_t array_len) { if (lua_istable(L, $input)) { + // It should accept a table of numbers. $2 = lua_rawlen(L, $input); $1 = ($1_ltype)malloc(($2) * sizeof($*1_type)); int i = 0, j = 0; while (i++ < $2) { lua_rawgeti(L, $input, i); + if (!lua_isnumber(L, -1)) { + // if current element cannot be converted to number, raise an error + lua_pop(L, 1); + return luaL_error(L, "List should only contain numbers"); + } $1[j++] = ($*1_ltype)lua_tonumber(L, -1); lua_pop(L, 1); } } else if (lua_isnil(L, $input)) { + // "nil" is also acceptable, equivalent as an empty table $1 = NULL; $2 = 0; + } else { + // else raise an error + return luaL_error(L, "A list of numbers expected."); } } -%typemap(in) (uint64_t* array, size_t array_len), +%typemap(freearg) (uint64_t* array, size_t array_len), (uint32_t* array, size_t array_len), (int64_t* array, size_t array_len), (int32_t* array, size_t array_len), diff --git a/lldb/test/API/lua_api/TestComprehensive.lua b/lldb/test/API/lua_api/TestComprehensive.lua --- a/lldb/test/API/lua_api/TestComprehensive.lua +++ b/lldb/test/API/lua_api/TestComprehensive.lua @@ -69,6 +69,9 @@ local size = self.var_inited:GetByteSize() local raw_data = self.process:ReadMemory(address:GetOffset(), size, error) assertTrue(error:Success()) + local data_le = lldb.SBData.CreateDataFromUInt32Array(lldb.eByteOrderLittle, 1, {0xDEADBEEF}) + local data_be = lldb.SBData.CreateDataFromUInt32Array(lldb.eByteOrderBig, 1, {0xDEADBEEF}) + assertTrue(data_le:GetUnsignedInt32(error, 0) == 0xDCADBEEF or data_be:GetUnsignedInt32(error, 0) == 0xDCADBEEF) assertTrue(raw_data == "\xEF\xBE\xAD\xDE" or raw_data == "\xDE\xAD\xBE\xEF") end diff --git a/lldb/test/API/lua_api/TestLuaAPI.py b/lldb/test/API/lua_api/TestLuaAPI.py --- a/lldb/test/API/lua_api/TestLuaAPI.py +++ b/lldb/test/API/lua_api/TestLuaAPI.py @@ -5,8 +5,129 @@ from lldbsuite.test.decorators import * from lldbsuite.test.lldbtest import * from lldbsuite.test import lldbutil -import lit.util +import subprocess +def to_string(b): + """Return the parameter as type 'str', possibly encoding it. + + In Python2, the 'str' type is the same as 'bytes'. In Python3, the + 'str' type is (essentially) Python2's 'unicode' type, and 'bytes' is + distinct. + + """ + if isinstance(b, str): + # In Python2, this branch is taken for types 'str' and 'bytes'. + # In Python3, this branch is taken only for 'str'. + return b + if isinstance(b, bytes): + # In Python2, this branch is never taken ('bytes' is handled as 'str'). + # In Python3, this is true only for 'bytes'. + try: + return b.decode('utf-8') + except UnicodeDecodeError: + # If the value is not valid Unicode, return the default + # repr-line encoding. + return str(b) + + # By this point, here's what we *don't* have: + # + # - In Python2: + # - 'str' or 'bytes' (1st branch above) + # - In Python3: + # - 'str' (1st branch above) + # - 'bytes' (2nd branch above) + # + # The last type we might expect is the Python2 'unicode' type. There is no + # 'unicode' type in Python3 (all the Python3 cases were already handled). In + # order to get a 'str' object, we need to encode the 'unicode' object. + try: + return b.encode('utf-8') + except AttributeError: + raise TypeError('not sure how to convert %s to %s' % (type(b), str)) + +class ExecuteCommandTimeoutException(Exception): + def __init__(self, msg, out, err, exitCode): + assert isinstance(msg, str) + assert isinstance(out, str) + assert isinstance(err, str) + assert isinstance(exitCode, int) + self.msg = msg + self.out = out + self.err = err + self.exitCode = exitCode + + +# Close extra file handles on UNIX (on Windows this cannot be done while +# also redirecting input). +kUseCloseFDs = not (platform.system() == 'Windows') + + +def executeCommand(command, cwd=None, env=None, input=None, timeout=0): + """Execute command ``command`` (list of arguments or string) with. + + * working directory ``cwd`` (str), use None to use the current + working directory + * environment ``env`` (dict), use None for none + * Input to the command ``input`` (str), use string to pass + no input. + * Max execution time ``timeout`` (int) seconds. Use 0 for no timeout. + + Returns a tuple (out, err, exitCode) where + * ``out`` (str) is the standard output of running the command + * ``err`` (str) is the standard error of running the command + * ``exitCode`` (int) is the exitCode of running the command + + If the timeout is hit an ``ExecuteCommandTimeoutException`` + is raised. + + """ + if input is not None: + input = to_bytes(input) + p = subprocess.Popen(command, cwd=cwd, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=env, close_fds=kUseCloseFDs) + timerObject = None + # FIXME: Because of the way nested function scopes work in Python 2.x we + # need to use a reference to a mutable object rather than a plain + # bool. In Python 3 we could use the "nonlocal" keyword but we need + # to support Python 2 as well. + hitTimeOut = [False] + try: + if timeout > 0: + def killProcess(): + # We may be invoking a shell so we need to kill the + # process and all its children. + hitTimeOut[0] = True + killProcessAndChildren(p.pid) + + timerObject = threading.Timer(timeout, killProcess) + timerObject.start() + + out, err = p.communicate(input=input) + exitCode = p.wait() + finally: + if timerObject != None: + timerObject.cancel() + + # Ensure the resulting output is always of string type. + out = to_string(out) + err = to_string(err) + + if hitTimeOut[0]: + raise ExecuteCommandTimeoutException( + msg='Reached timeout of {} seconds'.format(timeout), + out=out, + err=err, + exitCode=exitCode + ) + + # Detect Ctrl-C in subprocess. + if exitCode == -signal.SIGINT: + raise KeyboardInterrupt + + return out, err, exitCode class TestLuaAPI(TestBase): @@ -41,7 +162,7 @@ test_output = self.getBuildArtifact("output") test_input = self.getBuildArtifact("input") - lua_lldb_cpath = "%s/lua/?.so" % configuration.lldb_libs_dir + lua_lldb_cpath = "%s/lua/5.3/?.so" % configuration.lldb_libs_dir lua_prelude = "package.cpath = '%s;' .. package.cpath" % lua_lldb_cpath @@ -53,7 +174,7 @@ for lua_test in self.get_tests(): cmd = [lua_executable] + ["-e", lua_prelude] + [lua_test] - out, err, exitCode = lit.util.executeCommand(cmd, env=lua_env) + out, err, exitCode = executeCommand(cmd, env=lua_env) # Redirect Lua output print(out)