<table border="1" cellspacing="0" cellpadding="8">
    <tr>
        <th>Issue</th>
        <td>
            <a href=https://github.com/llvm/llvm-project/issues/55367>55367</a>
        </td>
    </tr>

    <tr>
        <th>Summary</th>
        <td>
            clang-analyzer: False positive when variable is cast multiple times (IS_ERR_VALUE() )
        </td>
    </tr>

    <tr>
      <th>Labels</th>
      <td>
            new issue
      </td>
    </tr>

    <tr>
      <th>Assignees</th>
      <td>
      </td>
    </tr>

    <tr>
      <th>Reporter</th>
      <td>
          daniel-thompson
      </td>
    </tr>
</table>

<pre>
    TL;DR analyzer fails to reason that e is non-zero when the following predicate is true: e >= (unsigned long) -4095 . Normally thje analyzer can apply this reasoning but appears to be confused by the casting involved in the Linux kernel functions and macros that do this.

After running clang-analyzer on Linux kernel code I discovered a false positive where the analyzer simultaneously assumes the same value as both non-zero and zero in different parts of the reasoning tree. The false positive triggers on a common code pattern inside the Linux kernel (returning an -ve errno instead of a pointer). I have provided multiple versions of the failed reasoning all of which are effectively different implementations of the IS_ERR_VALUE() macro found in the Linux kernel. If I implement the checks as a signed long then the analyzer can reason correctly, however it I implement similar checks using an unsigned long then there is a false error report.

It appears that the analyzer would normally reason correctly (or at least not report a warning), however the frequent casting between pointer, long, unsigned long and int types is confusing the analyzer.

The following minimized example has been tested on both clang-13 and clang-14:

~~~ c
// Use these to control which implementation of IS_ERR() to use
//#define WANT_SIGNED_LONG_IS_ERR_VALUE
#define WANT_UNSIGNED_LONG_IS_ERR_VALUE

#define MAX_ERRNO 4095

static inline int IS_ERR(const void *ptr)
{
#if defined WANT_SIGNED_LONG_IS_ERR_VALUE

        /*
         * This is semantically equivalent to the real IS_ERR_VALUE()
         * implementation but works using a long rather than an unsigned long.
         *
         * This implementation does not provoke any false-positive.
         */
        long e = (long) ptr;
        return e < 0 && e >= -MAX_ERRNO;

#elif defined WANT_UNSIGNED_LONG_IS_ERR_VALUE

        /*
         * This is partway between the two IS_ERR_VALUE() implementations above
         * and below and show the failed reasoning in the most obvious way.
         *
         * It is the same check as the one used in Linux kernel but prefixed with
         * an explicit comparison to zero. Thus the output from the analyzer is
         * easier to read since we can see it explicitly assume e in non-zero
         * rather than implicitly by checking ptr is (unsigned) >= -4095.
         *
         * It implies that the failure to reason about the range of x is
         * linked to casting it as an unsigned long.
         */
        unsigned long e = (unsigned long) ptr;
        return e != 0 && e >= (unsigned long) -MAX_ERRNO;

#else

        /*
         * This is the actual check that appears in the Linux kernel.
         *
         * By treating the value as unsigned then the range check can be a
         * single comparison. Currently clang-analyzer does not use use this
         * predicate to reason about the possible values of x.
         *
         * In particular clang-analyzer will explore code paths where
         * IS_ERR_VALUE(x) is true but then later assume that x is zero.
         */
        return ((unsigned long) (ptr) >= (unsigned long)-MAX_ERRNO);

#endif
}

#define EINVAL 22

struct task_struct {
        unsigned int ctrl;
};

static struct task_struct *ptrace_hbp_get_event(unsigned int note_type,
                                               struct task_struct *tsk)
{
        return note_type ? tsk : (void *) -EINVAL;
}

static int ptrace_hbp_get_ctrl(unsigned int note_type,
                               struct task_struct *tsk,
                               unsigned int *ctrl)
{
        struct task_struct *t = ptrace_hbp_get_event(note_type, tsk);

        if (IS_ERR(t))
                // Note: the false positive can also be dismissed if we
                // remove the (long) here!
                return (int) (long) t;

        *ctrl = t->ctrl;
        return 0;
}

int hw_break_get(struct task_struct *target, unsigned int note_type,
                 unsigned int *to)
{
        int ret;
        unsigned int ctrl;

        ret = ptrace_hbp_get_ctrl(note_type, target, &ctrl);
        if (ret)
                return ret;
        *to = ctrl;
        return 0;
}
~~~

The test case output is easiest to read when WANT_UNSIGNED_LONG_IS_ERR_VALUE is set as shown below (with a couple of `<<<< chevrons >>>>` to highlight the key parts.

~~~
maple$ clang-14 -Wall --analyze --analyzer-output text -c ptrace-IS_ERR_VALUE.c 
ptrace-IS_ERR_VALUE.c:109:6: warning: Assigned value is garbage or undefined [core.uninitialized.Assign]
        *to = ctrl;
            ^ ~~~~
ptrace-IS_ERR_VALUE.c:104:2: note: 'ctrl' declared without an initial value
        unsigned int ctrl;
        ^~~~~~~~~~~~~~~~~
ptrace-IS_ERR_VALUE.c:106:8: note: Calling 'ptrace_hbp_get_ctrl'
        ret = ptrace_hbp_get_ctrl(note_type, target, &ctrl);
              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ptrace-IS_ERR_VALUE.c:89:26: note: Calling 'ptrace_hbp_get_event'
        struct task_struct *t = ptrace_hbp_get_event(note_type, tsk);
                                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ptrace-IS_ERR_VALUE.c:82:9: note: Assuming 'note_type' is not equal to 0
        return note_type ? tsk : (void *) -EINVAL;
               ^~~~~~~~~
ptrace-IS_ERR_VALUE.c:82:9: note: '?' condition is true
ptrace-IS_ERR_VALUE.c:82:2: note: Returning pointer, which participates in a condition later
        return note_type ? tsk : (void *) -EINVAL;
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ptrace-IS_ERR_VALUE.c:89:26: note: Returning from 'ptrace_hbp_get_event'
        struct task_struct *t = ptrace_hbp_get_event(note_type, tsk);
                                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ptrace-IS_ERR_VALUE.c:91:6: note: Calling 'IS_ERR'
        if (IS_ERR(t))
            ^~~~~~~~~
ptrace-IS_ERR_VALUE.c:54:9: note: Assuming 'e' is not equal to 0, which participates in a condition later
        return e != 0 && e >= (unsigned long) -MAX_ERRNO;           <<<<< e/ret is assumed non-zero >>>>>
               ^~~~~~
ptrace-IS_ERR_VALUE.c:54:9: note: Left side of '&&' is true
ptrace-IS_ERR_VALUE.c:54:19: note: Assuming the condition is true
        return e != 0 && e >= (unsigned long) -MAX_ERRNO;
                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ptrace-IS_ERR_VALUE.c:54:2: note: Returning the value 1, which participates in a condition later
        return e != 0 && e >= (unsigned long) -MAX_ERRNO;
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ptrace-IS_ERR_VALUE.c:91:6: note: Returning from 'IS_ERR'
        if (IS_ERR(t))
            ^~~~~~~~~
ptrace-IS_ERR_VALUE.c:91:2: note: Taking true branch
        if (IS_ERR(t))
        ^
ptrace-IS_ERR_VALUE.c:94:3: note: Returning without writing to '*ctrl'
                return (int) (long) t;
                ^
ptrace-IS_ERR_VALUE.c:94:3: note: Returning value (loaded from 't'), which participates in a condition later
                return (int) (long) t;
                ^~~~~~~~~~~~~~~~~~~~~
ptrace-IS_ERR_VALUE.c:106:8: note: Returning from 'ptrace_hbp_get_ctrl'
        ret = ptrace_hbp_get_ctrl(note_type, target, &ctrl);
              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ptrace-IS_ERR_VALUE.c:107:6: note: Assuming 'ret' is 0
        if (ret)                                               <<<<< e/ret is later assumed to be zero >>>>>
            ^~~
ptrace-IS_ERR_VALUE.c:107:2: note: Taking false branch
        if (ret)
        ^
ptrace-IS_ERR_VALUE.c:109:6: note: Assigned value is garbage or undefined
        *to = ctrl;
            ^ ~~~~
1 warning generated.
~~~
</pre>
<img width="1px" height="1px" alt="" src="http://email.email.llvm.org/o/eJztWltz4rgS_jXkRQVFICTDQx5y3ZqqOdmq2Znd85YStgBtjMVKMoT99efrlm1sYy6ZyZzZh6UcYrDc6svX3Z9kJibeXH_51Bne3n8WMpXJ5m9lxVTqxAlvhFXSmVT4ufRCCe1EatIuRhixniv6XompSRKz1ulMLK2KdSQ9D_Q2U53hDe7qDB86w3vRGXzIUqdnqYpFYtJZZzAW3Yv-eCR64snYhUySDQT-qbZqRDIVcrnk7yEyKEMzTTJPF5S0rOVEicik08xB9GTDWkXSeRqp05VJVvheB20_6TR7FS_KpioR0yyNvDapw5SxWMjIGhdsjQ1P2ev07zv9m_B-M_XQyWYpqxAlMp11S1XhpJroyMRKfBSxdpFZKThGSHg1cUosjdNerxR50CpWqpTi9CJLvEyVyRysls5lC-V4jJMLJVYyyTDciYnx820sSHs-gZGxnk4hN_ViKa13wkz59q3vvFWqJ75Q5Or6eKtnMwWPwhYJAxYLnLAdS-lhegrxTsdq148IrVU-sywfMetCmrI2JYWcVzImLSRm0inkIPA9uGYuMWppzQoi4XzYrZcJTIQGHJJccYIirm_1B07o2nquo7mQ8KCCwRFZAI9trdcLSFvgTPqquI-_PT98_vz8-82nrw9Qm0DIcQeMs7QVJdB1CnVLeQFecxW9OIqEFBVM07W0HlLCcJ5FkbEWmiabzuBOzM1awVahfU04EKATaYsJMpe7tJY65TSWU61AFlxuAFC1NNbXkPuxki2E7pqCa5MlMbCUZ2BTVwoupOKuBFc8Bvp8Csy7lhxzeLFqEofNqr8yMqhIxInyawWtSxDc5VXgrmGb5DBAyc0S0Id5IbV1MLvUu2bgl1odWugUXvwbAtWrJM8Ca0gZmt0r4DEmhHMGhSQ-H_Kk-YcLVK2q7M7VQzhElH8xeMQhvjrOBHo3pKO3JslRWcceQS_ALgccxqNUVYV1BsNYTXWqxB83T1-ef_v4y9PD_fOnX59-ea4BNr-lNvjr05HhjZv-c_NfGvL0q6DiWx3jSN8Izk9oHMWgVBv2IfYro2Pg4WbpKYkL99yWM-ipCJPEJ9mRv4_ZAduPNAPqk-bgO7WQKbRibAJSGiWQk9AUZS1pyem6rEY4qHmsjd1mV8CdlZRQlCDpTsL1agLbNK1PERvlOFOovpkXQu0mJGm3KLcNkXBB8Zm1obbJPbNoleTz4W05KNRbHnYn-hh4iWPbbLtllCs35VFSSTNOJ0LocKSo3azlpkx0Co9fm7aK26zNcoIeWZdKCTlRSGg-cygt7d0gL9gLA3iayUqjcaIqbQ4FDNVQV3oqV1qq5PSNAfCZRuhGPyfQgN9M9SsurrWfN9VFrVkmOkI5R-eELzTzJsN9mdptlk-Q-SVETa1Z1MuwdnWJsFATHJmDwQM6jUAZFPcTpxT1jWLGkikQRUtLWlCXV4U3-T-_EXSJHcD8zZMaVaJGwSoQRdXimFtJrqo0GQpXZlWFSSLUWbhmUW0V1cbXHdtRf17gZqqrBY3z3GsPp2Ulh-otpUymHQK6N6sG53TLbmK1stiDyVZW-lOSiCER-QxVLeCSXVk07zZ2ciAitxsie9IXrbNkj6UFJV0JwQhTEsLAqGVdGJXKRFXA3RN3mSWqBRQ1yHBZ_pBK_Edcui5uu1ZowwaKpNOTJFeZydvrQeylXH50lDF3qmuz1mCMlCvGqpLPzl3g3w059Vr1ysUqrGW4ArC_EknrgDzjOD6E4JDoewGZI4sL4C6C8F3oqQdgVkEZGtwu0FJw36Il37e3_oePTzBNDAb1rm-zCKZJ9_Kcn297eiWTiA9E3ibbqTFNXY2cQLRJZNIgI_U8nyyfZ8o_gymmvmomyQdm1DMRP9DCbb5UDhFe7TN499JCS0rfl8Lh5EeBwYLWp1ChoDWcy8FHNSPbKJIXDXvYNaebc9yQPXfUJsDYMG-L1XtEcyncE4uqviL3ZgNo_THIA4aWzNAz-R_XlA0E-QnSyMN-d6nJC_vE8codS-SFdtxzsapTLYKsWphVWHZWCBFnL8p0Zfw2yTTZM64O97uW5N5jj_guMq8O71Jefx8aKATz9fME9euFHInp9nhdWr58J07Cx06QvWkNMV2FjlWVD6Rrxao2EOQArmOg1BtNsEBaZbaABVKhjoDccQ3d2BCe-g2OLlZfzeUereSIHpSUCiWYOZPzJWniPaojDDcsMpheEMtMc9YJs4jl8UZIRktItKDOJRS8Kw9qlytL_JWLdnFc9mn-uZ7NE_yFfvaiNmE7prZqbdi2kJinM7goF6Ki-wdtdnSLVrY9s93caK9evehGeSy7VcN6kQhyW68hMc8RkeHNJaVosY7H6Y3LARTYArwzk3YiiapZ4LJYN3RGtxH6aS_DfchpmdB6uxdu7ozy4OUlS-wJvKi8OqMHsfVH4ZL9qtMqfUD6pnmV6QyuAkCvsLaBB23O04lSEOUNWgar6tPvT5mKcjXdTjmO6E9u_1DV_w6hJqIGA1oT86qu0nslsRCNKLzZ0B9xHHTeB4Lt4PI05-WtreG9d-6N4sjr_-rWw76jnBlXXXdDPDb3XcXGq7DZ72nPBWmD9O3vAPCbSdUb3PNGayjSw0dSPzIgxLwZUzyMOC6qVlA-lzvalS3LsL8X1hoaywjFizJZmY1XB-_vqp-ZmW_Nxq3neJvj36Q84sLxedGHW-pZQbQbzjrEwt8lt0YXhyrFngrxnRnyPfsuVZsrJI14GpR9pH5Jj0p41R5vH53VqNvw4dT69EbXfVJTerQTBxqJWLJ1uQ-PFycWeN4eDH4a1V7r3tG7R1Lqx-bScd_sKdzbTa_znwnNf04lf4_atFvdf1aJYt1qof8iX8IjbtqvszKN5m9XidQ5PC0hbtjukmLFsbY6bLuakO43rSS-gbEj-xZtSffNioa04HkkPX8vYsltOTzO_aZ0-X6TfhiUWxZdR5nKv2uvpg-vmvWgSgt4J4ibWr8t8cJGUTPoR14Hunl1Jz7Ofwh0WlffOvsEc9tKTNjQ3F9jqntiJydsZUem4t0TtmPeabvlvNgIEjOVKgvvxsWGVTHwLL4exuPhWJ557RN1XX_WQho_7vzKKYXyVkt6nkM_56BfkZQ_9_GafuBUFuXaY2L8nWU2uZ57v3T0qwzeDp6hxGaTXmQW-JAkq-Jfd2nNnyqC2x81EKEcTkaj4eXV2fy6H_WjKOpPR-OBGsrBIFLTsRoMz4dXHyJ8np4lcqISd90Z3XYGg1StBYvAeWd0f6avB_3BoD867_c_jIb9QU_F08lFf3QRXYzi6fRq3Lnoq4XUSY_06Bk7O7PXrNIkmzlcTLTzbntRhpgqng7yZYamYa9jmWqVdHG-WDqTnrEK12zC_wAgAptr">