From 5570107a9da8c64390b07e78a1842f10cfd5f75b Mon Sep 17 00:00:00 2001 From: Francesco Montorsi Date: Thu, 27 Mar 2008 19:15:00 +0000 Subject: [PATCH] add the ability to parse the gccxml preprocessor output in order to reduce the number of false positives; fix wrong wxASSERT in wxMethod::IsOk; provide more help when called with --help git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@52861 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- utils/ifacecheck/rungccxml.sh.in | 19 +++-- utils/ifacecheck/src/ifacecheck.cpp | 107 ++++++++++++++++++++++++---- utils/ifacecheck/src/xmlparser.cpp | 30 +++++--- utils/ifacecheck/src/xmlparser.h | 25 ++++--- 4 files changed, 143 insertions(+), 38 deletions(-) diff --git a/utils/ifacecheck/rungccxml.sh.in b/utils/ifacecheck/rungccxml.sh.in index a5a34265f2..fcd58240cc 100755 --- a/utils/ifacecheck/rungccxml.sh.in +++ b/utils/ifacecheck/rungccxml.sh.in @@ -11,8 +11,9 @@ ############ -gccxmloutput="wxapi.xml" # where do we put the gccXML output: -allheaders="/tmp/wx-all.h" # headers which includes all wx public headers +gccxmloutput="wxapi.xml" # the file where we store the gccXML output +preprocoutput="wxapi-preproc.txt" # the file where we store the preprocessor's #define values +allheaders="/tmp/wx-all.h" # the header which includes all wx public headers (autogenerated) # the list of all wxWidgets public headers listcmd="ls wx/*.h wx/aui/*.h wx/html/*.h wx/protocol/*.h wx/richtext/*.h wx/stc/*.h wx/xml/*.h wx/xrc/*.h" @@ -52,14 +53,22 @@ done flags="@CXXFLAGS@" # NOTE: it's important to define __WXDEBUG__ because some functions of wx -# are declared (and thus parsed by gcc) only if that symbol is defined; -# so we remove __WXDEBUG__ symbol from $flags, in case it's defined: +# are declared (and thus parsed by gcc) only if that symbol is defined. +# So we remove it from $flags (in case it's defined) and then readd it. flags=`echo "$flags" | sed -e 's/-pthread//g' | sed -e 's/__WXDEBUG__//g'` +# append some other flags: +flags="-I . -I @top_srcdir@/include $flags -D__WXDEBUG__ -D__WX@TOOLKIT@__ -DWXBUILDING $allheaders" + # run gccxml with the same flag used for the real compilation of wx sources: echo "Running gccxml on the $allheaders file..." if [[ -f "$gccxmloutput" ]]; then rm $gccxmloutput; fi -gccxml -I . -I @top_srcdir@/include $flags -D__WX@TOOLKIT@__ -DWXBUILDING $allheaders -fxml=$gccxmloutput +gccxml $flags -fxml=$gccxmloutput + +# now get the list of the #defined values for wx headers, so that the result +# can be passed to ifacecheck to aid the comparison +echo "Running gccxml's preprocessor on the $allheaders file..." +gccxml -E -dM $flags >$preprocoutput # cleanup rm $allheaders diff --git a/utils/ifacecheck/src/ifacecheck.cpp b/utils/ifacecheck/src/ifacecheck.cpp index 033e29e5a1..d2c63869e6 100644 --- a/utils/ifacecheck/src/ifacecheck.cpp +++ b/utils/ifacecheck/src/ifacecheck.cpp @@ -35,18 +35,23 @@ bool g_verbose = false; // IfaceCheckApp // ---------------------------------------------------------------------------- -#define API_DUMP_FILE "dump.api.txt" -#define INTERFACE_DUMP_FILE "dump.interface.txt" +#define API_DUMP_FILE "dump.api.txt" +#define INTERFACE_DUMP_FILE "dump.interface.txt" -#define PROCESS_ONLY_SWITCH "p" -#define MODIFY_SWITCH "m" -#define DUMP_SWITCH "d" -#define HELP_SWITCH "h" -#define VERBOSE_SWITCH "v" +#define PROCESS_ONLY_OPTION "p" +#define USE_PREPROCESSOR_OPTION "u" + +#define MODIFY_SWITCH "m" +#define DUMP_SWITCH "d" +#define HELP_SWITCH "h" +#define VERBOSE_SWITCH "v" static const wxCmdLineEntryDesc g_cmdLineDesc[] = { - { wxCMD_LINE_OPTION, PROCESS_ONLY_SWITCH, "process-only", + { wxCMD_LINE_OPTION, USE_PREPROCESSOR_OPTION, "use-preproc", + "uses the preprocessor output to increase the checker accuracy", + wxCMD_LINE_VAL_STRING, wxCMD_LINE_NEEDS_SEPARATOR }, + { wxCMD_LINE_OPTION, PROCESS_ONLY_OPTION, "process-only", "processes only header files matching the given wildcard", wxCMD_LINE_VAL_STRING, wxCMD_LINE_NEEDS_SEPARATOR }, { wxCMD_LINE_SWITCH, MODIFY_SWITCH, "modify", @@ -71,6 +76,8 @@ public: virtual bool OnInit() { m_modify=false; return true; } virtual int OnRun(); + bool ParsePreprocessorOutput(const wxString& filename); + bool Compare(); int CompareClasses(const wxClass* iface, const wxClassPtrArray& api); void FixMethod(const wxString& header, const wxMethod* iface, const wxMethod* api); @@ -92,7 +99,7 @@ protected: // was the MODIFY_SWITCH passed? bool m_modify; - // if non-empty, then PROCESS_ONLY_SWITCH was passed and this is the + // if non-empty, then PROCESS_ONLY_OPTION was passed and this is the // wildcard expression to match wxString m_strToMatch; }; @@ -103,19 +110,33 @@ int IfaceCheckApp::OnRun() { long startTime = wxGetLocalTime(); // for timing purpose - // parse the command line... wxCmdLineParser parser(g_cmdLineDesc, argc, argv); + parser.SetLogo( + wxString::Format("wxWidgets Interface checker utility (built %s against %s)", + __DATE__, wxVERSION_STRING)); + + // parse the command line... bool ok = true; + wxString preprocFile; switch (parser.Parse()) { - case -1: - // HELP_SWITCH was passed - return 0; - case 0: if (parser.Found(VERBOSE_SWITCH)) g_verbose = true; + // IMPORTANT: parsing #define values must be done _before_ actually + // parsing the GCC/doxygen XML files + if (parser.Found(USE_PREPROCESSOR_OPTION, &preprocFile)) + { + if (!ParsePreprocessorOutput(preprocFile)) + return 1; + } + + // in any case set basic std preprocessor #defines: + m_interface.AddPreprocessorValue("NULL", "0"); + + // parse the two XML files which contain the real and the doxygen interfaces + // for wxWidgets API: if (!m_api.Parse(parser.GetParam(0)) || !m_interface.Parse(parser.GetParam(1))) return 1; @@ -133,7 +154,7 @@ int IfaceCheckApp::OnRun() if (parser.Found(MODIFY_SWITCH)) m_modify = true; - if (parser.Found(PROCESS_ONLY_SWITCH, &m_strToMatch)) + if (parser.Found(PROCESS_ONLY_OPTION, &m_strToMatch)) { size_t len = m_strToMatch.Len(); if (m_strToMatch.StartsWith("\"") && @@ -147,6 +168,20 @@ int IfaceCheckApp::OnRun() PrintStatistics(wxGetLocalTime() - startTime); return ok ? 0 : 1; + + default: + wxPrintf("\nThis utility checks that the interface XML files created by Doxygen are in\n"); + wxPrintf("synch with the real headers (whose contents are extracted by the gcc XML file).\n\n"); + wxPrintf("The 'gccXML' parameter should be the wxapi.xml file created by the 'rungccxml.sh'\n"); + wxPrintf("script which resides in 'utils/ifacecheck'.\n"); + wxPrintf("The 'doxygenXML' parameter should be the index.xml file created by Doxygen\n"); + wxPrintf("for the wxWidgets 'interface' folder.\n\n"); + wxPrintf("Since the gcc XML file does not contain info about #defines, if you use\n"); + wxPrintf("the -%s option, you'll get a smaller number of false warnings.\n", + USE_PREPROCESSOR_OPTION); + + // HELP_SWITCH was passed or a syntax error occurred + return 0; } return 1; @@ -462,6 +497,48 @@ void IfaceCheckApp::FixMethod(const wxString& header, const wxMethod* iface, con } } +bool IfaceCheckApp::ParsePreprocessorOutput(const wxString& filename) +{ + wxTextFile tf; + if (!tf.Open(filename)) { + LogError("can't open the '%s' preprocessor output file.", filename); + return false; + } + + size_t useful = 0; + for (unsigned int i=0; i < tf.GetLineCount(); i++) + { + const wxString& line = tf.GetLine(i); + wxString defnameval = line.Mid(8); // what follows the "#define " string + + // the format of this line should be: + // #define DEFNAME DEFVALUE + if (!line.StartsWith("#define ") || !defnameval.Contains(" ")) { + LogError("unexpected content in '%s' at line %d.", filename, i); + return false; + } + + // get DEFNAME + wxString defname = defnameval.BeforeFirst(' '); + if (defname.Contains("(")) + continue; // this is a macro, skip it! + + // get DEFVAL + wxString defval = defnameval.AfterFirst(' ').Strip(wxString::both); + if (defval.StartsWith("(") && defval.EndsWith(")")) + defval = defval.Mid(1, defval.Len()-2); + + // store this pair in the doxygen interface, where it can be useful + m_interface.AddPreprocessorValue(defname, defval); + useful++; + } + + LogMessage("Parsed %d preprocessor #defines from '%s' which will be used later...", + useful, filename); + + return true; +} + void IfaceCheckApp::PrintStatistics(long secs) { LogMessage("wx real headers contains declaration of %d classes (%d methods)", diff --git a/utils/ifacecheck/src/xmlparser.cpp b/utils/ifacecheck/src/xmlparser.cpp index d694d9a87c..69d5684f1d 100644 --- a/utils/ifacecheck/src/xmlparser.cpp +++ b/utils/ifacecheck/src/xmlparser.cpp @@ -115,10 +115,11 @@ bool wxType::operator==(const wxType& m) const // wxArgumentType // ---------------------------------------------------------------------------- -void wxArgumentType::SetDefaultValue(const wxString& defval) +void wxArgumentType::SetDefaultValue(const wxString& defval, const wxString& defvalForCmp) { m_strDefaultValue=defval.Strip(wxString::both); - + m_strDefaultValueForCmp=defvalForCmp.Strip(wxString::both); +/* // in order to make valid&simple comparison on argument defaults, // we reduce some of the multiple forms in which the same things may appear // to a single form: @@ -128,7 +129,7 @@ void wxArgumentType::SetDefaultValue(const wxString& defval) m_strDefaultValue.Replace("0", "NULL"); else m_strDefaultValue.Replace("NULL", "0"); - +*/ if (m_strDefaultValue.Contains("wxGetTranslation")) m_strDefaultValue = "_(TOFIX)"; // TODO: wxGetTranslation gives problems to gccxml @@ -139,7 +140,10 @@ bool wxArgumentType::operator==(const wxArgumentType& m) const if ((const wxType&)(*this) != (const wxType&)m) return false; - if (m_strDefaultValue != m.m_strDefaultValue) + const wxString& def1 = m_strDefaultValueForCmp.IsEmpty() ? m_strDefaultValue : m_strDefaultValueForCmp; + const wxString& def2 = m.m_strDefaultValueForCmp.IsEmpty() ? m.m_strDefaultValue : m.m_strDefaultValueForCmp; + + if (def1 != def2) return false; // we deliberately avoid checks on the argument name @@ -170,7 +174,7 @@ bool wxMethod::IsOk() const return false; } - wxASSERT((m_bVirtual && m_bPureVirtual) || !m_bVirtual); + wxASSERT(!m_bPureVirtual || (m_bPureVirtual && m_bVirtual)); for (unsigned int i=0; iGetName() == "declname") namestr = GetTextFromChildren(n); else if (n->GetName() == "defval") - defstr = GetTextFromChildren(n); + defstr = GetTextFromChildren(n).Strip(wxString::both); else if (n->GetName() == "array") arrstr = GetTextFromChildren(n); @@ -1175,7 +1179,15 @@ bool wxXmlDoxygenInterface::ParseMethod(const wxXmlNode* p, wxMethod& m, wxStrin return false; } - args.Add(wxArgumentType(typestr + arrstr, defstr, namestr)); + wxArgumentType newarg(typestr + arrstr, defstr, namestr); + + // can we use preprocessor output to transform the default value + // into the same form which gets processed by wxXmlGccInterface? + wxStringHashMap::const_iterator it = m_preproc.find(defstr); + if (it != m_preproc.end()) + newarg.SetDefaultValue(defstr, it->second); + + args.Add(newarg); } else if (child->GetName() == "location") { diff --git a/utils/ifacecheck/src/xmlparser.h b/utils/ifacecheck/src/xmlparser.h index a002f4244d..6e1de99a42 100644 --- a/utils/ifacecheck/src/xmlparser.h +++ b/utils/ifacecheck/src/xmlparser.h @@ -80,7 +80,7 @@ public: wxString GetArgumentName() const { return m_strArgName; } - void SetDefaultValue(const wxString& defval); + void SetDefaultValue(const wxString& defval, const wxString& defvalForCmp = wxEmptyString); wxString GetDefaultValue() const { return m_strDefaultValue; } @@ -93,6 +93,11 @@ public: protected: wxString m_strDefaultValue; + + // this string may differ from m_strDefaultValue if there were + // preprocessor substitutions; can be wxEmptyString. + wxString m_strDefaultValueForCmp; + wxString m_strArgName; // this one only makes sense when this wxType is // used as argument type (and not as return type) // and can be empty. @@ -312,6 +317,8 @@ protected: WX_DECLARE_HASH_MAP( unsigned long, wxString, wxIntegerHash, wxIntegerEqual, wxTypeIdHashMap ); + +WX_DECLARE_STRING_HASH_MAP( wxString, wxStringHashMap ); #else #include typedef std::basic_string stlString; @@ -328,14 +335,6 @@ class wxXmlGccInterface : public wxXmlInterface public: wxXmlGccInterface() {} - // !!SPEEDUP-TODO!! - // Using wxXmlDocument::Load as is, all the types contained in the - // the entire gccXML file are stored in memory while parsing; - // however we are only interested to wx's own structs/classes/funcs/etc - // so that we could use the file IDs to avoid loading stuff which does - // not belong to wx. See the very end of the gccXML file: it contains - // a set of nodes referenced by all nodes above. - bool Parse(const wxString& filename); bool ParseMethod(const wxXmlNode *p, const wxTypeIdHashMap& types, @@ -363,6 +362,14 @@ public: bool Parse(const wxString& filename); bool ParseCompoundDefinition(const wxString& filename); bool ParseMethod(const wxXmlNode*, wxMethod&, wxString& header); + + // this class can take advantage of the preprocessor output to give + // a minor number of false positive warnings in the final comparison + void AddPreprocessorValue(const wxString& name, const wxString& val) + { m_preproc[name]=val; } + +protected: + wxStringHashMap m_preproc; }; -- 2.47.2