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
This commit is contained in:
Francesco Montorsi
2008-03-27 19:15:00 +00:00
parent 1512f0f7d8
commit 5570107a9d
4 changed files with 143 additions and 38 deletions

View File

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

View File

@@ -38,7 +38,9 @@ bool g_verbose = false;
#define API_DUMP_FILE "dump.api.txt"
#define INTERFACE_DUMP_FILE "dump.interface.txt"
#define PROCESS_ONLY_SWITCH "p"
#define PROCESS_ONLY_OPTION "p"
#define USE_PREPROCESSOR_OPTION "u"
#define MODIFY_SWITCH "m"
#define DUMP_SWITCH "d"
#define HELP_SWITCH "h"
@@ -46,7 +48,10 @@ bool g_verbose = false;
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)",

View File

@@ -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; i<m_args.GetCount(); i++)
if (!m_args[i].IsOk()) {
@@ -403,11 +407,11 @@ void wxXmlInterface::Dump(const wxString& filename)
bool wxXmlInterface::CheckParseResults() const
{
// this check can be quite slow, so do it only for debug releases:
#ifdef __WXDEBUG__
//#ifdef __WXDEBUG__
for (unsigned int i=0; i<m_classes.GetCount(); i++)
if (!m_classes[i].CheckConsistency())
return false;
#endif
//#endif
return true;
}
@@ -1163,7 +1167,7 @@ bool wxXmlDoxygenInterface::ParseMethod(const wxXmlNode* p, wxMethod& m, wxStrin
else if (n->GetName() == "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")
{

View File

@@ -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 <map>
typedef std::basic_string<char> 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 <File> 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;
};