Latest available version: IDA and decompilers v8.4.240320 see all releases
Hex-Rays logo State-of-the-art binary code analysis tools
email icon

In one of the past tips we mentioned the __unused attribute which can be applied to function arguments. When can it be useful? 

Let’s consider this code from Apple’s dyld:

  v19 = (dyld4::ProcessConfig::PathOverrides *)_platform_strncmp(__s, "DYLD_INSERT_LIBRARIES", 0x15uLL);
  if ( !(_DWORD)v19 )
    result = (size_t)dyld4::ProcessConfig::PathOverrides::setString(v19, a4, this + 12, v15);

v19 is passed as fist argument to dyld4::ProcessConfig::PathOverrides::setString(). Since its name looks like a class method, the decompiler assigned the class type to the first argument (normally corresponding to the implicit this argument). However, strncmp returns a simple integer with the comparison result and has no relation to the PathOverrides class. What’s going on?

To clarify things, it can be useful to look inside the function being called. It is pretty short so we can show the whole output:

const char *__fastcall dyld4::ProcessConfig::PathOverrides::setString(
        dyld4::ProcessConfig::PathOverrides *this,
        lsl::Allocator *a2,
        const char **a3,
        const char *__s)
  size_t v7; // x22
  size_t v8; // x8
  char *v9; // x22
  char *v10; // x0
  const char *result; // x0
  __int64 v12; // [xsp+0h] [xbp-40h] BYREF

  if ( *a3 )
    v7 = _platform_strlen(*a3);
    v8 = (v7 + _platform_strlen(__s) + 17) & 0xFFFFFFFFFFFFFFF0LL;
    v9 = (char *)&v12 - v8;
    v10 = strcpy((char *)&v12 - v8, *a3);
    *(_WORD *)&v9[_platform_strlen(v10)] = 58;
    strcat(v9, __s);
    result = (const char *)lsl::Allocator::strdup(a2, v9);
    result = (const char *)lsl::Allocator::strdup(a2, __s);
  *a3 = result;
  return result;

You may notice a curious thing: the this argument is not used in the body of the function. This can be confirmed by checking the cross-references (shortcut X):

Empty list of "Local cross references to this"

An additional confirmation is the assembly code preceding the call to the function:

ADD             X2, X21, #0x60 ; '`' ; char **
MOV             X1, X22 ; lsl::Allocator *
MOV             X3, X23 ; __s
BL              dyld4::ProcessConfig::PathOverrides::setString(lsl::Allocator &,char const*&,char const*)

We can see X1, X2 and X3 being initialized with values for the three arguments, but X0 (this) is not explicitly initialized, so the decompiler falls back to the last initialized value (result of the call to __platform_strncmp()), which is obviously unrelated. Can we make the decompilation nicer?

The solution is to mark the this argument as unused by editing either the full function prototype or just the argument’s type:

[Please enter the type declaration]
__unused dyld4::ProcessConfig::PathOverrides *this

After returning to the caller and refreshing, the output is much nicer-looking:

  if ( !_platform_strncmp(__s, "DYLD_INSERT_LIBRARIES", 0x15uLL) )
    result = (size_t)dyld4::ProcessConfig::PathOverrides::setString(UNUSED_ARG(), a4, this + 12, v15);
    v23 = this[12];

The decompiler inlined the strncmp call into the if condition because it no longer needs the separate v19 variable. The bogus this argument got replaced by the dummy placeholder UNUSED_ARG().