<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">