ConstraintManager reports different constraints if the checker is run as Plugin

Hi,

I'm currently trying to write a static analyzer using llvm and started
with a simple example, that checks if the return value of malloc/calloc
was checked to be non-null before accessing it.

While testing I noticed, that the ConstraintManager shows inconsistent
behaviors. If the checker is compiled in-tree everything works as
expected. But if the checker is compiled as Plugin and run with
-analyzer-checker=core.NonNullParamChecker and -fgnuc-version=4.2.1
enabled, the ConstraintManager returns different constraints.

Example:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main(int argc, char ** argv, char ** envp){
        int z=8;
        int *p = malloc(sizeof(int));
        memcpy(p,&z,sizeof(int));
        free(p);
    }

If compiled with llvm the ConstraintManager.isNull reports isUnderconstrained for the symbol p pre memcpy.
If compiled as Plugin it returns true for isConstrainedFalse and isConstrained, but p can be null here.

I have posted my code on Github if anyone wants to look at it: https://github.com/schrc3b6/simple-static-analyzer

Is this a bug in my Code or am I misunderstanding something?

Thanks
Max

I suspect that you’re still running different sets of checks in different invocations. Typically you don’t want to use direct/manual -cc1 invocations as they’re completely unrepresentative of the actual behavior.

One way or another, you should dump the exploded graph to see where constraints originate and how they propagate (, )

Hi,

One way or another, you should dump the exploded graph to see where
constraints originate and how they propagate
(2019 LLVM Developers’ Meeting: A. Dergachev “Developing the Clang Static Analyzer” - YouTube,
Checker Developer Manual)

thanks, great talk. The exploded-graph-rewriter.py should be in the
documentation, it really helped, thanks!

To my Problem:

We want Students to write better C-Code including checking system calls for errors.
This partially overlaps with the NonNullParamChecker, because some
system calls return NULL on error. Even though most system calls like
memcpy have the nonnull attribute the NonNullParamChecker only reports
a Bug if the Parameter is only NULL and not can be NULL. (see line 179
NonNullParamChecker)

Example:

    int *p = malloc(sizeof(int));
    memcpy(p,&z,sizeof(int));
No bug is reported.

    int *p = NULL
    memcpy(p,&z,sizeof(int));
Bug is reported.

I however want bugs to be filed in the first case. The problem is that
the NonNullParamChecker generates a branch for NULL and not
NULL. In the NULL branch a Sink node is generated. Because of that if I
write a Checker that is in the lexical order behind the
Null Checkers the checkPreCall of my Checker is never called for the
NULL case (where the Sink was generated).

I don't want to write a checker that only works if it runs before other
checkers.

Is there a way to solve this?

Best Regards
Max