From 805a845b9d39ad0c6707d01b3b714d60badd728d Mon Sep 17 00:00:00 2001
From: Giovanni Bussi <giovanni.bussi@gmail.com>
Date: Thu, 28 Jul 2016 10:48:40 +0200
Subject: [PATCH] Implemented plumedcheck

I added a awk script src/maketools/plumedcheck
that works similarly to cppcheck. It takes as argument
a list of files and analyze them.
---
 src/maketools/plumedcheck | 355 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 355 insertions(+)
 create mode 100755 src/maketools/plumedcheck

diff --git a/src/maketools/plumedcheck b/src/maketools/plumedcheck
new file mode 100755
index 000000000..1ed3c9fcf
--- /dev/null
+++ b/src/maketools/plumedcheck
@@ -0,0 +1,355 @@
+#! /usr/bin/env gawk -E
+# needs gawk
+
+# print usage 
+function usage(){
+  print "Usage:"
+  print " plumedcheck [options] [files]"
+  print
+  print "Possible options are:"
+  print " -h/--help: print help"
+  print " --global-check: run global check; requires all sources and ac files to be passed"
+  print
+  print "Files can be"
+  print " .h files: interpreted as c++ header file"
+  print " .cpp files: interpreted as c++ source file"
+  print " .ac files: interpreted as autoconf files"
+  exit(0)
+}
+
+# print message
+function message(severity,id,mes){
+  if(global_check){
+    printf("[global_check] (%s) :plmd_%s: %s\n",severity,id,mes) > "/dev/stderr";
+  } else {
+    printf("[%s:%s] (%s) :plmd_%s: %s\n",FILENAME,FNR,severity,id,mes) > "/dev/stderr";
+  }
+}
+
+# print error message
+# error messages make codecheck fail
+function error(id,mes){
+  message("error",id,mes)
+}
+
+# print information message
+# information messages are neutral
+function information(id,mes){
+  message("information",id,mes)
+}
+
+# print style message
+# style messages are possibly bad ideas but do not trigger errors
+function style(id,mes){
+  message("style",id,mes)
+}
+
+BEGIN{
+# interpret command line arguments
+  newargv[0]=ARGV[0]
+  pre=""
+  opt_global_check=0
+  for(i=1;i<ARGC;i++){
+    opt=pre ARGV[i]
+    pre=""
+    switch(opt){
+      case "-h":
+      case "--help":
+        usage(); break;
+      case "--global-check":
+        opt_global_check=1; break;
+      case /-.*/:
+        print "Unknown option " opt; exit(1)
+      default:
+        newargv[length(newargv)]=opt
+    }
+  }
+# copy new command arguments
+  ARGC=length(newargv)
+  for(i=1;i<ARGC;i++) ARGV[i]=newargv[i]
+
+# no arguments, print help
+  if(ARGC==1) usage()
+
+
+# setup:
+
+# list of deprecated c++ headers
+  deprecated_headers["complex.h"]=1
+  deprecated_headers["ctype.h"]=1
+  deprecated_headers["errno.h"]=1
+  deprecated_headers["fenv.h"]=1
+  deprecated_headers["float.h"]=1
+  deprecated_headers["inttypes.h"]=1
+  deprecated_headers["iso646.h"]=1
+  deprecated_headers["limits.h"]=1
+  deprecated_headers["locale.h"]=1
+  deprecated_headers["math.h"]=1
+  deprecated_headers["setjmp.h"]=1
+  deprecated_headers["signal.h"]=1
+  deprecated_headers["stdalign.h"]=1
+  deprecated_headers["stdarg.h"]=1
+  deprecated_headers["stdbool.h"]=1
+  deprecated_headers["stddef.h"]=1
+  deprecated_headers["stdint.h"]=1
+  deprecated_headers["stdio.h"]=1
+  deprecated_headers["stdlib.h"]=1
+  deprecated_headers["string.h"]=1
+  deprecated_headers["tgmath.h"]=1
+}
+
+# for each input file
+BEGINFILE{
+# these variables are used to track if a file containes at least one item of a specific kind
+# as such they should be reset for every new input file
+  found_namespace_plmd=0
+  found_namespace_module=0
+  found_guard_ifdef=0
+  found_non_preprocessor_lines=0
+
+## not used, see below
+#  found_include_system_header=0
+#  report_include_system_headers=0
+
+# guess module name from directory
+# only works when path is specified
+  filename=FILENAME
+  gsub("/[.]/","/",filename);
+  gsub("^[.]/","",filename);
+  if(!match(filename,".*/.*")) filename=ENVIRON["PWD"] "/" filename
+  gsub("/+","/",filename); # fix multiple slashes
+  module=""
+  nf=split(filename,ff,"/");
+  if(nf>1){
+    module=ff[nf-1];
+    if(!(module in module_type)){
+      path=filename
+      sub("/.*$","/module.type",path);
+      if((getline < path > 0) ){
+        module_type[module]=$0
+        information("module_type","module " module " has type '" module_type[module] "'" );
+      }
+    }
+  }
+  file=ff[nf];
+  filebase=file;
+  sub("\\..*","",filebase);
+
+# guess type of file from extension
+  filetype=""
+  if(match(FILENAME,".*\\.h")) filetype="header"
+  if(match(FILENAME,".*\\.cpp")) filetype="source"
+  if(match(FILENAME,".*\\.ac")) filetype="autoconf"
+
+# checks that are done only on some modules will be skipped if the module name is not known
+  if((filetype=="source" || filetype=="header") && !module) information("missing_module_name","module name is not know, some check will be skipped");
+
+}
+
+# line by line analysis
+{
+  if(filetype=="source" || filetype=="header"){
+# detect plumed manual
+    if(match($0,"^//[+]PLUMEDOC ")) in_plumed_doc=$NF
+# detect doxygen manual - at least with /** and */ on separate lines
+    if($1=="/**") in_doxygen=1
+# detect copyright lines
+    if($0=="/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++") in_copyright=1;
+# check direct inclusion of headers that would bypass module dependencies
+    if(match($0,"^# *include +\\\"\\.\\.")) error("include_dots","cannot include files using .. path");
+
+# check if there is anything behind copyright and preprocessor commands
+    if(!in_copyright && !match($0,"^# *include") && !match($0,"^# *ifndef") && !match($0,"^# *endif") && !match($0,"^# *define") && !match($0,"^ *$")) found_non_preprocessor_lines=1
+
+## check if system headers are included after local headers.
+## I am not sure it is a good idea, so I let it commented for now.
+#    if(match($0,"^# *include +<")) found_include_system_header=1
+## it is a bad habit to include local headers after system headers
+## notice that I explicitly check for the final "h" in the included file so as to exclude
+## included files ending with tmp that are somewhere used in plumed to include automatically generated source code
+#   if(match($0,"^# *include +\\\".*h\\\"")){
+#     if(found_include_system_header && !report_include_system_headers){
+#       style("include_system_headers","include system headers before local headers is discouraged");
+#       report_include_system_headers=1 # only report once per file
+#     }
+#   }
+
+# check if deprecated headers are included
+    if(match($0,"^# *include +<[^>]+>.*$")){
+      h=$0
+      sub("^# *include +<","",h)
+      sub(">.*$","",h)
+      if(h in deprecated_headers) error("deprecated_header","including deprecated header " h);
+    }
+
+# check if namespace PLMD is present
+    if(match($0,"^ *namespace *PLMD")) found_namespace_plmd=1
+
+# check if namespace for module is present
+    if(match($0,"^ *namespace *" module)) found_namespace_module=1
+
+# take note of registered actions
+    if(match($0,"^ *PLUMED_REGISTER_ACTION")){
+      action=$0
+      sub("^ *PLUMED_REGISTER_ACTION\\(.*, *[\"]","",action);
+      sub("[\"].*$","",action);
+      information("registered_action","action " action);
+      if(action in registered_actions) error("multi_registered","action " action " already registered at "registered_actions[action]);
+      registered_actions[action]=FILENAME ":" FNR;
+    }
+
+# take note of registered cltools
+    if(match($0,"^ *PLUMED_REGISTER_CLTOOL")){
+      cltool=$0
+      sub("^ *PLUMED_REGISTER_CLTOOL\\(.*, *[\"]","",cltool);
+      sub("[\"].*$","",cltool);
+      information("registered_cltool","cltool " cltool);
+      if(cltool in registered_cltools) error("multi_registered","cltool " cltool " already registered at "registered_cltools[cltool]);
+      registered_cltools[cltool]=FILENAME ":" FNR;
+    }
+
+# take note of documented actions or cltools
+    if(match($0,"^//[+]PLUMEDOC ")){
+      doc=$NF;
+      information("documented","doc " doc);
+      if(doc in plumed_doc) error("multi_doc","doc " doc " already at "plumed_doc[action]);
+      plumed_doc[doc]=FILENAME ":" FNR;
+    }
+
+# check if, besides the \par Examples text, there is a verbatim command
+    if(in_plumed_doc && (in_plumed_doc in provide_examples) && match($0,"^ *\\\\verbatim *$")){
+      provide_verbatim[in_plumed_doc]=1
+    }
+#print match($0,"^ *\\\\verbatim *$"),"X" $0 "X"
+
+# take note of actions or cltools that provide examples
+    if(match($0,"\\par Examples")){
+      provide_examples[in_plumed_doc]=FILENAME ":" FNR;
+    }
+
+# check if guard is present
+    guard="^#ifndef __PLUMED_" module "_" filebase "_h"
+    if(match($0,guard)) found_guard_ifdef=1
+
+# check that "using namespace" statements are not used in header files
+    if(filetype=="header" && match($0,"using *namespace") && !in_plumed_doc && !in_doxygen) error("using_namespace_in_header","using namespace statement in header file")
+  }
+
+# analyze HAS macros (bouth source and autoconf)
+  if(match($0,"__PLUMED_HAS_[A-Z_]*")){
+    string=substr( $0, RSTART, RLENGTH )
+    sub("__PLUMED_HAS_","",string);
+    if(filetype=="autoconf"){
+# take note of defined HAS macros
+      information("defined_has",string);
+      plumed_definehas[string]=FILENAME ":" FNR;
+    } else {
+# take note of used HAS macros
+      information("used_has",string);
+      plumed_usehas[string]=FILENAME ":" FNR;
+    }
+  }
+
+  if(filetype=="source" || filetype=="header"){
+# detect plumed manual
+    if(match($0,"^//[+]ENDPLUMEDOC")) in_plumed_doc=0
+# detect doxygen manual - at least with /** and */ on separate lines
+    if($1=="*/") in_doxygen=0
+# detect copyright lines
+    if($0=="+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */") in_copyright=0;
+  }
+
+}
+
+# at the end of each file:
+ENDFILE{
+# check for namespaces
+# this is done only for non-trivial files (i.e. we skip files that just include an header)
+# we also skip special modules wrapper and main here, which do not define PLMD namespace
+  if(found_non_preprocessor_lines && (filetype=="source" || filetype=="header") && module!="wrapper" && module!="main" ){
+    if(!found_namespace_plmd) error("missing_namespace_plmd","missing PLMD namespace");
+# files belonging to "core modules" are not supposed to have a module namespace
+      if(module!="core" && module!="tools" && module!="reference"){
+      # this can be done only if module name is known:
+      if(module && !found_namespace_module) error("missing_namespace_module","missing " module " namespace")
+    }
+  }
+
+# check if header guard is present
+# check is done only if module is known since module name is used to build the macro name
+  if(filetype=="header"){
+    # this can be done only if module name is known:
+    if(module && !found_guard_ifdef) error("missing_guard","missing ifndef guard");
+  }
+
+# check if every header has a corresponding cpp
+# this is done only for non-trivial headers (i.e. we skip headers that just include another header)
+   if(filetype=="header" && found_non_preprocessor_lines){
+     notfound=0
+     cppfile=FILENAME
+     sub("\\.h$",".cpp",cppfile);
+     if((getline < cppfile < 0)){
+       cppfile=FILENAME
+       sub("\\.h$",".c",cppfile);
+       if((getline < cppfile < 0)) notfound=1
+     }
+     if(notfound){
+       error("non_existing_cpp","file " file " is a header but there is no corresponding source");
+     }
+     while((getline < cppfile)>=0){
+       if(match($0,"^ *#include")){
+         sub("^ *#include","");
+         if($1!="\"" file "\"") error("non_included_h","file " file " is a header but " cppfile " does not include it as first include");
+         break;
+       }
+     }
+   }
+}
+
+# after processing all files, perform some global check
+END{
+
+# checks are only done if enabled
+  if(!opt_global_check) exit(0)
+
+# this is required to properly report messages
+  global_check=1
+
+# check that all registered actions were documented
+  for(action in registered_actions){
+    if(!(action in plumed_doc)){
+      error("undocumented_action","action " action " at " registered_actions[action] " is not documented")
+    } else if(!(action in provide_examples)){
+      error("action_without_examples","action " action " at " plumed_doc[action] " has no example")
+    } else if(!(action in provide_verbatim)){
+# this should be made into an error:
+      style("action_without_verbatim","action " action " at " provide_examples[action] " has no verbatim")
+    }
+  }
+
+# check that all registered cltools were documented
+  for(cltool in registered_cltools){
+    if(!(cltool in plumed_doc)){
+      error("undocumented_cltool","cltool " cltool " at " registered_cltools[cltool] " is not documented")
+    } else if(!(cltool in provide_examples)){
+      error("cltool_without_examples","cltool " cltool " at " plumed_doc[cltool] " has no example")
+    } else if(!(cltool in provide_verbatim)){
+      error("cltool_without_verbatim","cltool " cltool " at " provide_examples[cltool] " has no verbatim")
+    }
+  }
+
+# check that all used __PLUMED_HAS macros have been defined in autoconf
+  for(has in plumed_usehas){
+    if(!(has in plumed_definehas)){
+      error("undefined_has","has " has " at " plumed_usehas[has] " is not defined")
+    }
+  }
+
+# check that all defined  __PLUMED_HAS macros have been used in source code
+  for(has in plumed_definehas){
+    if(!(has in plumed_usehas)){
+      error("unused_has","has " has " at " plumed_definehas[has] " is not used")
+    }
+  }
+}
+
-- 
GitLab