Feature: libClang PETSc Macro Static Analyzer
Requires
- libclang python bindings
pip install clang
- Working clang installation, specifically location of
libclang.dylib
.
Note for macOS v11.3 the following is no longer relevant, you may just use either of the two standard locations, or let the linter find libclang
automatically.
Getting this to work on macOS v11.2.3 and lower was an absolute nightmare, you must do the following:
// install CLT if not already installed, should already be installed
xcode-select --install
// Switch from XCode app to CLT, as the math.h header files from XCode are broken as of latest macOS 11.2.3 [1]
sudo xcode-select -s /Library/Developer/CommandLineTools
[1]: see here.
Then when running petscClangParser.py
you must set option --clang_[dir|lib]
to use the XCode libclang
. No that is not a typo, using either XCode headers+CLT libclang or CLT headers+CLT libclang or any other combination does NOT work, believe me I tried them all:
$ [python3] petscClangParser.py --clang_dir = '/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/'
You may have to uninstall and reinstall XCode the above to work...
Alternatively you can do brew install llvm
and use the headers and libclang
all from the LLVM install (but only from LLVM! mixing these with either xcode or CLT won't work either).
Functionality
Allows linting of all of the bug-prone macros in petsc source with the introspective powers of clangs AST. Currently catches errors in PetscValid[XXX]Pointer()
, PetscValidLogicalCollective[XXX]()
, PetscValid[Type|HeaderSpecific[Type]]()
, PetscCheckSame[Type|Comm]()
, MatCheckXXX()
, VecNestCheckCompatible[2|3]()
.
It is capable of:
- Catching type mismatches between the object and the provided PetscClassId (although requires manual tabulation of this mapping).
PetscErrorCode somePetscVecFunction(Vec v)
{
...
PetscValidHeaderSpecific(v,MAT_CLASSID,1); // Error! Expected VEC_CLASSID
- Catching pointer type mismatch of object.
PetscErrorCode somePetscVecFunction(Vec *v)
{
...
PetscValidHeaderSpecific(v,VEC_CLASSID,1); // Error! Expected _p_Vec * not _p_Vec **
- Catching argument number mismatch. (An example from current main in HDF5
PetscViewer
impls)
PetscErrorCode PetscViewerHDF5ReadObjectAttribute(PetscViewer viewer, PetscObject obj, const char name[], PetscDataType datatype, void *defaultValue, void *value)
{
...
PetscValidPointer(value, 5);
In this particular example the output is:
ERROR 0: /Users/jacobfaibussowitsch/NoSync/petsc/src/sys/classes/viewer/impls/hdf5/hdf5v.c:28:1070
'5' of derived type 'int', canonical type 'int'
> 1070: PetscValidPointer(value, 5);
^
Argument number doesn't match for 'value'. Found '5' expected '6' from
> 1062: PetscErrorCode PetscViewerHDF5ReadObjectAttribute(PetscViewer viewer, PetscObject obj, const char name[], PetscDataType datatype, void *defaultValue, void *value)
^^^^^^^^^^^
Extending it
It is built to be trivially extensible to more macros as each "test" is built from fundamental test building blocks. To add an additional macro (say PetscCheckMyMacro(object,argidx,object2,classid)
to the analyzer one simply must:
- Add a template definition of the macro to
petscastfix.hpp
.
template <typename Ta, typename Tb>
void PetscCheckMyMacro(T,int,Tb,int);
- Gate the original macro definition around
#if !defined(PETSC_AST_FIX) #endif
.
#if !defined(PETSC_AST_FIX)
#define PetscCheckMyMacro(obj,argidx,object2,classid) do {\
...
} while (0)
#endif
- Create a test function and register it in
checkFunctionMap
dictionary inpetscClangLinter.py
def checkPetscCheckMyMacro(linter,func,parent):
# (Almost) universal header for all tests, formats the arguments and extracts any necessary informationn
try:
funcArgs = tuple(PetscCursor(a,i+1) for i,a in enumerate(func.get_arguments()))
parentArgs = tuple(PetscCursor(a,i+1) for i,a in enumerate(parent.get_arguments()))
except ParsingError as pe:
linter.addWarning(pe)
return
# extract each argument
objA,idxA,objB,classid = funcArgs
# perform tests!
checkMatchingArgNum(badSource,objA,idxA,parentArgs) # check the argument index is correct
checkMatchingClassid(badSource,objB,classid) # check the classid is sane
fltr = FilterFunctor(scalarTypes,func,successHook=checkIsPetscScalarAndNotPetscReal)
checkMatchingSpecificType(badSource,obj,fltr) # can even perform checks for type-correctness between fuzzy petsc-types
return
checkFunctionMap = {
...
"PetscCheckMyMacro" : checkPetscCheckMyMacro,
}
How it works
Normally macros cannot carry any type information, and are handled/expanded before the AST is built, however I get around this by:
-
#if def
'ing out the original macros:
#if !defined(PETSC_CLANG_STATIC_ANALYZER)
#define PetscValidHeaderSpecific(...)
...
- Redefining them as a templated function below
#else
template <typename T>
void PetscValidHeaderSpecific(T,PetscClassId,int);
#endif
- Defining
PETSC_CLANG_STATIC_ANALYZER
during compilation and forcing everything to be compiled as cpp
flags = ['-x','c++','-D','PETSC_CLANG_STATIC_ANALYZER',...]
index = clang.cindex.Index.create()
tu = index.parse(src,args=flags)