X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/5570107a9da8c64390b07e78a1842f10cfd5f75b..84ce7b7dba590a3e6042190b79a3b1c0908b0a4c:/utils/ifacecheck/src/ifacecheck.cpp diff --git a/utils/ifacecheck/src/ifacecheck.cpp b/utils/ifacecheck/src/ifacecheck.cpp index d2c63869e6..75831e5994 100644 --- a/utils/ifacecheck/src/ifacecheck.cpp +++ b/utils/ifacecheck/src/ifacecheck.cpp @@ -3,7 +3,6 @@ // Purpose: Interface headers <=> real headers coherence checker // Author: Francesco Montorsi // Created: 2008/03/17 -// RCS-ID: $Id$ // Copyright: (c) 2008 Francesco Montorsi // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// @@ -18,6 +17,7 @@ // for all others, include the necessary headers #ifndef WX_PRECOMP #include "wx/app.h" + #include "wx/crt.h" #endif #include "wx/cmdline.h" @@ -69,6 +69,19 @@ static const wxCmdLineEntryDesc g_cmdLineDesc[] = wxCMD_LINE_DESC_END }; +class IfaceCheckLog : public wxLog +{ +public: + IfaceCheckLog() {} + + virtual void DoLogText(const wxString& msg) + { + // send all messages to stdout (normal behaviour is to sent them to stderr) + wxPuts(msg); + fflush(stdout); + } +}; + class IfaceCheckApp : public wxAppConsole { public: @@ -79,10 +92,10 @@ public: 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); + int CompareClasses(const wxClass* iface, const wxClass* api); + bool FixMethod(const wxString& header, const wxMethod* iface, const wxMethod* api); + bool StringContainsMethodName(const wxString& str, const wxMethod* m); - void ShowProgress(); void PrintStatistics(long secs); bool IsToProcess(const wxString& headername) const @@ -93,8 +106,8 @@ public: } protected: - wxXmlGccInterface m_api; // "real" headers API - wxXmlDoxygenInterface m_interface; // doxygen-commented headers API + wxXmlGccInterface m_gccInterface; // "real" headers API + wxXmlDoxygenInterface m_doxyInterface; // doxygen-commented headers API // was the MODIFY_SWITCH passed? bool m_modify; @@ -115,6 +128,10 @@ int IfaceCheckApp::OnRun() wxString::Format("wxWidgets Interface checker utility (built %s against %s)", __DATE__, wxVERSION_STRING)); + // make the output more readable: + wxLog::SetActiveTarget(new IfaceCheckLog); + wxLog::DisableTimestamp(); + // parse the command line... bool ok = true; wxString preprocFile; @@ -133,21 +150,21 @@ int IfaceCheckApp::OnRun() } // in any case set basic std preprocessor #defines: - m_interface.AddPreprocessorValue("NULL", "0"); + m_doxyInterface.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))) + if (!m_gccInterface.Parse(parser.GetParam(0)) || + !m_doxyInterface.Parse(parser.GetParam(1))) return 1; if (parser.Found(DUMP_SWITCH)) { - LogMessage("Dumping real API to '%s'...", API_DUMP_FILE); - m_api.Dump(API_DUMP_FILE); + wxLogMessage("Dumping real API to '%s'...", API_DUMP_FILE); + m_gccInterface.Dump(API_DUMP_FILE); - LogMessage("Dumping interface API to '%s'...", INTERFACE_DUMP_FILE); - m_interface.Dump(INTERFACE_DUMP_FILE); + wxLogMessage("Dumping interface API to '%s'...", INTERFACE_DUMP_FILE); + m_doxyInterface.Dump(INTERFACE_DUMP_FILE); } else { @@ -163,6 +180,7 @@ int IfaceCheckApp::OnRun() m_strToMatch = m_strToMatch.Mid(1, len-2); } + ok = Compare(); } @@ -183,84 +201,93 @@ int IfaceCheckApp::OnRun() // HELP_SWITCH was passed or a syntax error occurred return 0; } - - return 1; -} - -void IfaceCheckApp::ShowProgress() -{ - wxPrint("."); - //fflush(stdout); } bool IfaceCheckApp::Compare() { - const wxClassArray& interface = m_interface.GetClasses(); + const wxClassArray& interfaces = m_doxyInterface.GetClasses(); const wxClass* c; - wxClassPtrArray api; int mcount = 0, ccount = 0; - LogMessage("Comparing the interface API to the real API (%d classes to compare)...", - interface.GetCount()); + wxLogMessage("Comparing the interface API to the real API (%d classes to compare)...", + interfaces.GetCount()); if (!m_strToMatch.IsEmpty()) - LogMessage("Processing only header files matching '%s' expression.", m_strToMatch); + { + wxLogMessage("Processing only header files matching '%s' expression.", m_strToMatch); + } - for (unsigned int i=0; i<interface.GetCount(); i++) + for (unsigned int i=0; i<interfaces.GetCount(); i++) { + // only compare the methods which are available for the port + // for which the gcc XML was produced + if (interfaces[i].GetAvailability() != wxPORT_UNKNOWN && + (interfaces[i].GetAvailability() & m_gccInterface.GetInterfacePort()) == 0) { + + if (g_verbose) + { + wxLogMessage("skipping class '%s' since it's not available for the %s port.", + interfaces[i].GetName(), m_gccInterface.GetInterfacePortName()); + } + + continue; // skip this method + } + // shorten the name of the header so the log file is more readable // and also for calling IsToProcess() against it - wxString header = wxFileName(interface[i].GetHeader()).GetFullName(); + wxString header = wxFileName(interfaces[i].GetHeader()).GetFullName(); if (!IsToProcess(header)) continue; // skip this one - wxString cname = interface[i].GetName(); + wxString cname = interfaces[i].GetName(); - api.Empty(); - - // search in the real headers for i-th interface class - // for both class cname and cnameBase as in wxWidgets world, most often + // search in the real headers for i-th interface class; we search for + // both class cname and cnameBase since in wxWidgets world tipically // class cname is platform-specific while the real public interface of // that class is part of the cnameBase class. - c = m_api.FindClass(cname); - if (c) api.Add(c); - c = m_api.FindClass(cname + "Base"); - if (c) api.Add(c); + /*c = m_gccInterface.FindClass(cname + "Base"); + if (c) api.Add(c);*/ + + c = m_gccInterface.FindClass(cname); + if (!c) + { + // sometimes the platform-specific class is named "wxGeneric" + cname + // or similar: + c = m_gccInterface.FindClass("wxGeneric" + cname.Mid(2)); + if (!c) + { + c = m_gccInterface.FindClass("wxGtk" + cname.Mid(2)); + } + } - if (api.GetCount()>0) { + if (c) { - // there is a class with exactly the same name! - mcount += CompareClasses(&interface[i], api); + // there is a class with the same (logic) name! + mcount += CompareClasses(&interfaces[i], c); } else { - LogMessage("%s: couldn't find the real interface for the '%s' class", + wxLogMessage("%s: couldn't find the real interface for the '%s' class", header, cname); ccount++; } } - LogMessage("%d methods (%.1f%%) of the interface headers do not exist in the real headers", - mcount, (float)(100.0 * mcount/m_interface.GetMethodCount())); - LogMessage("%d classes (%.1f%%) of the interface headers do not exist in the real headers", - ccount, (float)(100.0 * ccount/m_interface.GetClassesCount())); + wxLogMessage("%d on a total of %d methods (%.1f%%) of the interface headers do not exist in the real headers", + mcount, m_doxyInterface.GetMethodCount(), (float)(100.0 * mcount/m_doxyInterface.GetMethodCount())); + wxLogMessage("%d on a total of %d classes (%.1f%%) of the interface headers do not exist in the real headers", + ccount, m_doxyInterface.GetClassesCount(), (float)(100.0 * ccount/m_doxyInterface.GetClassesCount())); return true; } -int IfaceCheckApp::CompareClasses(const wxClass* iface, const wxClassPtrArray& api) +int IfaceCheckApp::CompareClasses(const wxClass* iface, const wxClass* api) { - wxString searchedclasses; const wxMethod *real; int count = 0; - wxASSERT(iface && api.GetCount()>0); - - // build a string with the names of the API classes compared to iface - for (unsigned int j=0; j<api.GetCount(); j++) - searchedclasses += "/" + api[j]->GetName(); - searchedclasses.Remove(0, 1); + wxASSERT(iface && api); // shorten the name of the header so the log file is more readable wxString header = wxFileName(iface->GetHeader()).GetFullName(); @@ -268,140 +295,210 @@ int IfaceCheckApp::CompareClasses(const wxClass* iface, const wxClassPtrArray& a for (unsigned int i=0; i<iface->GetMethodCount(); i++) { const wxMethod& m = iface->GetMethod(i); - int matches = 0; + + // only compare the methods which are available for the port + // for which the gcc XML was produced + if (m.GetAvailability() != wxPORT_UNKNOWN && + (m.GetAvailability() & m_gccInterface.GetInterfacePort()) == 0) { + + if (g_verbose) + { + wxLogMessage("skipping method '%s' since it's not available for the %s port.", + m.GetAsString(), m_gccInterface.GetInterfacePortName()); + } + + continue; // skip this method + } // search in the methods of the api classes provided - for (unsigned int j=0; j<api.GetCount(); j++) + real = api->RecursiveUpwardFindMethod(m, &m_gccInterface); + + // avoid some false positives: + if (!real && m.ActsAsDefaultCtor()) { - real = api[j]->FindMethod(m); - if (real) - matches++; // there is a real matching prototype! It's ok! + // build an artificial default ctor for this class: + wxMethod temp(m); + temp.GetArgumentTypes().Clear(); + + // repeat search: + real = api->RecursiveUpwardFindMethod(temp, &m_gccInterface); } - if (matches == 0) + // no matches? + if (!real) { - wxMethodPtrArray overloads; - - // try searching for a method with the same name but with - // different return type / arguments / qualifiers - for (unsigned int j=0; j<api.GetCount(); j++) - { - wxMethodPtrArray results = api[j]->FindMethodNamed(m.GetName()); + bool proceed = true; + wxMethodPtrArray overloads = + api->RecursiveUpwardFindMethodsNamed(m.GetName(), &m_gccInterface); + + // avoid false positives: + for (unsigned int k=0; k<overloads.GetCount(); k++) + if (overloads[k]->MatchesExceptForAttributes(m) && + m.IsDeprecated() && !overloads[k]->IsDeprecated()) + { + // maybe the iface method is marked as deprecated but the + // real method is not? + wxMethod tmp(*overloads[k]); + tmp.SetDeprecated(true); - // append "results" array to "overloads" - WX_APPEND_ARRAY(overloads, results); - } + if (tmp == m) + { + // in this case, we can disregard this warning... the real + // method probably is included in WXWIN_COMPAT sections! + proceed = false; // skip this method + } + } - if (overloads.GetCount()==0) - { - /* - TODO: sometimes the interface headers re-document a method - inherited from a base class even if the real header does - not actually re-implement it. - To avoid false positives, we'd need to search in the base classes - of api[] classes and search for a matching method. - */ - LogMessage("%s: real '%s' class has no method '%s'", - header, searchedclasses, m.GetAsString()); - // we've found no overloads - } - else - { - // first, output a warning - wxString warning = header; - if (overloads.GetCount()>1) - warning += wxString::Format(": in the real headers there are %d overloads of '%s' for " - "'%s' all with different signatures:\n", - overloads.GetCount(), m.GetName(), searchedclasses); - else - warning += wxString::Format(": in the real headers there is a method '%s' for '%s'" - " but has different signature:\n", - m.GetName(), searchedclasses); +#define HACK_TO_AUTO_CORRECT_ONLY_METHOD_ATTRIBUTES 0 +#if HACK_TO_AUTO_CORRECT_ONLY_METHOD_ATTRIBUTES + for (unsigned int k=0; k<overloads.GetCount(); k++) + if (overloads[k]->MatchesExceptForAttributes(m)) + { + // fix default values of results[k]: + wxMethod tmp(*overloads[k]); + tmp.SetArgumentTypes(m.GetArgumentTypes()); - warning += "\tdoxy header: " + m.GetAsString(); - for (unsigned int j=0; j<overloads.GetCount(); j++) - warning += "\n\treal header: " + overloads[j]->GetAsString(); + // modify interface header + if (FixMethod(iface->GetHeader(), &m, &tmp)) + { + wxLogMessage("Adjusted attributes of '%s' method", m.GetAsString()); + } - wxPrint(warning + "\n"); - count++; + proceed = false; + break; + } +#endif // HACK_TO_AUTO_CORRECT_ONLY_METHOD_ATTRIBUTES - if (overloads.GetCount()>1) + if (proceed) + { + if (overloads.GetCount()==0) { - // TODO: decide which of these overloads is the most "similar" to m - // and eventually modify it - if (m_modify) - wxPrint("\tmanual fix is required\n"); + wxLogMessage("%s: real '%s' class and their parents have no method '%s'", + header, api->GetName(), m.GetAsString()); + // we've found no overloads } else { - wxASSERT(overloads.GetCount() == 1); + // first, output a warning + wxString warning = header; + if (overloads.GetCount()>1) + warning += wxString::Format(": in the real headers there are %d overloads of '%s' for " + "'%s' all with different signatures:\n", + overloads.GetCount(), m.GetName(), api->GetName()); + else { + warning += wxString::Format(": in the real headers there is a method '%s' for '%s'" + " but has different signature:\n", + m.GetName(), api->GetName()); + } + + // get a list of the prototypes with _all_ possible attributes: + warning += "\tdoxy header: " + m.GetAsString(true, true, true, true); + for (unsigned int j=0; j<overloads.GetCount(); j++) + warning += "\n\treal header: " + overloads[j]->GetAsString(true, true, true, true); - if (m_modify) + wxLogWarning("%s", warning); + count++; + + if (overloads.GetCount()>1) + { + // TODO: decide which of these overloads is the most "similar" to m + // and eventually modify it + if (m_modify) + { + wxLogWarning("\tmanual fix is required"); + } + } + else { - wxPrint("\tfixing it...\n"); + wxASSERT(overloads.GetCount() == 1); - // try to modify it! - FixMethod(iface->GetHeader(), &m, overloads[0]); + if (m_modify || m.IsCtor()) + { + wxLogWarning("\tfixing it..."); + + // try to modify it! + FixMethod(iface->GetHeader(), &m, overloads[0]); + } } } - } - count++; + count++; + } // if (proceed) } } return count; } -void IfaceCheckApp::FixMethod(const wxString& header, const wxMethod* iface, const wxMethod* api) +bool IfaceCheckApp::StringContainsMethodName(const wxString& str, const wxMethod* m) +{ + return str.Contains(m->GetName()) || + (m->IsOperator() && str.Contains("operator")); +} + +bool IfaceCheckApp::FixMethod(const wxString& header, const wxMethod* iface, const wxMethod* api) { + unsigned int i,j; wxASSERT(iface && api); wxTextFile file; if (!file.Open(header)) { - LogError("\tcan't open the '%s' header file.", header); - return; + wxLogError("\tcan't open the '%s' header file.", header); + return false; } - // GetLocation() returns the line where the last part of the prototype is placed: + // GetLocation() returns the line where the last part of the prototype is placed; + // i.e. the line containing the semicolon at the end of the declaration. int end = iface->GetLocation()-1; if (end <= 0 || end >= (int)file.GetLineCount()) { - LogWarning("\tinvalid location info for method '%s': %d.", + wxLogWarning("\tinvalid location info for method '%s': %d.", iface->GetAsString(), iface->GetLocation()); - return; + return false; } if (!file.GetLine(end).Contains(";")) { - LogWarning("\tinvalid location info for method '%s': %d.", + wxLogWarning("\tinvalid location info for method '%s': %d.", iface->GetAsString(), iface->GetLocation()); - return; + return false; } - // find the start point of this prototype declaration: - int start = end-1; + // is this a one-line prototype declaration? bool founddecl = false; - while (start > 0 && - !file.GetLine(start).Contains(";") && - !file.GetLine(start).Contains("*/")) + int start; + if (StringContainsMethodName(file.GetLine(end), iface)) { - start--; + // yes, this prototype is all on this line: + start = end; + founddecl = true; + } + else + { + start = end; // will be decremented inside the while{} loop below - founddecl |= file.GetLine(start).Contains(iface->GetName()); + // find the start point of this prototype declaration; i.e. the line + // containing the function name, which is also the line following + // the marker '*/' for the closure of the doxygen comment + do + { + start--; // go up one line + + if (StringContainsMethodName(file.GetLine(start), iface)) + founddecl = true; + } + while (start > 0 && !founddecl && + !file.GetLine(start).Contains(";") && + !file.GetLine(start).Contains("*/")); } if (start <= 0 || !founddecl) { - LogError("\tcan't find the beginning of the declaration of '%s' method in '%s' header", - iface->GetAsString(), header); - return; + wxLogError("\tcan't find the beginning of the declaration of '%s' method in '%s' header looking backwards from line %d; I arrived at %d and gave up", + iface->GetAsString(), header, end+1 /* zero-based => 1-based */, start); + return false; } - // start-th line contains either the declaration of another prototype - // or the closing tag */ of a doxygen comment; start one line below - start++; - // remove the old prototype - for (int i=start; i<=end; i++) + for (int k=start; k<=end; k++) file.RemoveLine(start); // remove (end-start)-nth times the start-th line #define INDENTATION_STR wxString(" ") @@ -422,15 +519,23 @@ void IfaceCheckApp::FixMethod(const wxString& header, const wxMethod* iface, con wxMethod tmp(*api); - // discard API argument names and replace them with those parsed from doxygen XML: + // discard gcc XML argument names and replace them with those parsed from doxygen XML; + // in this way we should avoid introducing doxygen warnings about cases where the argument + // 'xx' of the prototype is called 'yy' in the function's docs. const wxArgumentTypeArray& doxygenargs = iface->GetArgumentTypes(); const wxArgumentTypeArray& realargs = api->GetArgumentTypes(); if (realargs.GetCount() == doxygenargs.GetCount()) { - for (unsigned int j=0; j<doxygenargs.GetCount(); j++) + for (j=0; j<doxygenargs.GetCount(); j++) if (doxygenargs[j]==realargs[j]) + { realargs[j].SetArgumentName(doxygenargs[j].GetArgumentName()); + if (realargs[j].GetDefaultValue().IsNumber() && + doxygenargs[j].GetDefaultValue().StartsWith("wx")) + realargs[j].SetDefaultValue(doxygenargs[j].GetDefaultValue()); + } + tmp.SetArgumentTypes(realargs); } @@ -443,7 +548,7 @@ void IfaceCheckApp::FixMethod(const wxString& header, const wxMethod* iface, con wxASSERT(nStartColumn != wxNOT_FOUND); // wrap lines too long at comma boundaries - for (unsigned int i=0; i<toinsert.GetCount(); i++) + for (i=0; i<toinsert.GetCount(); i++) { size_t len = toinsert[i].Len(); if (len > WRAP_COLUMN) @@ -463,29 +568,31 @@ void IfaceCheckApp::FixMethod(const wxString& header, const wxMethod* iface, con } // insert the new lines - for (unsigned int i=0; i<toinsert.GetCount(); i++) + for (i=0; i<toinsert.GetCount(); i++) file.InsertLine(toinsert[i], start+i); // now save the modification if (!file.Write()) { - LogError("\tcan't save the '%s' header file.", header); - return; + wxLogError("\tcan't save the '%s' header file.", header); + return false; } // how many lines did we add/remove in total? int nOffset = toinsert.GetCount() + deprecationOffset - (end-start+1); if (nOffset == 0) - return; + return false; if (g_verbose) - LogMessage("\tthe final row offset for following methods is %d lines.", nOffset); + { + wxLogMessage("\tthe final row offset for following methods is %d lines.", nOffset); + } // update the other method's locations for those methods which belong to the modified header // and are placed _below_ the modified method - wxClassPtrArray cToUpdate = m_interface.FindClassesDefinedIn(header); - for (unsigned int i=0; i < cToUpdate.GetCount(); i++) + wxClassPtrArray cToUpdate = m_doxyInterface.FindClassesDefinedIn(header); + for (i=0; i < cToUpdate.GetCount(); i++) { - for (unsigned int j=0; j < cToUpdate[i]->GetMethodCount(); j++) + for (j=0; j < cToUpdate[i]->GetMethodCount(); j++) { wxMethod& m = cToUpdate[i]->GetMethod(j); if (m.GetLocation() > iface->GetLocation()) @@ -495,13 +602,15 @@ void IfaceCheckApp::FixMethod(const wxString& header, const wxMethod* iface, con } } } + + return true; } bool IfaceCheckApp::ParsePreprocessorOutput(const wxString& filename) { wxTextFile tf; if (!tf.Open(filename)) { - LogError("can't open the '%s' preprocessor output file.", filename); + wxLogError("can't open the '%s' preprocessor output file.", filename); return false; } @@ -513,27 +622,37 @@ bool IfaceCheckApp::ParsePreprocessorOutput(const wxString& filename) // 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); + if (!line.StartsWith("#define ")) { + wxLogError("unexpected content in '%s' at line %d.", filename, i+1); 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++; + if (defnameval.Contains(" ")) + { + // 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_doxyInterface.AddPreprocessorValue(defname, defval); + useful++; + } + else + { + // it looks like the format of this line is: + // #define DEFNAME + // we are not interested to symbols #defined to nothing, + // so we just ignore this line. + } } - LogMessage("Parsed %d preprocessor #defines from '%s' which will be used later...", + wxLogMessage("Parsed %d preprocessor #defines from '%s' which will be used later...", useful, filename); return true; @@ -541,10 +660,29 @@ bool IfaceCheckApp::ParsePreprocessorOutput(const wxString& filename) void IfaceCheckApp::PrintStatistics(long secs) { - LogMessage("wx real headers contains declaration of %d classes (%d methods)", - m_api.GetClassesCount(), m_api.GetMethodCount()); - LogMessage("wx interface headers contains declaration of %d classes (%d methods)", - m_interface.GetClassesCount(), m_interface.GetMethodCount()); - LogMessage("total processing took %d seconds.", secs); + // these stats, for what regards the gcc XML, are all referred to the wxWidgets + // classes only! + + wxLogMessage("wx real headers contains declaration of %d classes (%d methods)", + m_gccInterface.GetClassesCount(), m_gccInterface.GetMethodCount()); + wxLogMessage("wx interface headers contains declaration of %d classes (%d methods)", + m_doxyInterface.GetClassesCount(), m_doxyInterface.GetMethodCount()); + + // build a list of the undocumented wx classes + wxString list; + int undoc = 0; + const wxClassArray& arr = m_gccInterface.GetClasses(); + for (unsigned int i=0; i<arr.GetCount(); i++) { + if (m_doxyInterface.FindClass(arr[i].GetName()) == NULL) { + list += arr[i].GetName() + ", "; + undoc++; + } + } + + list.RemoveLast(); + list.RemoveLast(); + + wxLogMessage("the list of the %d undocumented wx classes is: %s", undoc, list); + wxLogMessage("total processing took %d seconds.", secs); }