| #!/usr/bin/perl |
| ######################################################################## |
| # ABI Compliance Checker (ABICC) 2.3 |
| # A tool for checking backward compatibility of a C/C++ library API |
| # |
| # Copyright (C) 2009-2011 Institute for System Programming, RAS |
| # Copyright (C) 2011-2012 Nokia Corporation and/or its subsidiary(-ies) |
| # Copyright (C) 2012-2019 Andrey Ponomarenko's ABI Laboratory |
| # |
| # Written by Andrey Ponomarenko |
| # |
| # PLATFORMS |
| # ========= |
| # Linux, FreeBSD, Solaris, Mac OS X, MS Windows, Symbian, Haiku |
| # |
| # REQUIREMENTS |
| # ============ |
| # Linux |
| # - G++ (3.0 or newer) |
| # - GNU Binutils (readelf, c++filt, objdump) |
| # - Perl 5 |
| # - Ctags |
| # - ABI Dumper >= 1.1 |
| # |
| # Mac OS X |
| # - Xcode (g++, c++filt, otool, nm) |
| # - Ctags |
| # |
| # MS Windows |
| # - MinGW (3.0 or newer) |
| # - MS Visual C++ (dumpbin, undname, cl) |
| # - Active Perl 5 (5.8 or newer) |
| # - Sigcheck v2.52 or newer |
| # - GnuWin Zip and UnZip |
| # - Ctags (Exuberant or Universal) |
| # - Add tool locations to the PATH environment variable |
| # - Run vcvars64.bat (C:\Microsoft Visual Studio 9.0\VC\bin\) |
| # |
| # This library is free software; you can redistribute it and/or |
| # modify it under the terms of the GNU Lesser General Public |
| # License as published by the Free Software Foundation; either |
| # version 2.1 of the License, or (at your option) any later version. |
| # |
| # This library is distributed in the hope that it will be useful, |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| # Lesser General Public License for more details. |
| # |
| # You should have received a copy of the GNU Lesser General Public |
| # License along with this library; if not, write to the Free Software |
| # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, |
| # MA 02110-1301 USA |
| ######################################################################## |
| use Getopt::Long; |
| Getopt::Long::Configure ("posix_default", "no_ignore_case"); |
| use File::Path qw(mkpath rmtree); |
| use File::Temp qw(tempdir); |
| use File::Copy qw(copy); |
| use File::Basename qw(dirname); |
| use Cwd qw(abs_path cwd); |
| use Data::Dumper; |
| |
| my $TOOL_VERSION = "2.3"; |
| my $ABI_DUMP_VERSION = "3.5"; |
| my $ABI_DUMP_VERSION_MIN = "3.5"; |
| |
| my $XML_REPORT_VERSION = "1.2"; |
| my $XML_ABI_DUMP_VERSION = "1.2"; |
| |
| # Internal modules |
| my $MODULES_DIR = getModules(); |
| push(@INC, dirname($MODULES_DIR)); |
| |
| # Basic modules |
| my %LoadedModules = (); |
| loadModule("Basic"); |
| loadModule("Input"); |
| loadModule("Path"); |
| loadModule("Logging"); |
| loadModule("Utils"); |
| loadModule("TypeAttr"); |
| loadModule("Filter"); |
| loadModule("SysFiles"); |
| loadModule("Descriptor"); |
| loadModule("Mangling"); |
| |
| # Rules DB |
| my %RULES_PATH = ( |
| "Binary" => $MODULES_DIR."/RulesBin.xml", |
| "Source" => $MODULES_DIR."/RulesSrc.xml"); |
| |
| my $BYTE = 8; |
| my $CmdName = getFilename($0); |
| |
| my %HomePage = ( |
| "Dev"=>"https://github.com/lvc/abi-compliance-checker", |
| "Doc"=>"https://lvc.github.io/abi-compliance-checker/" |
| ); |
| |
| my $ShortUsage = "ABI Compliance Checker (ABICC) $TOOL_VERSION |
| A tool for checking backward compatibility of a C/C++ library API |
| Copyright (C) 2019 Andrey Ponomarenko's ABI Laboratory |
| License: GNU LGPL 2.1 |
| |
| Usage: $CmdName [options] |
| Example: $CmdName -l NAME -old ABI-0.dump -new ABI-1.dump |
| |
| ABI-0.dump and ABI-1.dump are ABI dumps generated |
| by the ABI Dumper or ABICC tools. |
| |
| More info: $CmdName --help\n"; |
| |
| if($#ARGV==-1) |
| { |
| printMsg("INFO", $ShortUsage); |
| exit(0); |
| } |
| |
| GetOptions( |
| "h|help!" => \$In::Opt{"Help"}, |
| "i|info!" => \$In::Opt{"InfoMsg"}, |
| "v|version!" => \$In::Opt{"ShowVersion"}, |
| "dumpversion!" => \$In::Opt{"DumpVersion"}, |
| # General |
| "l|lib|library=s" => \$In::Opt{"TargetLib"}, |
| "d1|old|o=s" => \$In::Desc{1}{"Path"}, |
| "d2|new|n=s" => \$In::Desc{2}{"Path"}, |
| "dump|dump-abi|dump_abi=s" => \$In::Opt{"DumpABI"}, |
| "d|f|filter=s" => \$In::Opt{"FilterPath"}, |
| # Extra |
| "debug!" => \$In::Opt{"Debug"}, |
| "debug-mangling!" => \$In::Opt{"DebugMangling"}, |
| "ext|extended!" => \$In::Opt{"ExtendedCheck"}, |
| "static|static-libs!" => \$In::Opt{"UseStaticLibs"}, |
| "gcc-path|cross-gcc=s" => \$In::Opt{"CrossGcc"}, |
| "gcc-prefix|cross-prefix=s" => \$In::Opt{"CrossPrefix"}, |
| "gcc-options=s" => \$In::Opt{"GccOptions"}, |
| "count-symbols=s" => \$In::Opt{"CountPubSymbols"}, |
| "count-all-symbols=s" => \$In::Opt{"CountAllSymbols"}, |
| "use-dumps!" => \$In::Opt{"UseDumps"}, |
| "xml!" => \$In::Opt{"UseXML"}, |
| "app|application=s" => \$In::Opt{"AppPath"}, |
| "headers-only!" => \$In::Opt{"CheckHeadersOnly"}, |
| "v1|vnum1|version1=s" => \$In::Desc{1}{"TargetVersion"}, |
| "v2|vnum2|version2=s" => \$In::Desc{2}{"TargetVersion"}, |
| "relpath1=s" => \$In::Desc{1}{"RelativeDirectory"}, |
| "relpath2=s" => \$In::Desc{2}{"RelativeDirectory"}, |
| # Test |
| "test!" => \$In::Opt{"TestTool"}, |
| "test-dump!" => \$In::Opt{"TestDump"}, |
| "test-abi-dumper!" => \$In::Opt{"TestABIDumper"}, |
| # Report |
| "s|strict!" => \$In::Opt{"StrictCompat"}, |
| "binary|bin|abi!" => \$In::Opt{"BinOnly"}, |
| "source|src|api!" => \$In::Opt{"SrcOnly"}, |
| "warn-newsym!" => \$In::Opt{"WarnNewSym"}, |
| # Report path |
| "report-path=s" => \$In::Opt{"OutputReportPath"}, |
| "bin-report-path=s" => \$In::Opt{"BinReportPath"}, |
| "src-report-path=s" => \$In::Opt{"SrcReportPath"}, |
| # Report format |
| "show-retval!" => \$In::Opt{"ShowRetVal"}, |
| "stdout!" => \$In::Opt{"StdOut"}, |
| "report-format=s" => \$In::Opt{"ReportFormat"}, |
| "old-style!" => \$In::Opt{"OldStyle"}, |
| "title=s" => \$In::Opt{"TargetTitle"}, |
| "component=s" => \$In::Opt{"TargetComponent"}, |
| "p|params=s" => \$In::Opt{"ParamNamesPath"}, |
| "limit-affected|affected-limit=s" => \$In::Opt{"AffectLimit"}, |
| "all-affected!" => \$In::Opt{"AllAffected"}, |
| "list-affected!" => \$In::Opt{"ListAffected"}, |
| # ABI dump |
| "dump-path=s" => \$In::Opt{"OutputDumpPath"}, |
| "dump-format=s" => \$In::Opt{"DumpFormat"}, |
| "check!" => \$In::Opt{"CheckInfo"}, |
| "extra-info=s" => \$In::Opt{"ExtraInfo"}, |
| "extra-dump!" => \$In::Opt{"ExtraDump"}, |
| "relpath=s" => \$In::Desc{1}{"RelativeDirectory"}, |
| "vnum=s" => \$In::Desc{1}{"TargetVersion"}, |
| "sort!" => \$In::Opt{"SortDump"}, |
| # Filter symbols and types |
| "symbols-list=s" => \$In::Opt{"SymbolsListPath"}, |
| "types-list=s" => \$In::Opt{"TypesListPath"}, |
| "skip-symbols=s" => \$In::Opt{"SkipSymbolsListPath"}, |
| "skip-types=s" => \$In::Opt{"SkipTypesListPath"}, |
| "skip-internal-symbols|skip-internal=s" => \$In::Opt{"SkipInternalSymbols"}, |
| "skip-internal-types=s" => \$In::Opt{"SkipInternalTypes"}, |
| "keep-cxx!" => \$In::Opt{"KeepCxx"}, |
| "keep-reserved!" => \$In::Opt{"KeepReserved"}, |
| # Filter header files |
| "skip-headers=s" => \$In::Opt{"SkipHeadersPath"}, |
| "headers-list=s" => \$In::Opt{"TargetHeadersPath"}, |
| "header=s" => \$In::Opt{"TargetHeader"}, |
| "nostdinc!" => \$In::Opt{"NoStdInc"}, |
| "tolerance=s" => \$In::Opt{"Tolerance"}, |
| "tolerant!" => \$In::Opt{"Tolerant"}, |
| "skip-unidentified!" => \$In::Opt{"SkipUnidentified"}, |
| # Filter rules |
| "skip-typedef-uncover!" => \$In::Opt{"SkipTypedefUncover"}, |
| "check-private-abi!" => \$In::Opt{"CheckPrivateABI"}, |
| "disable-constants-check!" => \$In::Opt{"DisableConstantsCheck"}, |
| "skip-added-constants!" => \$In::Opt{"SkipAddedConstants"}, |
| "skip-removed-constants!" => \$In::Opt{"SkipRemovedConstants"}, |
| # Other |
| "lang=s" => \$In::Opt{"UserLang"}, |
| "arch=s" => \$In::Opt{"TargetArch"}, |
| "mingw-compatible!" => \$In::Opt{"MinGWCompat"}, |
| "cxx-incompatible|cpp-incompatible!" => \$In::Opt{"CxxIncompat"}, |
| "cpp-compatible!" => \$In::Opt{"CxxCompat"}, |
| "quick!" => \$In::Opt{"Quick"}, |
| "force!" => \$In::Opt{"Force"}, |
| # OS analysis |
| "dump-system=s" => \$In::Opt{"DumpSystem"}, |
| "cmp-systems!" => \$In::Opt{"CmpSystems"}, |
| "sysroot=s" => \$In::Opt{"SystemRoot"}, |
| "sysinfo=s" => \$In::Opt{"TargetSysInfo"}, |
| "libs-list=s" => \$In::Opt{"TargetLibsPath"}, |
| # Logging |
| "log-path=s" => \$In::Opt{"LoggingPath"}, |
| "log1-path=s" => \$In::Desc{1}{"OutputLogPath"}, |
| "log2-path=s" => \$In::Desc{2}{"OutputLogPath"}, |
| "logging-mode=s" => \$In::Opt{"LogMode"}, |
| "q|quiet!" => \$In::Opt{"Quiet"} |
| ) or errMsg(); |
| |
| sub errMsg() |
| { |
| printMsg("INFO", "\n".$ShortUsage); |
| exit(getErrorCode("Error")); |
| } |
| |
| # Default log path |
| $In::Opt{"DefaultLog"} = "logs/run.log"; |
| |
| my $HelpMessage = " |
| NAME: |
| ABI Compliance Checker ($CmdName) |
| Check backward compatibility of a C/C++ library API |
| |
| DESCRIPTION: |
| ABI Compliance Checker (ABICC) is a tool for checking backward binary |
| compatibility and backward source compatibility of a C/C++ library API. |
| |
| The tool analyzes changes in API and ABI (ABI=API+compiler ABI) that may |
| break binary compatibility and/or source compatibility: changes in calling |
| stack, v-table changes, removed symbols, renamed fields, etc. |
| |
| Binary incompatibility may result in crashing or incorrect behavior of |
| applications built with an old version of a library if they run on a new |
| one. Source incompatibility may result in recompilation errors with a new |
| library version. |
| |
| The tool can create and compare ABI dumps for header files and shared |
| objects of a library. The ABI dump for a library can also be created by |
| the ABI Dumper tool (https://github.com/lvc/abi-dumper) if shared objects |
| include debug-info. |
| |
| The tool is intended for developers of software libraries and maintainers |
| of operating systems who are interested in ensuring backward compatibility, |
| i.e. allow old applications to run or to be recompiled with newer library |
| versions. |
| |
| Also the tool can be used by ISVs for checking applications portability to |
| new library versions. Found issues can be taken into account when adapting |
| the application to a new library version. |
| |
| This tool is free software: you can redistribute it and/or modify it |
| under the terms of the GNU LGPL 2.1. |
| |
| USAGE #1 (WITH ABI DUMPER): |
| |
| 1. Library should be compiled with \"-g -Og\" GCC options |
| to contain DWARF debug info |
| |
| 2. Create ABI dumps for both library versions |
| using the ABI Dumper (https://github.com/lvc/abi-dumper) tool: |
| |
| abi-dumper OLD.so -o ABI-0.dump -lver 0 |
| abi-dumper NEW.so -o ABI-1.dump -lver 1 |
| |
| 3. You can filter public ABI with the help of |
| additional -public-headers option of the ABI Dumper tool. |
| |
| 4. Compare ABI dumps to create report: |
| |
| abi-compliance-checker -l NAME -old ABI-0.dump -new ABI-1.dump |
| |
| USAGE #2 (ORIGINAL): |
| |
| 1. Create XML-descriptors for two versions |
| of a library (OLD.xml and NEW.xml): |
| |
| <version> |
| 1.0 |
| </version> |
| |
| <headers> |
| /path/to/headers/ |
| </headers> |
| |
| <libs> |
| /path/to/libraries/ |
| </libs> |
| |
| 2. Compare Xml-descriptors to create report: |
| |
| abi-compliance-checker -lib NAME -old OLD.xml -new NEW.xml |
| |
| USAGE #3 (CREATE ABI DUMPS): |
| |
| 1. Create XML-descriptors for two versions |
| of a library (OLD.xml and NEW.xml): |
| |
| <version> |
| 1.0 |
| </version> |
| |
| <headers> |
| /path/to/headers/ |
| </headers> |
| |
| <libs> |
| /path/to/libraries/ |
| </libs> |
| |
| 2. Create ABI dumps: |
| |
| abi-compliance-checker -lib NAME -dump OLD.xml -dump-path ./ABI-0.dump |
| abi-compliance-checker -lib NAME -dump NEW.xml -dump-path ./ABI-1.dump |
| |
| 3. Compare ABI dumps to create report: |
| |
| abi-compliance-checker -l NAME -old ABI-0.dump -new ABI-1.dump |
| |
| INFO OPTIONS: |
| -h|-help |
| Print this help. |
| |
| -i|-info |
| Print complete info including all options. |
| |
| -v|-version |
| Print version information. |
| |
| -dumpversion |
| Print the tool version ($TOOL_VERSION) and don't do anything else. |
| |
| GENERAL OPTIONS: |
| -l|-library NAME |
| Any name of the library. |
| |
| -old|-d1 PATH |
| Descriptor of the 1st (old) library version. |
| It may be one of the following: |
| |
| 1. ABI dump generated by the ABI Dumper tool |
| 2. XML-descriptor (*.xml file): |
| |
| <version> |
| 1.0 |
| </version> |
| |
| <headers> |
| /path1/to/header(s)/ |
| /path2/to/header(s)/ |
| ... |
| </headers> |
| |
| <libs> |
| /path1/to/library(ies)/ |
| /path2/to/library(ies)/ |
| ... |
| </libs> |
| |
| ... |
| |
| 3. ABI dump generated by -dump option |
| 4. Directory with headers and libraries |
| 5. Single header file |
| |
| If you are using 4-5 descriptor types then you should |
| specify version numbers with -v1 and -v2 options. |
| |
| For more information, please see: |
| https://lvc.github.io/abi-compliance-checker/Xml-Descriptor.html |
| |
| -new|-d2 PATH |
| Descriptor of the 2nd (new) library version. |
| |
| -dump PATH |
| Create library ABI dump for the input XML descriptor. You can |
| transfer it anywhere and pass instead of the descriptor. Also |
| it can be used for debugging the tool. |
| |
| -filter PATH |
| A path to XML descriptor with skip_* rules to filter |
| analyzed symbols in the report. |
| "; |
| |
| sub helpMsg() { |
| printMsg("INFO", $HelpMessage." |
| MORE OPTIONS: |
| $CmdName --info\n"); |
| } |
| |
| sub infoMsg() |
| { |
| printMsg("INFO", "$HelpMessage |
| EXTRA OPTIONS: |
| -debug |
| Debugging mode. Print debug info on the screen. Save intermediate |
| analysis stages in the debug directory: |
| debug/LIB_NAME/VERSION/ |
| |
| Also consider using -dump option for debugging the tool. |
| |
| -ext|-extended |
| If your library A is supposed to be used by other library B and you |
| want to control the ABI of B, then you should enable this option. The |
| tool will check for changes in all data types, even if they are not |
| used by any function in the library A. Such data types are not part |
| of the A library ABI, but may be a part of the ABI of the B library. |
| |
| The short scheme is: |
| app C (broken) -> lib B (broken ABI) -> lib A (stable ABI) |
| |
| -static |
| Check static libraries instead of the shared ones. The <libs> section |
| of the XML-descriptor should point to static libraries location. |
| |
| -gcc-path PATH |
| Path to the cross GCC compiler to use instead of the usual (host) GCC. |
| |
| -gcc-prefix PREFIX |
| GCC toolchain prefix. |
| |
| -gcc-options OPTS |
| Additional compiler options. |
| |
| -count-symbols PATH |
| Count total public symbols in the ABI dump. |
| |
| -use-dumps |
| Make dumps for two versions of a library and compare dumps. This should |
| increase the performance of the tool and decrease the system memory usage. |
| |
| -xml |
| Alias for: --report-format=xml or --dump-format=xml |
| |
| -app|-application PATH |
| This option allows one to specify the application that should be checked |
| for portability to the new library version. |
| |
| -headers-only |
| Check header files without libraries. It is easy to run, but may |
| provide a low quality compatibility report with false positives and |
| without detecting of added/removed symbols. |
| |
| -v1|-vnum1 NUM |
| Specify 1st library version outside the descriptor. This option is needed |
| if you have preferred an alternative descriptor type (see -d1 option). |
| |
| In general case you should specify it in the XML-descriptor: |
| <version> |
| VERSION |
| </version> |
| |
| -v2|-vnum2 NUM |
| Specify 2nd library version outside the descriptor. |
| |
| -relpath1 PATH |
| Replace {RELPATH} macros to PATH in the 1st XML-descriptor (-d1). |
| |
| -relpath2 PATH |
| Replace {RELPATH} macros to PATH in the 2nd XML-descriptor (-d2). |
| |
| TEST OPTIONS: |
| -test |
| Run internal tests. Create two binary incompatible versions of a sample |
| library and run the tool to check them for compatibility. This option |
| allows one to check if the tool works correctly in the current environment. |
| |
| -test-dump |
| Test ability to create, read and compare ABI dumps. |
| |
| -test-abi-dumper |
| Compare ABI dumps created by the ABI Dumper tool. |
| |
| REPORT OPTIONS: |
| -binary|-bin|-abi |
| Show binary compatibility problems only. |
| Generate report to: |
| compat_reports/LIB_NAME/V1_to_V2/abi_compat_report.html |
| |
| -source|-src|-api |
| Show source compatibility problems only. |
| Generate report to: |
| compat_reports/LIB_NAME/V1_to_V2/src_compat_report.html |
| |
| -s|-strict |
| Treat all compatibility warnings as problems. Add a number of \"Low\" |
| severity problems to the return value of the tool. |
| |
| -warn-newsym |
| Treat new symbols as problems, because that breaks semantic versioning. |
| For more info please visit https://semver.org/ |
| |
| REPORT PATH OPTIONS: |
| -report-path PATH |
| Path to compatibility report. |
| Default: |
| compat_reports/LIB_NAME/V1_to_V2/compat_report.html |
| |
| -bin-report-path PATH |
| Path to binary compatibility report. |
| Default: |
| compat_reports/LIB_NAME/V1_to_V2/abi_compat_report.html |
| |
| -src-report-path PATH |
| Path to source compatibility report. |
| Default: |
| compat_reports/LIB_NAME/V1_to_V2/src_compat_report.html |
| |
| REPORT FORMAT OPTIONS: |
| -show-retval |
| Show the symbol's return type in the report. |
| |
| -stdout |
| Print analysis results (compatibility reports and ABI dumps) to stdout |
| instead of creating a file. This would allow piping data to other programs. |
| |
| -report-format FMT |
| Change format of compatibility report. |
| Formats: |
| htm - HTML format (default) |
| xml - XML format |
| |
| -old-style |
| Generate old-style report. |
| |
| -title NAME |
| Change library name in the report title to NAME. By default |
| will be displayed a name specified by -l option. |
| |
| -component NAME |
| The component name in the title and summary of the HTML report. |
| Default: |
| library |
| |
| -p|-params PATH |
| Path to file with the function parameter names. It can be used |
| for improving report view if the library header files have no |
| parameter names. File format: |
| |
| func1;param1;param2;param3 ... |
| func2;param1;param2;param3 ... |
| ... |
| |
| -limit-affected LIMIT |
| The maximum number of affected symbols listed under the description |
| of the changed type in the report. |
| |
| -list-affected |
| Generate file with the list of incompatible |
| symbols beside the HTML compatibility report. |
| Use 'c++filt \@file' command from GNU binutils |
| to unmangle C++ symbols in the generated file. |
| Default names: |
| abi_affected.txt |
| src_affected.txt |
| |
| ABI DUMP OPTIONS: |
| -dump-path PATH |
| Specify a *.dump file path where to generate an ABI dump. |
| Default: |
| abi_dumps/LIB_NAME/VERSION/ABI.dump |
| |
| -dump-format FMT |
| Change format of ABI dump. |
| Formats: |
| perl - Data::Dumper format (default) |
| xml - XML format |
| |
| -check |
| Check completeness of the ABI dump. |
| |
| -extra-info DIR |
| Dump extra info to DIR. |
| |
| -extra-dump |
| Create extended ABI dump containing all symbols |
| from the translation unit. |
| |
| -relpath PATH |
| Replace {RELPATH} macros to PATH in the XML-descriptor used |
| for dumping the library ABI (see -dump option). |
| |
| -vnum NUM |
| Specify the library version in the generated ABI dump. The <version> section |
| of the input XML descriptor will be overwritten in this case. |
| |
| -sort |
| Enable sorting of data in ABI dumps. |
| |
| FILTER SYMBOLS OPTIONS: |
| -symbols-list PATH |
| This option allows one to specify a file with a list of symbols (mangled |
| names in C++) that should be checked. Other symbols will not be checked. |
| |
| -types-list PATH |
| This option allows one to specify a file with a list of types that should |
| be checked. Other types will not be checked. |
| |
| -skip-symbols PATH |
| The list of symbols that should not be checked. |
| |
| -skip-types PATH |
| The list of types that should not be checked. |
| |
| -skip-internal-symbols PATTERN |
| Do not check symbols matched by the regular expression. |
| |
| -skip-internal-types PATTERN |
| Do not check types matched by the regular expression. |
| It's matched against full qualified type names (e.g. 'struct xyz::Name<T>'). |
| It has to match any part of type name. |
| |
| -keep-cxx |
| Check _ZS*, _ZNS* and _ZNKS* symbols. |
| |
| -keep-reserved |
| Report changes in reserved fields. |
| |
| FILTER HEADERS OPTIONS: |
| -skip-headers PATH |
| The file with the list of header files, that should not be checked. |
| |
| -headers-list PATH |
| The file with a list of headers, that should be checked/dumped. |
| |
| -header NAME |
| Check/Dump ABI of this header only. |
| |
| -nostdinc |
| Do not search in GCC standard system directories for header files. |
| |
| -tolerance LEVEL |
| Apply a set of heuristics to successfully compile input |
| header files. You can enable several tolerance levels by |
| joining them into one string (e.g. 13, 124, etc.). |
| Levels: |
| 1 - skip non-Linux headers (e.g. win32_*.h, etc.) |
| 2 - skip internal headers (e.g. *_p.h, impl/*.h, etc.) |
| 3 - skip headers that include non-Linux headers |
| 4 - skip headers included by others |
| |
| -tolerant |
| Enable highest tolerance level [1234]. |
| |
| -skip-unidentified |
| Skip header files in 'headers' and 'include_preamble' sections |
| of the XML descriptor that cannot be found. This is useful if |
| you are trying to use the same descriptor for different targets. |
| |
| FILTER RULES OPTIONS: |
| -skip-typedef-uncover |
| Do not report a problem if type is covered or |
| uncovered by typedef (useful for broken debug info). |
| |
| -check-private-abi |
| Check data types from the private part of the ABI when |
| comparing ABI dumps created by the ABI Dumper tool with |
| use of the -public-headers option. |
| |
| Requires ABI Dumper >= 0.99.14 |
| |
| -disable-constants-check |
| Do not check for changes in constants. |
| |
| -skip-added-constants |
| Do not detect added constants. |
| |
| -skip-removed-constants |
| Do not detect removed constants. |
| |
| OTHER OPTIONS: |
| -lang LANG |
| Set library language (C or C++). You can use this option if the tool |
| cannot auto-detect a language. This option may be useful for checking |
| C-library headers (--lang=C) in --headers-only or --extended modes. |
| |
| -arch ARCH |
| Set library architecture (x86, x86_64, ia64, arm, ppc32, ppc64, s390, |
| etc.). The option is useful if the tool cannot detect correct architecture |
| of the input objects. |
| |
| -mingw-compatible |
| If input header files are compatible with the MinGW GCC compiler, |
| then you can tell the tool about this and speedup the analysis. |
| |
| -cxx-incompatible |
| Set this option if input C header files use C++ keywords. The tool |
| will try to replace such keywords at preprocessor stage and replace |
| them back in the final TU dump. |
| |
| -cpp-compatible |
| Do nothing. |
| |
| -quick |
| Quick analysis. Disable check of some template instances. |
| |
| -force |
| Try to enable this option if the tool checked not all |
| types and symbols in header files. |
| |
| OS ANALYSIS OPTIONS: |
| -dump-system NAME -sysroot DIR |
| Find all the shared libraries and header files in DIR directory, |
| create XML descriptors and make ABI dumps for each library. The result |
| set of ABI dumps can be compared (--cmp-systems) with the other one |
| created for other version of operating system in order to check them for |
| compatibility. Do not forget to specify -cross-gcc option if your target |
| system requires some specific version of GCC compiler (different from |
| the host GCC). The system ABI dump will be generated to: |
| sys_dumps/NAME/ARCH |
| |
| -dump-system DESCRIPTOR.xml |
| The same as the previous option but takes an XML descriptor of the target |
| system as input, where you should describe it: |
| |
| /* Primary sections */ |
| |
| <name> |
| /* Name of the system */ |
| </name> |
| |
| <headers> |
| /* The list of paths to header files and/or |
| directories with header files, one per line */ |
| </headers> |
| |
| <libs> |
| /* The list of paths to shared libraries and/or |
| directories with shared libraries, one per line */ |
| </libs> |
| |
| /* Optional sections */ |
| |
| <search_headers> |
| /* List of directories to be searched |
| for header files to automatically |
| generate include paths, one per line */ |
| </search_headers> |
| |
| <search_libs> |
| /* List of directories to be searched |
| for shared libraries to resolve |
| dependencies, one per line */ |
| </search_libs> |
| |
| <tools> |
| /* List of directories with tools used |
| for analysis (GCC toolchain), one per line */ |
| </tools> |
| |
| <cross_prefix> |
| /* GCC toolchain prefix. |
| Examples: |
| arm-linux-gnueabi |
| arm-none-symbianelf */ |
| </cross_prefix> |
| |
| <gcc_options> |
| /* Additional GCC options, one per line */ |
| </gcc_options> |
| |
| -cmp-systems -d1 sys_dumps/NAME1/ARCH -d2 sys_dumps/NAME2/ARCH |
| Compare two ABI dumps of a system. Create compatibility reports for |
| each system library and the common HTML report including the summary |
| of test results for all checked libraries. |
| |
| Summary report will be generated to: |
| sys_compat_reports/NAME1_to_NAME2/ARCH |
| |
| -sysroot DIR |
| Specify the alternative root directory. The tool will search for include |
| paths in the DIR/usr/include and DIR/usr/lib directories. |
| |
| -sysinfo DIR |
| This option should be used with -dump-system option to dump |
| ABI of operating systems and configure the dumping process. |
| |
| -libs-list PATH |
| The file with a list of libraries, that should be dumped by |
| the -dump-system option or should be checked by the -cmp-systems option. |
| |
| LOGGING OPTIONS: |
| -log-path PATH |
| Log path for all messages. |
| Default: |
| logs/LIB_NAME/VERSION/log.txt |
| |
| -log1-path PATH |
| Log path for 1st version of a library. |
| Default: |
| logs/LIB_NAME/V1/log.txt |
| |
| -log2-path PATH |
| Log path for 2nd version of a library. |
| Default: |
| logs/LIB_NAME/V2/log.txt |
| |
| -logging-mode MODE |
| Change logging mode. |
| Modes: |
| w - overwrite old logs (default) |
| a - append old logs |
| n - do not write any logs |
| |
| -q|-quiet |
| Print all messages to the file instead of stdout and stderr. |
| Default path (can be changed by -log-path option): |
| ".$In::Opt{"DefaultLog"}." |
| |
| REPORT PATH: |
| Compatibility report will be generated to: |
| compat_reports/LIB_NAME/V1_to_V2/compat_report.html |
| |
| LOG PATH: |
| Log will be generated to: |
| logs/LIB_NAME/V1/log.txt |
| logs/LIB_NAME/V2/log.txt |
| |
| EXIT CODES: |
| 0 - Compatible. The tool has run without any errors. |
| non-zero - Incompatible or the tool has run with errors. |
| |
| MORE INFO: |
| ".$HomePage{"Doc"}." |
| ".$HomePage{"Dev"}."\n\n"); |
| } |
| |
| # Aliases |
| my (%SymbolInfo, %TypeInfo, %TName_Tid, %Constants) = (); |
| |
| # Global |
| my %Cache; |
| my $TOP_REF = "<a class='top_ref' href='#Top'>to the top</a>"; |
| my %RESULT; |
| |
| # Counter |
| my %CheckedTypes; |
| my %CheckedSymbols; |
| |
| # Classes |
| my %VirtualTable; |
| my %VirtualTable_Model; |
| my %VTableClass; |
| my %AllocableClass; |
| my %ClassMethods; |
| my %ClassNames; |
| my %OverriddenMethods; |
| |
| # Symbols |
| my %Func_ShortName; |
| my %AddSymbolParams; |
| my %GlobalDataObject; |
| |
| # Merging |
| my %CompSign; |
| my %AddedInt; |
| my %RemovedInt; |
| my %AddedInt_Virt; |
| my %RemovedInt_Virt; |
| my %VirtualReplacement; |
| my %ChangedTypedef; |
| my %CompatRules; |
| my %IncompleteRules; |
| my %UnknownRules; |
| my %VTableChanged_M; |
| my %ReturnedClass; |
| my %ParamClass; |
| my %SourceAlternative; |
| my %SourceAlternative_B; |
| my %SourceReplacement; |
| |
| # Extra |
| my %ExtendedSymbols; |
| |
| #Report |
| my %TypeChanges; |
| |
| #Speedup |
| my %TypeProblemsIndex; |
| |
| # Calling Conventions |
| my %UseConv_Real = ( |
| 1=>{ "R"=>0, "P"=>0 }, |
| 2=>{ "R"=>0, "P"=>0 } |
| ); |
| |
| # ABI Dump |
| my %UsedDump; |
| |
| # Recursion locks |
| my @RecurTypes; |
| my @RecurTypes_Diff; |
| my @RecurConstant; |
| |
| # Problem descriptions |
| my %CompatProblems; |
| my %CompatProblems_Constants; |
| my %TotalAffected; |
| |
| # Reports |
| my $ContentID = 1; |
| my $ContentSpanStart = "<span class=\"section\" onclick=\"javascript:showContent(this, 'CONTENT_ID')\">\n"; |
| my $ContentSpanStart_Affected = "<span class=\"sect_aff\" onclick=\"javascript:showContent(this, 'CONTENT_ID')\">\n"; |
| my $ContentSpanStart_Info = "<span class=\"sect_info\" onclick=\"javascript:showContent(this, 'CONTENT_ID')\">\n"; |
| my $ContentSpanEnd = "</span>\n"; |
| my $ContentDivStart = "<div id=\"CONTENT_ID\" style=\"display:none;\">\n"; |
| my $ContentDivEnd = "</div>\n"; |
| my $Content_Counter = 0; |
| |
| my %Severity_Val=( |
| "High"=>3, |
| "Medium"=>2, |
| "Low"=>1, |
| "Safe"=>-1 |
| ); |
| |
| sub getModules() |
| { |
| my $TOOL_DIR = dirname($0); |
| if(not $TOOL_DIR) |
| { # patch for MS Windows |
| $TOOL_DIR = "."; |
| } |
| my @SEARCH_DIRS = ( |
| # tool's directory |
| abs_path($TOOL_DIR), |
| # relative path to modules |
| abs_path($TOOL_DIR)."/../share/abi-compliance-checker", |
| # install path |
| 'MODULES_INSTALL_PATH' |
| ); |
| foreach my $DIR (@SEARCH_DIRS) |
| { |
| if($DIR!~/\A(\/|\w+:[\/\\])/) |
| { # relative path |
| $DIR = abs_path($TOOL_DIR)."/".$DIR; |
| } |
| if(-d $DIR."/modules") { |
| return $DIR."/modules"; |
| } |
| } |
| |
| print STDERR "ERROR: can't find modules (Did you installed the tool by 'make install' command?)\n"; |
| exit(9); # Module_Error |
| } |
| |
| sub loadModule($) |
| { |
| my $Name = $_[0]; |
| if(defined $LoadedModules{$Name}) { |
| return; |
| } |
| my $Path = $MODULES_DIR."/Internals/$Name.pm"; |
| if(not -f $Path) |
| { |
| print STDERR "can't access \'$Path\'\n"; |
| exit(2); |
| } |
| require $Path; |
| $LoadedModules{$Name} = 1; |
| } |
| |
| sub readModule($$) |
| { |
| my ($Module, $Name) = @_; |
| my $Path = $MODULES_DIR."/Internals/$Module/".$Name; |
| if(not -f $Path) { |
| exitStatus("Module_Error", "can't access \'$Path\'"); |
| } |
| return readFile($Path); |
| } |
| |
| sub selectSymbolNs($$) |
| { |
| my ($Symbol, $LVer) = @_; |
| |
| my $NS = $CompSign{$LVer}{$Symbol}{"NameSpace"}; |
| if(not $NS) |
| { |
| if(my $Class = $CompSign{$LVer}{$Symbol}{"Class"}) { |
| $NS = $TypeInfo{$LVer}{$Class}{"NameSpace"}; |
| } |
| } |
| if($NS) |
| { |
| if(defined $In::ABI{$LVer}{"NameSpaces"}{$NS}) { |
| return $NS; |
| } |
| else |
| { |
| while($NS=~s/::[^:]+\Z//) |
| { |
| if(defined $In::ABI{$LVer}{"NameSpaces"}{$NS}) { |
| return $NS; |
| } |
| } |
| } |
| } |
| |
| return ""; |
| } |
| |
| sub selectTypeNs($$) |
| { |
| my ($TypeName, $LVer) = @_; |
| |
| my $Tid = $TName_Tid{$LVer}{$TypeName}; |
| |
| if(my $NS = $TypeInfo{$LVer}{$Tid}{"NameSpace"}) |
| { |
| if(defined $In::ABI{$LVer}{"NameSpaces"}{$NS}) { |
| return $NS; |
| } |
| else |
| { |
| while($NS=~s/::[^:]+\Z//) |
| { |
| if(defined $In::ABI{$LVer}{"NameSpaces"}{$NS}) { |
| return $NS; |
| } |
| } |
| } |
| } |
| return ""; |
| } |
| |
| sub getChargeLevel($$) |
| { |
| my ($Symbol, $LVer) = @_; |
| |
| if(defined $CompSign{$LVer}{$Symbol} |
| and $CompSign{$LVer}{$Symbol}{"ShortName"}) |
| { |
| if($CompSign{$LVer}{$Symbol}{"Constructor"}) |
| { |
| if($Symbol=~/C([1-4])[EI]/) |
| { # [in-charge] |
| # [not-in-charge] |
| return "[C".$1."]"; |
| } |
| } |
| elsif($CompSign{$LVer}{$Symbol}{"Destructor"}) |
| { |
| if($Symbol=~/D([0-4])[EI]/) |
| { # [in-charge] |
| # [not-in-charge] |
| # [in-charge-deleting] |
| return "[D".$1."]"; |
| } |
| } |
| } |
| |
| return undef; |
| } |
| |
| sub blackName($) |
| { |
| my $N = $_[0]; |
| return "<span class='iname_b'>".$N."</span>"; |
| } |
| |
| sub highLight_ItalicColor($$) |
| { |
| my ($Symbol, $LVer) = @_; |
| return getSignature($Symbol, $LVer, "Full|HTML|Italic|Color"); |
| } |
| |
| sub getSignature($$$) |
| { |
| my ($Symbol, $LVer, $Kind) = @_; |
| if($Cache{"getSignature"}{$LVer}{$Symbol}{$Kind}) { |
| return $Cache{"getSignature"}{$LVer}{$Symbol}{$Kind}; |
| } |
| |
| # settings |
| my ($Html, $Simple, $Italic, $Color, $Full, $ShowClass, $ShowName, |
| $ShowParams, $ShowQuals, $ShowAttr, $Desc, $Target) = (); |
| |
| if($Kind=~/HTML/) { |
| $Html = 1; |
| } |
| if($Kind=~/Simple/) { |
| $Simple = 1; |
| } |
| if($Kind=~/Italic/) { |
| $Italic = 1; |
| } |
| if($Kind=~/Color/) { |
| $Color = 1; |
| } |
| |
| if($Kind=~/Full/) { |
| $Full = 1; |
| } |
| if($Kind=~/Class/) { |
| $ShowClass = 1; |
| } |
| if($Kind=~/Name/) { |
| $ShowName = 1; |
| } |
| if($Kind=~/Param/) { |
| $ShowParams = 1; |
| } |
| if($Kind=~/Qual/) { |
| $ShowQuals = 1; |
| } |
| if($Kind=~/Attr/) { |
| $ShowAttr = 1; |
| } |
| if($Kind=~/Desc/) { |
| $Desc = 1; |
| } |
| |
| if($Kind=~/Target=(\d+)/) { |
| $Target = $1; |
| } |
| |
| if($Full) |
| { |
| $ShowName = 1; |
| $ShowClass = 1; |
| } |
| |
| my ($MnglName, $VSpec, $SVer) = symbolParts($Symbol); |
| |
| if(index($Symbol, "_ZTV")==0) |
| { |
| if(my $ClassId = $CompSign{$LVer}{$Symbol}{"Class"}) |
| { |
| my $ClassName = $TypeInfo{$LVer}{$ClassId}{"Name"}; |
| $ClassName=~s/\bstruct //g; |
| |
| if($Html) { |
| return "vtable for ".specChars($ClassName)." <span class='attr'>[data]</span>"; |
| } |
| |
| return "vtable for $ClassName [data]"; |
| } |
| else |
| { # failure |
| return undef; |
| } |
| } |
| |
| my $Mngl = (index($Symbol, "_Z")==0 or index($Symbol, "?")==0); |
| |
| my $Signature = ""; |
| if($ShowName) |
| { |
| my $ShortName = $CompSign{$LVer}{$Symbol}{"ShortName"}; |
| |
| if(not $Mngl and defined $CompSign{$LVer}{$Symbol}{"Alias"}) |
| { # alias symbol |
| $ShortName = $MnglName; |
| } |
| |
| if($Html) { |
| $ShortName = specChars($ShortName); |
| } |
| |
| $Signature .= $ShortName; |
| |
| my $ClassId = $CompSign{$LVer}{$Symbol}{"Class"}; |
| |
| if($Mngl or $ClassId) |
| { |
| if($CompSign{$LVer}{$Symbol}{"Destructor"}) { |
| $Signature = "~".$Signature; |
| } |
| |
| if($ShowClass) |
| { |
| if($ClassId) |
| { |
| my $Class = $TypeInfo{$LVer}{$ClassId}{"Name"}; |
| $Class=~s/\bstruct //g; |
| |
| if($Html) { |
| $Class = specChars($Class); |
| } |
| |
| $Signature = $Class."::".$Signature; |
| } |
| elsif(my $NameSpace = $CompSign{$LVer}{$Symbol}{"NameSpace"}) { |
| $Signature = $NameSpace."::".$Signature; |
| } |
| } |
| } |
| } |
| |
| my @Params = (); |
| if(defined $CompSign{$LVer}{$Symbol}{"Param"}) |
| { |
| foreach my $PPos (sort {$a<=>$b} keys(%{$CompSign{$LVer}{$Symbol}{"Param"}})) |
| { |
| my $PTid = $CompSign{$LVer}{$Symbol}{"Param"}{$PPos}{"type"}; |
| if(not $PTid) { |
| next; |
| } |
| |
| if(my $PTName = $TypeInfo{$LVer}{$PTid}{"Name"}) |
| { |
| foreach my $Typedef (keys(%ChangedTypedef)) |
| { |
| if(index($PTName, $Typedef)!=-1) |
| { |
| if($PTName=~/\b\Q$Typedef\E\b/) |
| { |
| $PTName = uncoverTypedefs($PTName, $LVer); |
| last; |
| } |
| } |
| } |
| |
| $PTName=~s/\(kind=\d+\)//g; |
| |
| if($Html) { |
| $PTName = specChars($PTName); |
| } |
| |
| my $PName = $CompSign{$LVer}{$Symbol}{"Param"}{$PPos}{"name"}; |
| if($Mngl and ($PName eq "this" or $PName eq "__in_chrg" or $PName eq "__vtt_parm")) |
| { # do NOT show first hidden "this"-parameter |
| next; |
| } |
| |
| if($PName and ($Full or $ShowParams)) |
| { |
| if($Simple) { |
| $PName = "<i>$PName</i>"; |
| } |
| elsif($Html) |
| { |
| if(defined $Target |
| and $Target==adjustParamPos($PPos, $Symbol, $LVer)) { |
| $PName = "<span class='fp'>$PName</span>"; |
| } |
| elsif($Color) { |
| $PName = "<span class='color_p'>$PName</span>"; |
| } |
| elsif($Italic) { |
| $PName = "<i>$PName</i>"; |
| } |
| } |
| |
| push(@Params, createMemDecl($PTName, $PName)); |
| } |
| else { |
| push(@Params, $PTName); |
| } |
| } |
| } |
| } |
| |
| if($Simple) { |
| $Signature = "<b>".$Signature."</b>"; |
| } |
| |
| if($CompSign{$LVer}{$Symbol}{"Data"}) |
| { |
| $Signature .= " [data]"; |
| } |
| else |
| { |
| if($Full or $ShowAttr) |
| { |
| if($Mngl) |
| { |
| if(my $ChargeLevel = getChargeLevel($Symbol, $LVer)) { |
| $Signature .= " ".$ChargeLevel; |
| } |
| } |
| } |
| |
| if($Html and not $Simple) |
| { |
| $Signature .= " "; |
| |
| if($Desc) { |
| $Signature .= "<span class='sym_pd'>"; |
| } |
| else { |
| $Signature .= "<span class='sym_p'>"; |
| } |
| if(@Params) |
| { |
| foreach my $Pos (0 .. $#Params) |
| { |
| my $Name = ""; |
| |
| if($Pos==0) { |
| $Name .= "( "; |
| } |
| |
| $Name .= $Params[$Pos]; |
| |
| $Name = "<span>".$Name."</span>"; |
| |
| if($Pos==$#Params) { |
| $Name .= " )"; |
| } |
| else { |
| $Name .= ", "; |
| } |
| |
| $Signature .= $Name; |
| } |
| } |
| else { |
| $Signature .= "( )"; |
| } |
| $Signature .= "</span>"; |
| } |
| else |
| { |
| if(@Params) { |
| $Signature .= " ( ".join(", ", @Params)." )"; |
| } |
| else { |
| $Signature .= " ( )"; |
| } |
| } |
| |
| |
| if($Full or $ShowQuals) |
| { |
| if($CompSign{$LVer}{$Symbol}{"Const"} |
| or $Symbol=~/\A_ZN(V|)K/) { |
| $Signature .= " const"; |
| } |
| |
| if($CompSign{$LVer}{$Symbol}{"Volatile"} |
| or $Symbol=~/\A_ZN(K|)V/) { |
| $Signature .= " volatile"; |
| } |
| } |
| |
| if($Full or $ShowAttr) |
| { |
| if($CompSign{$LVer}{$Symbol}{"Static"} |
| and $Mngl) { |
| $Signature .= " [static]"; |
| } |
| } |
| } |
| |
| if($Full) |
| { |
| if(defined $In::Opt{"ShowRetVal"}) |
| { |
| if(my $ReturnId = $CompSign{$LVer}{$Symbol}{"Return"}) |
| { |
| my $RName = $TypeInfo{$LVer}{$ReturnId}{"Name"}; |
| if($Simple) { |
| $Signature .= " <b>:</b> ".specChars($RName); |
| } |
| elsif($Html) { |
| $Signature .= "<span class='sym_p nowrap'>  <b>:</b>  ".specChars($RName)."</span>"; |
| } |
| else { |
| $Signature .= " : ".$RName; |
| } |
| } |
| } |
| |
| if($SVer) |
| { |
| if($Html) { |
| $Signature .= "<span class='sym_ver'> $VSpec $SVer</span>"; |
| } |
| else { |
| $Signature .= $VSpec.$SVer; |
| } |
| } |
| } |
| |
| if($Html) { |
| $Signature=~s!(\[C\d\]|\[D\d\]|\[static\]|\[data\])!<span class='attr'>$1</span>!g; |
| } |
| |
| if($Simple) { |
| $Signature=~s/\[\]/\[ \]/g; |
| } |
| elsif($Html) |
| { |
| $Signature=~s!\[\]![ ]!g; |
| $Signature=~s!operator=!operator =!g; |
| } |
| |
| return ($Cache{"getSignature"}{$LVer}{$Symbol}{$Kind} = $Signature); |
| } |
| |
| sub createMemDecl($$) |
| { |
| my ($TName, $Member) = @_; |
| if($TName=~/\([\*]+\)/) |
| { |
| $TName=~s/\(([\*]+)\)/\($1$Member\)/; |
| return $TName; |
| } |
| else |
| { |
| my @ArraySizes = (); |
| while($TName=~s/(\[[^\[\]]*\])\Z//) { |
| push(@ArraySizes, $1); |
| } |
| return $TName." ".$Member.join("", @ArraySizes); |
| } |
| } |
| |
| sub prepareSymbols($) |
| { |
| my $LVer = $_[0]; |
| |
| if(not keys(%{$SymbolInfo{$LVer}})) |
| { # check if input is valid |
| if(not $In::Opt{"ExtendedCheck"}) |
| { |
| if($In::Opt{"CheckHeadersOnly"}) { |
| exitStatus("Empty_Set", "the set of public symbols is empty"); |
| } |
| else { |
| exitStatus("Empty_Intersection", "the sets of public symbols in headers and libraries have empty intersection"); |
| } |
| } |
| } |
| |
| foreach my $InfoId (sort {$b<=>$a} keys(%{$SymbolInfo{$LVer}})) |
| { |
| my $MnglName = $SymbolInfo{$LVer}{$InfoId}{"MnglName"}; |
| my $ShortName = $SymbolInfo{$LVer}{$InfoId}{"ShortName"}; |
| |
| if(not $MnglName) |
| { |
| $SymbolInfo{$LVer}{$InfoId}{"MnglName"} = $ShortName; |
| $MnglName = $ShortName; |
| } |
| |
| # symbol and its symlink have same signatures |
| if(my $SVer = $In::ABI{$LVer}{"SymbolVersion"}{$MnglName}) { |
| $CompSign{$LVer}{$SVer} = $SymbolInfo{$LVer}{$InfoId}; |
| } |
| |
| if(my $Alias = $SymbolInfo{$LVer}{$InfoId}{"Alias"}) |
| { |
| $CompSign{$LVer}{$Alias} = $SymbolInfo{$LVer}{$InfoId}; |
| |
| if(my $SAVer = $In::ABI{$LVer}{"SymbolVersion"}{$Alias}) { |
| $CompSign{$LVer}{$SAVer} = $SymbolInfo{$LVer}{$InfoId}; |
| } |
| } |
| |
| if(defined $CompSign{$LVer}{$MnglName}) |
| { # NOTE: duplicated entries in the ABI Dump |
| if(defined $SymbolInfo{$LVer}{$InfoId}{"Param"}) |
| { |
| if($SymbolInfo{$LVer}{$InfoId}{"Param"}{0}{"name"} eq "p1") { |
| next; |
| } |
| } |
| } |
| |
| if(not $CompSign{$LVer}{$MnglName}{"MnglName"}) |
| { # NOTE: global data may enter here twice |
| $CompSign{$LVer}{$MnglName} = $SymbolInfo{$LVer}{$InfoId}; |
| } |
| |
| if(not defined $SymbolInfo{$LVer}{$InfoId}{"Unmangled"}) |
| { |
| if($MnglName eq $ShortName) { |
| $SymbolInfo{$LVer}{$InfoId}{"Unmangled"} = $ShortName; |
| } |
| else { |
| $SymbolInfo{$LVer}{$InfoId}{"Unmangled"} = getSignature($MnglName, $LVer, "Class|Name|Qual"); |
| } |
| } |
| } |
| |
| if($In::ABI{$LVer}{"Language"} eq "C++" |
| and getCmdPath("c++filt")) |
| { |
| my @VTables = (); |
| foreach my $S (keys(%{$In::ABI{$LVer}{"SymLib"}})) |
| { |
| if(index($S, "_ZTV")==0) { |
| push(@VTables, $S); |
| } |
| } |
| translateSymbols(@VTables, $LVer); |
| } |
| |
| if($In::Opt{"ExtendedCheck"}) { |
| addExtension($LVer); |
| } |
| |
| foreach my $Symbol (keys(%{$CompSign{$LVer}})) |
| { # detect allocable classes with public exported constructors |
| # or classes with auto-generated or inline-only constructors |
| # and other temp info |
| if(my $ClassId = $CompSign{$LVer}{$Symbol}{"Class"}) |
| { |
| my $ClassName = $TypeInfo{$LVer}{$ClassId}{"Name"}; |
| if($CompSign{$LVer}{$Symbol}{"Constructor"} |
| and not $CompSign{$LVer}{$Symbol}{"InLine"}) |
| { # Class() { ... } will not be exported |
| if(not $CompSign{$LVer}{$Symbol}{"Private"}) |
| { |
| if($In::Opt{"CheckHeadersOnly"} or linkSymbol($Symbol, $LVer, "-Deps")) { |
| $AllocableClass{$LVer}{$ClassName} = 1; |
| } |
| } |
| } |
| if(not $CompSign{$LVer}{$Symbol}{"Private"}) |
| { # all imported class methods |
| if(symbolFilter($Symbol, $CompSign{$LVer}{$Symbol}, "Affected", "Binary", $LVer)) |
| { |
| if($In::Opt{"CheckHeadersOnly"}) |
| { |
| if(not $CompSign{$LVer}{$Symbol}{"InLine"} |
| or $CompSign{$LVer}{$Symbol}{"Virt"}) |
| { # all symbols except non-virtual inline |
| $ClassMethods{"Binary"}{$LVer}{$ClassName}{$Symbol} = 1; |
| } |
| } |
| else { |
| $ClassMethods{"Binary"}{$LVer}{$ClassName}{$Symbol} = 1; |
| } |
| } |
| if(symbolFilter($Symbol, $CompSign{$LVer}{$Symbol}, "Affected", "Source", $LVer)) { |
| $ClassMethods{"Source"}{$LVer}{$ClassName}{$Symbol} = 1; |
| } |
| } |
| $ClassNames{$LVer}{$ClassName} = 1; |
| } |
| if(my $RetId = $CompSign{$LVer}{$Symbol}{"Return"}) |
| { |
| my %Base = getBaseType($RetId, $LVer); |
| if(defined $Base{"Type"} |
| and $Base{"Type"}=~/Struct|Class/) |
| { |
| my $Name = $TypeInfo{$LVer}{$Base{"Tid"}}{"Name"}; |
| if($Name=~/<([^<>\s]+)>/) |
| { |
| if(my $Tid = getTypeIdByName($1, $LVer)) { |
| $ReturnedClass{$LVer}{$Tid} = 1; |
| } |
| } |
| else { |
| $ReturnedClass{$LVer}{$Base{"Tid"}} = 1; |
| } |
| } |
| } |
| foreach my $Num (keys(%{$CompSign{$LVer}{$Symbol}{"Param"}})) |
| { |
| my $PId = $CompSign{$LVer}{$Symbol}{"Param"}{$Num}{"type"}; |
| if(getPLevel($PId, $LVer)>=1) |
| { |
| if(my %Base = getBaseType($PId, $LVer)) |
| { |
| if($Base{"Type"}=~/Struct|Class/) |
| { |
| $ParamClass{$LVer}{$Base{"Tid"}}{$Symbol} = 1; |
| foreach my $SubId (getSubClasses($Base{"Tid"}, $LVer, 1)) |
| { # mark all derived classes |
| $ParamClass{$LVer}{$SubId}{$Symbol} = 1; |
| } |
| } |
| } |
| } |
| } |
| |
| # mapping {short name => symbols} |
| $Func_ShortName{$LVer}{$CompSign{$LVer}{$Symbol}{"ShortName"}}{$Symbol} = 1; |
| } |
| |
| foreach my $ClassName (keys(%{$In::ABI{$LVer}{"ClassVTable"}})) |
| { |
| my $MnglName = $In::ABI{$LVer}{"ClassVTable"}{$ClassName}; |
| |
| if(my $ClassId = $TName_Tid{$LVer}{$ClassName}) |
| { |
| if(my $H = $TypeInfo{$LVer}{$ClassId}{"Header"}) { |
| $CompSign{$LVer}{$MnglName}{"Header"} = $H; |
| } |
| if(my $S = $TypeInfo{$LVer}{$ClassId}{"Source"}) { |
| $CompSign{$LVer}{$MnglName}{"Source"} = $S; |
| } |
| $CompSign{$LVer}{$MnglName}{"Class"} = $ClassId; |
| $CompSign{$LVer}{$MnglName}{"Unmangled"} = getSignature($MnglName, $LVer, "Class|Name"); |
| } |
| |
| $VTableClass{$LVer}{$MnglName} = $ClassName; |
| } |
| |
| # types |
| foreach my $TypeId (keys(%{$TypeInfo{$LVer}})) |
| { |
| if(my $TName = $TypeInfo{$LVer}{$TypeId}{"Name"}) |
| { |
| if(defined $TypeInfo{$LVer}{$TypeId}{"VTable"}) { |
| $ClassNames{$LVer}{$TName} = 1; |
| } |
| if(defined $TypeInfo{$LVer}{$TypeId}{"Base"}) |
| { |
| $ClassNames{$LVer}{$TName} = 1; |
| foreach my $Bid (keys(%{$TypeInfo{$LVer}{$TypeId}{"Base"}})) |
| { |
| if(my $BName = $TypeInfo{$LVer}{$Bid}{"Name"}) { |
| $ClassNames{$LVer}{$BName} = 1; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| sub addExtension($) |
| { |
| my $LVer = $_[0]; |
| foreach my $Tid (sort {$a<=>$b} keys(%{$TypeInfo{$LVer}})) |
| { |
| if(pickType($Tid, $LVer)) |
| { |
| my $TName = $TypeInfo{$LVer}{$Tid}{"Name"}; |
| $TName=~s/\A(struct|union|class|enum) //; |
| my $Symbol = "external_func_".$TName; |
| $Symbol=~s/\W+/_/g; |
| |
| %{$CompSign{$LVer}{$Symbol}} = ( |
| "Header" => "extended.h", |
| "ShortName" => $Symbol, |
| "MnglName" => $Symbol, |
| "Param" => { 0 => { "type"=>$Tid, "name"=>"p1" } } |
| ); |
| |
| $ExtendedSymbols{$Symbol} = 1; |
| $CheckedSymbols{"Binary"}{$Symbol} = 1; |
| $CheckedSymbols{"Source"}{$Symbol} = 1; |
| } |
| } |
| |
| $ExtendedSymbols{"external_func_0"} = 1; |
| $CheckedSymbols{"Binary"}{"external_func_0"} = 1; |
| $CheckedSymbols{"Source"}{"external_func_0"} = 1; |
| } |
| |
| sub findMethod($$$) |
| { |
| my ($VirtFunc, $ClassId, $LVer) = @_; |
| foreach my $BaseClass_Id (keys(%{$TypeInfo{$LVer}{$ClassId}{"Base"}})) |
| { |
| if(my $VirtMethodInClass = findMethod_Class($VirtFunc, $BaseClass_Id, $LVer)) { |
| return $VirtMethodInClass; |
| } |
| elsif(my $VirtMethodInBaseClasses = findMethod($VirtFunc, $BaseClass_Id, $LVer)) { |
| return $VirtMethodInBaseClasses; |
| } |
| } |
| return undef; |
| } |
| |
| sub findMethod_Class($$$) |
| { |
| my ($VirtFunc, $ClassId, $LVer) = @_; |
| |
| my $ClassName = $TypeInfo{$LVer}{$ClassId}{"Name"}; |
| if(not defined $VirtualTable{$LVer}{$ClassName}) { |
| return undef; |
| } |
| my $TargetSuffix = getSignature($VirtFunc, $LVer, "Qual"); |
| my $TargetShortName = $CompSign{$LVer}{$VirtFunc}{"ShortName"}; |
| |
| foreach my $Candidate (keys(%{$VirtualTable{$LVer}{$ClassName}})) |
| { # search for interface with the same parameters suffix (overridden) |
| if($TargetSuffix eq getSignature($Candidate, $LVer, "Qual")) |
| { |
| if($CompSign{$LVer}{$VirtFunc}{"Destructor"}) |
| { |
| if($CompSign{$LVer}{$Candidate}{"Destructor"}) |
| { |
| if(($VirtFunc=~/D0E/ and $Candidate=~/D0E/) |
| or ($VirtFunc=~/D1E/ and $Candidate=~/D1E/) |
| or ($VirtFunc=~/D2E/ and $Candidate=~/D2E/)) { |
| return $Candidate; |
| } |
| } |
| } |
| else |
| { |
| if($TargetShortName eq $CompSign{$LVer}{$Candidate}{"ShortName"}) { |
| return $Candidate; |
| } |
| } |
| } |
| } |
| return undef; |
| } |
| |
| sub registerVTable($) |
| { |
| my $LVer = $_[0]; |
| foreach my $Symbol (keys(%{$CompSign{$LVer}})) |
| { |
| if($CompSign{$LVer}{$Symbol}{"Virt"} |
| or $CompSign{$LVer}{$Symbol}{"PureVirt"}) |
| { |
| my $ClassName = $TypeInfo{$LVer}{$CompSign{$LVer}{$Symbol}{"Class"}}{"Name"}; |
| if(not $In::Opt{"StdcxxTesting"} and $ClassName=~/\A(std::|__cxxabi)/) { |
| next; |
| } |
| if($CompSign{$LVer}{$Symbol}{"Destructor"} |
| and $Symbol=~/D2E/) |
| { # pure virtual D2-destructors are marked as "virt" in the dump |
| # virtual D2-destructors are NOT marked as "virt" in the dump |
| # both destructors are not presented in the v-table |
| next; |
| } |
| my ($MnglName, $VersionSpec, $SymbolVersion) = symbolParts($Symbol); |
| $VirtualTable{$LVer}{$ClassName}{$MnglName} = 1; |
| } |
| } |
| } |
| |
| sub registerOverriding($) |
| { |
| my $LVer = $_[0]; |
| my @Classes = keys(%{$VirtualTable{$LVer}}); |
| @Classes = sort {$TName_Tid{$LVer}{$a}<=>$TName_Tid{$LVer}{$b}} @Classes; |
| foreach my $ClassName (@Classes) |
| { |
| foreach my $VirtFunc (keys(%{$VirtualTable{$LVer}{$ClassName}})) |
| { |
| if($CompSign{$LVer}{$VirtFunc}{"PureVirt"}) |
| { # pure virtuals |
| next; |
| } |
| my $ClassId = $TName_Tid{$LVer}{$ClassName}; |
| if(my $Overridden = findMethod($VirtFunc, $ClassId, $LVer)) |
| { |
| if($CompSign{$LVer}{$Overridden}{"Virt"} |
| or $CompSign{$LVer}{$Overridden}{"PureVirt"}) |
| { # both overridden virtual methods |
| # and implemented pure virtual methods |
| $CompSign{$LVer}{$VirtFunc}{"Override"} = $Overridden; |
| $OverriddenMethods{$LVer}{$Overridden}{$VirtFunc} = 1; |
| |
| # remove from v-table model |
| delete($VirtualTable{$LVer}{$ClassName}{$VirtFunc}); |
| } |
| } |
| } |
| if(not keys(%{$VirtualTable{$LVer}{$ClassName}})) { |
| delete($VirtualTable{$LVer}{$ClassName}); |
| } |
| } |
| } |
| |
| sub setVirtFuncPositions($) |
| { |
| my $LVer = $_[0]; |
| foreach my $ClassName (keys(%{$VirtualTable{$LVer}})) |
| { |
| my ($Num, $Rel) = (1, 0); |
| |
| if(my @Funcs = sort keys(%{$VirtualTable{$LVer}{$ClassName}})) |
| { |
| if($UsedDump{$LVer}{"DWARF"}) { |
| @Funcs = sort {$CompSign{$LVer}{$a}{"VirtPos"}<=>$CompSign{$LVer}{$b}{"VirtPos"}} @Funcs; |
| } |
| else { |
| @Funcs = sort {$CompSign{$LVer}{$a}{"Line"}<=>$CompSign{$LVer}{$b}{"Line"}} @Funcs; |
| } |
| foreach my $VirtFunc (@Funcs) |
| { |
| if($UsedDump{$LVer}{"DWARF"}) |
| { |
| if(defined $CompSign{$LVer}{$VirtFunc}{"VirtPos"}) { |
| $VirtualTable{$LVer}{$ClassName}{$VirtFunc} = $CompSign{$LVer}{$VirtFunc}{"VirtPos"}; |
| } |
| else { |
| $VirtualTable{$LVer}{$ClassName}{$VirtFunc} = 1; |
| } |
| } |
| else { |
| $VirtualTable{$LVer}{$ClassName}{$VirtFunc} = $Num++; |
| } |
| |
| # set relative positions |
| if(defined $VirtualTable{1}{$ClassName} and defined $VirtualTable{1}{$ClassName}{$VirtFunc} |
| and defined $VirtualTable{2}{$ClassName} and defined $VirtualTable{2}{$ClassName}{$VirtFunc}) |
| { # relative position excluding added and removed virtual functions |
| if(not $CompSign{1}{$VirtFunc}{"Override"} |
| and not $CompSign{2}{$VirtFunc}{"Override"}) { |
| $CompSign{$LVer}{$VirtFunc}{"RelPos"} = $Rel++; |
| } |
| } |
| } |
| } |
| } |
| foreach my $ClassName (keys(%{$ClassNames{$LVer}})) |
| { |
| my $AbsNum = 1; |
| foreach my $VirtFunc (getVTable_Model($ClassName, $LVer)) { |
| $VirtualTable_Model{$LVer}{$ClassName}{$VirtFunc} = $AbsNum++; |
| } |
| } |
| } |
| |
| sub getVTable_Model($$) |
| { # return an ordered list of v-table elements |
| my ($ClassName, $LVer) = @_; |
| |
| my $ClassId = $TName_Tid{$LVer}{$ClassName}; |
| my @Bases = getBaseClasses($ClassId, $LVer, 1); |
| my @Elements = (); |
| foreach my $BaseId (@Bases, $ClassId) |
| { |
| if(my $BName = $TypeInfo{$LVer}{$BaseId}{"Name"}) |
| { |
| if(defined $VirtualTable{$LVer}{$BName}) |
| { |
| my @VFuncs = sort keys(%{$VirtualTable{$LVer}{$BName}}); |
| if($UsedDump{$LVer}{"DWARF"}) { |
| @VFuncs = sort {$CompSign{$LVer}{$a}{"VirtPos"} <=> $CompSign{$LVer}{$b}{"VirtPos"}} @VFuncs; |
| } |
| else { |
| @VFuncs = sort {$CompSign{$LVer}{$a}{"Line"} <=> $CompSign{$LVer}{$b}{"Line"}} @VFuncs; |
| } |
| |
| foreach my $VFunc (@VFuncs) { |
| push(@Elements, $VFunc); |
| } |
| } |
| } |
| } |
| return @Elements; |
| } |
| |
| sub getVShift($$) |
| { |
| my ($ClassId, $LVer) = @_; |
| my @Bases = getBaseClasses($ClassId, $LVer, 1); |
| my $VShift = 0; |
| foreach my $BaseId (@Bases) |
| { |
| if(my $BName = $TypeInfo{$LVer}{$BaseId}{"Name"}) |
| { |
| if(defined $VirtualTable{$LVer}{$BName}) { |
| $VShift+=keys(%{$VirtualTable{$LVer}{$BName}}); |
| } |
| } |
| } |
| return $VShift; |
| } |
| |
| sub getShift($$) |
| { |
| my ($ClassId, $LVer) = @_; |
| my @Bases = getBaseClasses($ClassId, $LVer, 0); |
| my $Shift = 0; |
| foreach my $BaseId (@Bases) |
| { |
| if(my $Size = $TypeInfo{$LVer}{$BaseId}{"Size"}) |
| { |
| if($Size!=1) |
| { # not empty base class |
| $Shift+=$Size; |
| } |
| } |
| } |
| return $Shift; |
| } |
| |
| sub getVTable_Size($$) |
| { # number of v-table elements |
| my ($ClassName, $LVer) = @_; |
| my $Size = 0; |
| # three approaches |
| if(not $Size) |
| { # real size |
| if(my %VTable = getVTable_Real($ClassName, $LVer)) { |
| $Size = keys(%VTable); |
| } |
| } |
| if(not $Size) |
| { # shared library symbol size |
| if(my $VTSym = $In::ABI{$LVer}{"ClassVTable"}{$ClassName}) |
| { |
| if($Size = getSymbolSize($VTSym, $LVer)) { |
| $Size /= $In::ABI{$LVer}{"WordSize"}; |
| } |
| } |
| } |
| if(not $Size) |
| { # model size |
| if(defined $VirtualTable_Model{$LVer}{$ClassName}) { |
| $Size = keys(%{$VirtualTable_Model{$LVer}{$ClassName}}) + 2; |
| } |
| } |
| return $Size; |
| } |
| |
| sub isLeafClass($$) |
| { |
| my ($ClassId, $LVer) = @_; |
| if(not defined $In::ABI{$LVer}{"Class_SubClasses"}{$ClassId} |
| or not keys(%{$In::ABI{$LVer}{"Class_SubClasses"}{$ClassId}})) { |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| sub havePubFields($) |
| { # check structured type for public fields |
| return isAccessible($_[0], {}, 0, -1); |
| } |
| |
| sub isAccessible($$$$) |
| { # check interval in structured type for public fields |
| my ($TypePtr, $Skip, $Start, $End) = @_; |
| if(not $TypePtr) { |
| return 0; |
| } |
| if($End==-1) { |
| $End = keys(%{$TypePtr->{"Memb"}})-1; |
| } |
| foreach my $MemPos (sort {$a<=>$b} keys(%{$TypePtr->{"Memb"}})) |
| { |
| if($Skip and $Skip->{$MemPos}) |
| { # skip removed/added fields |
| next; |
| } |
| if(int($MemPos)>=$Start and int($MemPos)<=$End) |
| { |
| if(isPublic($TypePtr, $MemPos)) { |
| return ($MemPos+1); |
| } |
| } |
| } |
| return 0; |
| } |
| |
| sub isPublic($$) |
| { |
| my ($TypePtr, $FieldPos) = @_; |
| |
| return 0 if(not $TypePtr); |
| return 0 if(not defined $TypePtr->{"Memb"}{$FieldPos}); |
| return 0 if(not defined $TypePtr->{"Memb"}{$FieldPos}{"name"}); |
| |
| my $Access = $TypePtr->{"Memb"}{$FieldPos}{"access"}; |
| if($Access eq "private") |
| { # by access in C++ language |
| return 0; |
| } |
| |
| # by name in C language |
| # TODO: add other methods to detect private members |
| my $MName = $TypePtr->{"Memb"}{$FieldPos}{"name"}; |
| if($MName=~/priv|abidata|parent_object|impl/i) |
| { # C-styled private data |
| return 0; |
| } |
| if(lc($MName) eq "abi") |
| { # ABI information/reserved field |
| return 0; |
| } |
| if(isReserved($MName)) |
| { # reserved fields |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| sub getVTable_Real($$) |
| { |
| my ($ClassName, $LVer) = @_; |
| if(my $ClassId = $TName_Tid{$LVer}{$ClassName}) |
| { |
| my %Type = getType($ClassId, $LVer); |
| if(defined $Type{"VTable"}) { |
| return %{$Type{"VTable"}}; |
| } |
| } |
| return (); |
| } |
| |
| sub cmpVTables($) |
| { |
| my $ClassName = $_[0]; |
| my $Res = cmpVTables_Real($ClassName, 1); |
| if($Res==-1) { |
| $Res = cmpVTables_Model($ClassName); |
| } |
| return $Res; |
| } |
| |
| sub cmpVTables_Model($) |
| { |
| my $ClassName = $_[0]; |
| foreach my $Symbol (keys(%{$VirtualTable_Model{1}{$ClassName}})) |
| { |
| if(not defined $VirtualTable_Model{2}{$ClassName}{$Symbol}) { |
| return 1; |
| } |
| } |
| return 0; |
| } |
| |
| sub cmpVTables_Real($$) |
| { |
| my ($ClassName, $Strong) = @_; |
| if(defined $Cache{"cmpVTables_Real"}{$Strong}{$ClassName}) { |
| return $Cache{"cmpVTables_Real"}{$Strong}{$ClassName}; |
| } |
| my %VTable_Old = getVTable_Real($ClassName, 1); |
| my %VTable_New = getVTable_Real($ClassName, 2); |
| if(not %VTable_Old or not %VTable_New) |
| { # old ABI dumps |
| return ($Cache{"cmpVTables_Real"}{$Strong}{$ClassName} = -1); |
| } |
| my %Indexes = map {$_=>1} (keys(%VTable_Old), keys(%VTable_New)); |
| foreach my $Offset (sort {$a<=>$b} keys(%Indexes)) |
| { |
| if(not defined $VTable_Old{$Offset}) |
| { # v-table v.1 < v-table v.2 |
| return ($Cache{"cmpVTables_Real"}{$Strong}{$ClassName} = $Strong); |
| } |
| my $Entry1 = $VTable_Old{$Offset}; |
| if(not defined $VTable_New{$Offset}) |
| { # v-table v.1 > v-table v.2 |
| return ($Cache{"cmpVTables_Real"}{$Strong}{$ClassName} = ($Strong or $Entry1!~/__cxa_pure_virtual/)); |
| } |
| my $Entry2 = $VTable_New{$Offset}; |
| |
| $Entry1 = simpleVEntry($Entry1); |
| $Entry2 = simpleVEntry($Entry2); |
| |
| if($Entry1=~/ 0x/ or $Entry2=~/ 0x/) |
| { # NOTE: problem with vtable-dumper |
| next; |
| } |
| |
| if($Entry1 ne $Entry2) |
| { # register as changed |
| if($Entry1=~/::([^:]+)\Z/) |
| { |
| my $M1 = $1; |
| if($Entry2=~/::([^:]+)\Z/) |
| { |
| my $M2 = $1; |
| if($M1 eq $M2) |
| { # overridden |
| next; |
| } |
| } |
| } |
| if($In::ABI{1}{"GccVersion"} ne $In::ABI{2}{"GccVersion"}) |
| { |
| if($Entry1=~/\A\-(0x|\d+)/ and $Entry2=~/\A\-(0x|\d+)/) |
| { |
| # GCC 4.6.1: -0x00000000000000010 |
| # GCC 4.7.0: -16 |
| next; |
| } |
| } |
| return ($Cache{"cmpVTables_Real"}{$Strong}{$ClassName} = 1); |
| } |
| } |
| return ($Cache{"cmpVTables_Real"}{$Strong}{$ClassName} = 0); |
| } |
| |
| sub mergeVTables($) |
| { # merging v-tables without diagnostics |
| my $Level = $_[0]; |
| foreach my $ClassName (keys(%{$VirtualTable{1}})) |
| { |
| my $ClassId = $TName_Tid{1}{$ClassName}; |
| if(isPrivateABI($ClassId, 1)) { |
| next; |
| } |
| |
| if($VTableChanged_M{$ClassName}) |
| { # already registered |
| next; |
| } |
| if(cmpVTables_Real($ClassName, 0)==1) |
| { |
| my @Affected = (keys(%{$ClassMethods{$Level}{1}{$ClassName}})); |
| foreach my $Symbol (@Affected) |
| { |
| %{$CompatProblems{$Level}{$Symbol}{"Virtual_Table_Changed_Unknown"}{$ClassName}}=( |
| "Type_Name"=>$ClassName, |
| "Target"=>$ClassName); |
| } |
| } |
| } |
| } |
| |
| sub mergeBases($) |
| { |
| my $Level = $_[0]; |
| foreach my $ClassName (sort keys(%{$ClassNames{1}})) |
| { # detect added and removed virtual functions |
| my $ClassId = $TName_Tid{1}{$ClassName}; |
| next if(not $ClassId); |
| |
| if(isPrivateABI($ClassId, 1)) { |
| next; |
| } |
| |
| if(defined $VirtualTable{2}{$ClassName}) |
| { |
| foreach my $Symbol (keys(%{$VirtualTable{2}{$ClassName}})) |
| { |
| if($TName_Tid{1}{$ClassName} |
| and not defined $VirtualTable{1}{$ClassName}{$Symbol}) |
| { # added to v-table |
| if(defined $CompSign{1}{$Symbol}) |
| { |
| if($CompSign{1}{$Symbol}{"Virt"}) |
| { # override some method in v.1 |
| next; |
| } |
| } |
| else |
| { |
| if(linkSymbol($Symbol, 1, "+Deps")) { |
| next; |
| } |
| } |
| |
| $AddedInt_Virt{$Level}{$ClassName}{$Symbol} = 1; |
| } |
| } |
| } |
| if(defined $VirtualTable{1}{$ClassName}) |
| { |
| foreach my $Symbol (keys(%{$VirtualTable{1}{$ClassName}})) |
| { |
| if($TName_Tid{2}{$ClassName} |
| and not defined $VirtualTable{2}{$ClassName}{$Symbol}) |
| { # removed from v-table |
| if(defined $CompSign{2}{$Symbol}) |
| { |
| if($CompSign{2}{$Symbol}{"Virt"}) |
| { # override some method in v.2 |
| next; |
| } |
| } |
| else |
| { |
| if(linkSymbol($Symbol, 2, "+Deps")) { |
| next; |
| } |
| } |
| |
| $RemovedInt_Virt{$Level}{$ClassName}{$Symbol} = 1; |
| } |
| } |
| } |
| |
| if($Level eq "Binary") |
| { # Binary-level |
| my %Class_Type = getType($ClassId, 1); |
| foreach my $AddedVFunc (keys(%{$AddedInt_Virt{$Level}{$ClassName}})) |
| { # check replacements, including pure virtual methods |
| my $AddedPos = $VirtualTable{2}{$ClassName}{$AddedVFunc}; |
| foreach my $RemovedVFunc (keys(%{$RemovedInt_Virt{$Level}{$ClassName}})) |
| { |
| my $RemovedPos = $VirtualTable{1}{$ClassName}{$RemovedVFunc}; |
| if($AddedPos==$RemovedPos) |
| { |
| $VirtualReplacement{$AddedVFunc} = $RemovedVFunc; |
| $VirtualReplacement{$RemovedVFunc} = $AddedVFunc; |
| last; # other methods will be reported as "added" or "removed" |
| } |
| } |
| if(my $RemovedVFunc = $VirtualReplacement{$AddedVFunc}) |
| { |
| if(lc($AddedVFunc) eq lc($RemovedVFunc)) |
| { # skip: DomUi => DomUI parameter (Qt 4.2.3 to 4.3.0) |
| next; |
| } |
| |
| my $ProblemType = "Virtual_Replacement"; |
| my @Affected = (); |
| |
| if($CompSign{1}{$RemovedVFunc}{"PureVirt"}) |
| { # pure methods |
| if(not isUsedClass($ClassId, 1, $Level)) |
| { # not a parameter of some exported method |
| next; |
| } |
| $ProblemType = "Pure_Virtual_Replacement"; |
| |
| # affected all methods (both virtual and non-virtual ones) |
| @Affected = (keys(%{$ClassMethods{$Level}{1}{$ClassName}})); |
| } |
| else { |
| @Affected = ($RemovedVFunc); |
| } |
| |
| push(@Affected, keys(%{$OverriddenMethods{1}{$RemovedVFunc}})); |
| |
| $VTableChanged_M{$ClassName} = 1; |
| |
| foreach my $AffectedInt (@Affected) |
| { |
| if($CompSign{1}{$AffectedInt}{"PureVirt"}) |
| { # affected exported methods only |
| next; |
| } |
| if(not symbolFilter($AffectedInt, $CompSign{1}{$AffectedInt}, "Affected", $Level, 1)) { |
| next; |
| } |
| %{$CompatProblems{$Level}{$AffectedInt}{$ProblemType}{$AddedVFunc}}=( |
| "Type_Name"=>$Class_Type{"Name"}, |
| "Target"=>$AddedVFunc, |
| "Old_Value"=>$RemovedVFunc); |
| } |
| } |
| } |
| } |
| } |
| |
| foreach my $ClassName (sort keys(%{$ClassNames{1}})) |
| { |
| my $ClassId_Old = $TName_Tid{1}{$ClassName}; |
| next if(not $ClassId_Old); |
| |
| if(isPrivateABI($ClassId_Old, 1)) { |
| next; |
| } |
| |
| if(not isCreatable($ClassId_Old, 1)) |
| { # skip classes without public constructors (including auto-generated) |
| # example: class has only a private exported or private inline constructor |
| next; |
| } |
| |
| my %Class_Old = getType($ClassId_Old, 1); |
| my $ClassId_New = $TName_Tid{2}{$ClassName}; |
| if(not $ClassId_New) { |
| next; |
| } |
| my %Class_New = getType($ClassId_New, 2); |
| if($Class_New{"Type"}!~/Class|Struct/) |
| { # became typedef |
| if($Level eq "Binary") { |
| next; |
| } |
| if($Level eq "Source") |
| { |
| %Class_New = getPureType($ClassId_New, 2); |
| if($Class_New{"Type"}!~/Class|Struct/) { |
| next; |
| } |
| $ClassId_New = $Class_New{"Tid"}; |
| } |
| } |
| |
| if(not $Class_New{"Size"} or not $Class_Old{"Size"}) |
| { # incomplete info in the ABI dump |
| next; |
| } |
| |
| if($Level eq "Binary" and cmpVTables_Real($ClassName, 1)!=0) |
| { |
| foreach my $Symbol (sort keys(%{$RemovedInt_Virt{$Level}{$ClassName}})) |
| { |
| if($VirtualReplacement{$Symbol}) { |
| next; |
| } |
| |
| if(symbolFilter($Symbol, $CompSign{1}{$Symbol}, "Affected", $Level, 1)) |
| { |
| my $ProblemType = "Removed_Virtual_Method"; |
| if($CompSign{1}{$Symbol}{"PureVirt"}) { |
| $ProblemType = "Removed_Pure_Virtual_Method"; |
| } |
| |
| %{$CompatProblems{$Level}{$Symbol}{$ProblemType}{getSignature($Symbol, 1, "Class|Name|Qual")}}=( |
| "Type_Name"=>$ClassName, |
| "Target"=>$Symbol); |
| } |
| |
| $VTableChanged_M{$ClassName} = 1; |
| foreach my $SubId (getSubClasses($ClassId_Old, 1, 1)) |
| { |
| if(my $SubName = $TypeInfo{1}{$SubId}{"Name"}) { |
| $VTableChanged_M{$SubName} = 1; |
| } |
| } |
| } |
| } |
| |
| if(index($ClassName, ">")!=-1) |
| { # skip affected template instances |
| next; |
| } |
| |
| my @Bases_Old = sort {$Class_Old{"Base"}{$a}{"pos"}<=>$Class_Old{"Base"}{$b}{"pos"}} keys(%{$Class_Old{"Base"}}); |
| my @Bases_New = sort {$Class_New{"Base"}{$a}{"pos"}<=>$Class_New{"Base"}{$b}{"pos"}} keys(%{$Class_New{"Base"}}); |
| |
| my %Tr_Old = map {$TypeInfo{1}{$_}{"Name"} => uncoverTypedefs($TypeInfo{1}{$_}{"Name"}, 1)} @Bases_Old; |
| my %Tr_New = map {$TypeInfo{2}{$_}{"Name"} => uncoverTypedefs($TypeInfo{2}{$_}{"Name"}, 2)} @Bases_New; |
| |
| my ($BNum1, $BNum2) = (1, 1); |
| my %BasePos_Old = map {$Tr_Old{$TypeInfo{1}{$_}{"Name"}} => $BNum1++} @Bases_Old; |
| my %BasePos_New = map {$Tr_New{$TypeInfo{2}{$_}{"Name"}} => $BNum2++} @Bases_New; |
| my %ShortBase_Old = map {getShortClass($_, 1) => 1} @Bases_Old; |
| my %ShortBase_New = map {getShortClass($_, 2) => 1} @Bases_New; |
| my $Shift_Old = getShift($ClassId_Old, 1); |
| my $Shift_New = getShift($ClassId_New, 2); |
| my %BaseId_New = map {$Tr_New{$TypeInfo{2}{$_}{"Name"}} => $_} @Bases_New; |
| my @StableBases_Old = (); |
| foreach my $BaseId (@Bases_Old) |
| { |
| my $BaseName = $TypeInfo{1}{$BaseId}{"Name"}; |
| if($BasePos_New{$Tr_Old{$BaseName}}) { |
| push(@StableBases_Old, $BaseId); |
| } |
| elsif(not $ShortBase_New{$Tr_Old{$BaseName}} |
| and not $ShortBase_New{getShortClass($BaseId, 1)}) |
| { # removed base |
| # excluding namespace::SomeClass to SomeClass renaming |
| my $ProblemKind = "Removed_Base_Class"; |
| if($Level eq "Binary") |
| { # Binary-level |
| if($Shift_Old ne $Shift_New) |
| { # affected fields |
| if(havePubFields(\%Class_Old)) { |
| $ProblemKind .= "_And_Shift"; |
| } |
| elsif($Class_Old{"Size"} ne $Class_New{"Size"}) { |
| $ProblemKind .= "_And_Size"; |
| } |
| } |
| if(keys(%{$VirtualTable_Model{1}{$BaseName}}) |
| and cmpVTables($ClassName)==1) |
| { # affected v-table |
| $ProblemKind .= "_And_VTable"; |
| $VTableChanged_M{$ClassName} = 1; |
| } |
| } |
| my @Affected = keys(%{$ClassMethods{$Level}{1}{$ClassName}}); |
| foreach my $SubId (getSubClasses($ClassId_Old, 1, 1)) |
| { |
| if(my $SubName = $TypeInfo{1}{$SubId}{"Name"}) |
| { |
| push(@Affected, keys(%{$ClassMethods{$Level}{1}{$SubName}})); |
| if($ProblemKind=~/VTable/) { |
| $VTableChanged_M{$SubName} = 1; |
| } |
| } |
| } |
| foreach my $Interface (@Affected) |
| { |
| if(symbolFilter($Interface, $CompSign{1}{$Interface}, "Affected", $Level, 1)) |
| { |
| %{$CompatProblems{$Level}{$Interface}{$ProblemKind}{"this"}}=( |
| "Type_Name"=>$ClassName, |
| "Target"=>$BaseName, |
| "Old_Size"=>$Class_Old{"Size"}*$BYTE, |
| "New_Size"=>$Class_New{"Size"}*$BYTE, |
| "Shift"=>abs($Shift_New-$Shift_Old)); |
| } |
| } |
| } |
| } |
| my @StableBases_New = (); |
| foreach my $BaseId (@Bases_New) |
| { |
| my $BaseName = $TypeInfo{2}{$BaseId}{"Name"}; |
| if($BasePos_Old{$Tr_New{$BaseName}}) { |
| push(@StableBases_New, $BaseId); |
| } |
| elsif(not $ShortBase_Old{$Tr_New{$BaseName}} |
| and not $ShortBase_Old{getShortClass($BaseId, 2)}) |
| { # added base |
| # excluding namespace::SomeClass to SomeClass renaming |
| my $ProblemKind = "Added_Base_Class"; |
| if($Level eq "Binary") |
| { # Binary-level |
| if($Shift_Old ne $Shift_New) |
| { # affected fields |
| if(havePubFields(\%Class_Old)) { |
| $ProblemKind .= "_And_Shift"; |
| } |
| elsif($Class_Old{"Size"} ne $Class_New{"Size"}) { |
| $ProblemKind .= "_And_Size"; |
| } |
| } |
| if(keys(%{$VirtualTable_Model{2}{$BaseName}}) |
| and cmpVTables($ClassName)==1) |
| { # affected v-table |
| $ProblemKind .= "_And_VTable"; |
| $VTableChanged_M{$ClassName} = 1; |
| } |
| } |
| my @Affected = keys(%{$ClassMethods{$Level}{1}{$ClassName}}); |
| foreach my $SubId (getSubClasses($ClassId_Old, 1, 1)) |
| { |
| if(my $SubName = $TypeInfo{1}{$SubId}{"Name"}) |
| { |
| push(@Affected, keys(%{$ClassMethods{$Level}{1}{$SubName}})); |
| if($ProblemKind=~/VTable/) { |
| $VTableChanged_M{$SubName} = 1; |
| } |
| } |
| } |
| foreach my $Interface (@Affected) |
| { |
| if(symbolFilter($Interface, $CompSign{1}{$Interface}, "Affected", $Level, 1)) |
| { |
| %{$CompatProblems{$Level}{$Interface}{$ProblemKind}{"this"}}=( |
| "Type_Name"=>$ClassName, |
| "Target"=>$BaseName, |
| "Old_Size"=>$Class_Old{"Size"}*$BYTE, |
| "New_Size"=>$Class_New{"Size"}*$BYTE, |
| "Shift"=>abs($Shift_New-$Shift_Old)); |
| } |
| } |
| } |
| } |
| if($Level eq "Binary") |
| { # Binary-level |
| ($BNum1, $BNum2) = (1, 1); |
| my %BaseRelPos_Old = map {$Tr_Old{$TypeInfo{1}{$_}{"Name"}} => $BNum1++} @StableBases_Old; |
| my %BaseRelPos_New = map {$Tr_New{$TypeInfo{2}{$_}{"Name"}} => $BNum2++} @StableBases_New; |
| foreach my $BaseId (@Bases_Old) |
| { |
| my $BaseName = $TypeInfo{1}{$BaseId}{"Name"}; |
| if(my $NewPos = $BaseRelPos_New{$Tr_Old{$BaseName}}) |
| { |
| my $BaseNewId = $BaseId_New{$Tr_Old{$BaseName}}; |
| my $OldPos = $BaseRelPos_Old{$Tr_Old{$BaseName}}; |
| if($NewPos!=$OldPos) |
| { # changed position of the base class |
| foreach my $Interface (keys(%{$ClassMethods{$Level}{1}{$ClassName}})) |
| { |
| if(symbolFilter($Interface, $CompSign{1}{$Interface}, "Affected", $Level, 1)) |
| { |
| %{$CompatProblems{$Level}{$Interface}{"Base_Class_Position"}{"this"}}=( |
| "Type_Name"=>$ClassName, |
| "Target"=>$BaseName, |
| "Old_Value"=>$OldPos-1, |
| "New_Value"=>$NewPos-1); |
| } |
| } |
| } |
| if($Class_Old{"Base"}{$BaseId}{"virtual"} |
| and not $Class_New{"Base"}{$BaseNewId}{"virtual"}) |
| { # became non-virtual base |
| foreach my $Interface (keys(%{$ClassMethods{$Level}{1}{$ClassName}})) |
| { |
| if(symbolFilter($Interface, $CompSign{1}{$Interface}, "Affected", $Level, 1)) |
| { |
| %{$CompatProblems{$Level}{$Interface}{"Base_Class_Became_Non_Virtually_Inherited"}{"this->".$BaseName}}=( |
| "Type_Name"=>$ClassName, |
| "Target"=>$BaseName ); |
| } |
| } |
| } |
| elsif(not $Class_Old{"Base"}{$BaseId}{"virtual"} |
| and $Class_New{"Base"}{$BaseNewId}{"virtual"}) |
| { # became virtual base |
| foreach my $Interface (keys(%{$ClassMethods{$Level}{1}{$ClassName}})) |
| { |
| if(not symbolFilter($Interface, $CompSign{1}{$Interface}, "Affected", $Level, 1)) { |
| next; |
| } |
| %{$CompatProblems{$Level}{$Interface}{"Base_Class_Became_Virtually_Inherited"}{"this->".$BaseName}}=( |
| "Type_Name"=>$ClassName, |
| "Target"=>$BaseName ); |
| } |
| } |
| } |
| } |
| # detect size changes in base classes |
| if($Shift_Old!=$Shift_New) |
| { # size of allocable class |
| foreach my $BaseId (@StableBases_Old) |
| { # search for changed base |
| my %BaseType = getType($BaseId, 1); |
| my $Size_Old = $TypeInfo{1}{$BaseId}{"Size"}; |
| my $Size_New = $TypeInfo{2}{$BaseId_New{$Tr_Old{$BaseType{"Name"}}}}{"Size"}; |
| if($Size_Old ne $Size_New |
| and $Size_Old and $Size_New) |
| { |
| my $ProblemType = undef; |
| if(isCopyingClass($BaseId, 1)) { |
| $ProblemType = "Size_Of_Copying_Class"; |
| } |
| elsif($AllocableClass{1}{$BaseType{"Name"}}) |
| { |
| if($Size_New>$Size_Old) |
| { # increased size |
| $ProblemType = "Size_Of_Allocable_Class_Increased"; |
| } |
| else |
| { # decreased size |
| $ProblemType = "Size_Of_Allocable_Class_Decreased"; |
| if(not havePubFields(\%Class_Old)) |
| { # affected class has no public members |
| next; |
| } |
| } |
| } |
| next if(not $ProblemType); |
| foreach my $Interface (keys(%{$ClassMethods{$Level}{1}{$ClassName}})) |
| { # base class size changes affecting current class |
| if(not symbolFilter($Interface, $CompSign{1}{$Interface}, "Affected", $Level, 1)) { |
| next; |
| } |
| %{$CompatProblems{$Level}{$Interface}{$ProblemType}{"this->".$BaseType{"Name"}}}=( |
| "Type_Name"=>$BaseType{"Name"}, |
| "Target"=>$BaseType{"Name"}, |
| "Old_Size"=>$Size_Old*$BYTE, |
| "New_Size"=>$Size_New*$BYTE ); |
| } |
| } |
| } |
| } |
| |
| if(defined $VirtualTable_Model{1}{$ClassName} |
| and cmpVTables_Real($ClassName, 1)==1 |
| and my @VFunctions = keys(%{$VirtualTable_Model{1}{$ClassName}})) |
| { # compare virtual tables size in base classes |
| my $VShift_Old = getVShift($ClassId_Old, 1); |
| my $VShift_New = getVShift($ClassId_New, 2); |
| if($VShift_Old ne $VShift_New) |
| { # changes in the base class or changes in the list of base classes |
| my @AllBases_Old = getBaseClasses($ClassId_Old, 1, 1); |
| my @AllBases_New = getBaseClasses($ClassId_New, 2, 1); |
| ($BNum1, $BNum2) = (1, 1); |
| my %StableBase = map {$Tr_New{$TypeInfo{2}{$_}{"Name"}} => $_} @AllBases_New; |
| foreach my $BaseId (@AllBases_Old) |
| { |
| my %BaseType = getType($BaseId, 1); |
| if(not $StableBase{$Tr_Old{$BaseType{"Name"}}}) |
| { # lost base |
| next; |
| } |
| my $VSize_Old = getVTable_Size($BaseType{"Name"}, 1); |
| my $VSize_New = getVTable_Size($BaseType{"Name"}, 2); |
| if($VSize_Old!=$VSize_New) |
| { |
| foreach my $Symbol (@VFunctions) |
| { # TODO: affected non-virtual methods? |
| if(not defined $VirtualTable_Model{2}{$ClassName}{$Symbol}) |
| { # Removed_Virtual_Method, will be registered in mergeVirtualTables() |
| next; |
| } |
| |
| if($VirtualTable_Model{2}{$ClassName}{$Symbol}-$VirtualTable_Model{1}{$ClassName}{$Symbol}==0) |
| { # skip interfaces that have not changed the absolute virtual position |
| next; |
| } |
| |
| if(not symbolFilter($Symbol, $CompSign{1}{$Symbol}, "Affected", $Level, 1)) { |
| next; |
| } |
| |
| $VTableChanged_M{$BaseType{"Name"}} = 1; |
| $VTableChanged_M{$ClassName} = 1; |
| |
| foreach my $VirtFunc (keys(%{$AddedInt_Virt{$Level}{$BaseType{"Name"}}})) |
| { # the reason of the layout change: added virtual functions |
| next if($VirtualReplacement{$VirtFunc}); |
| my $ProblemType = "Added_Virtual_Method"; |
| if($CompSign{2}{$VirtFunc}{"PureVirt"}) { |
| $ProblemType = "Added_Pure_Virtual_Method"; |
| } |
| %{$CompatProblems{$Level}{$Symbol}{$ProblemType}{getSignature($VirtFunc, 2, "Class|Name|Qual")}}=( |
| "Type_Name"=>$BaseType{"Name"}, |
| "Target"=>$VirtFunc ); |
| } |
| |
| foreach my $VirtFunc (keys(%{$RemovedInt_Virt{$Level}{$BaseType{"Name"}}})) |
| { # the reason of the layout change: removed virtual functions |
| next if($VirtualReplacement{$VirtFunc}); |
| my $ProblemType = "Removed_Virtual_Method"; |
| if($CompSign{1}{$VirtFunc}{"PureVirt"}) { |
| $ProblemType = "Removed_Pure_Virtual_Method"; |
| } |
| %{$CompatProblems{$Level}{$Symbol}{$ProblemType}{getSignature($VirtFunc, 1, "Class|Name|Qual")}}=( |
| "Type_Name"=>$BaseType{"Name"}, |
| "Target"=>$VirtFunc ); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| sub isCreatable($$) |
| { |
| my ($ClassId, $LVer) = @_; |
| if($AllocableClass{$LVer}{$TypeInfo{$LVer}{$ClassId}{"Name"}} |
| or isCopyingClass($ClassId, $LVer)) { |
| return 1; |
| } |
| if(keys(%{$In::ABI{$LVer}{"Class_SubClasses"}{$ClassId}})) |
| { # Fix for incomplete data: if this class has |
| # a base class then it should also has a constructor |
| return 1; |
| } |
| if($ReturnedClass{$LVer}{$ClassId}) |
| { # returned by some method of this class |
| # or any other class |
| return 1; |
| } |
| return 0; |
| } |
| |
| sub isUsedClass($$$) |
| { |
| my ($ClassId, $LVer, $Level) = @_; |
| if(keys(%{$ParamClass{$LVer}{$ClassId}})) |
| { # parameter of some exported method |
| return 1; |
| } |
| my $CName = $TypeInfo{$LVer}{$ClassId}{"Name"}; |
| if(keys(%{$ClassMethods{$Level}{$LVer}{$CName}})) |
| { # method from target class |
| return 1; |
| } |
| return 0; |
| } |
| |
| sub mergeVirtualTables($$) |
| { # check for changes in the virtual table |
| my ($Interface, $Level) = @_; |
| # affected methods: |
| # - virtual |
| # - pure-virtual |
| # - non-virtual |
| if($CompSign{1}{$Interface}{"Data"}) |
| { # global data is not affected |
| return; |
| } |
| my $Class_Id = $CompSign{1}{$Interface}{"Class"}; |
| if(not $Class_Id) { |
| return; |
| } |
| my $CName = $TypeInfo{1}{$Class_Id}{"Name"}; |
| if(cmpVTables_Real($CName, 1)==0) |
| { # no changes |
| return; |
| } |
| $CheckedTypes{$Level}{$CName} = 1; |
| if($Level eq "Binary") |
| { # Binary-level |
| if($CompSign{1}{$Interface}{"PureVirt"} |
| and not isUsedClass($Class_Id, 1, $Level)) |
| { # pure virtuals should not be affected |
| # if there are no exported methods using this class |
| return; |
| } |
| } |
| foreach my $Func (keys(%{$VirtualTable{1}{$CName}})) |
| { |
| if(defined $VirtualTable{2}{$CName}{$Func} |
| and defined $CompSign{2}{$Func}) |
| { |
| if(not $CompSign{1}{$Func}{"PureVirt"} |
| and $CompSign{2}{$Func}{"PureVirt"}) |
| { # became pure virtual |
| %{$CompatProblems{$Level}{$Interface}{"Virtual_Method_Became_Pure"}{$Func}}=( |
| "Type_Name"=>$CName, |
| "Target"=>$Func ); |
| $VTableChanged_M{$CName} = 1; |
| } |
| elsif($CompSign{1}{$Func}{"PureVirt"} |
| and not $CompSign{2}{$Func}{"PureVirt"}) |
| { # became non-pure virtual |
| %{$CompatProblems{$Level}{$Interface}{"Virtual_Method_Became_Non_Pure"}{$Func}}=( |
| "Type_Name"=>$CName, |
| "Target"=>$Func ); |
| $VTableChanged_M{$CName} = 1; |
| } |
| } |
| } |
| if($Level eq "Binary") |
| { # Binary-level |
| # check virtual table structure |
| foreach my $AddedVFunc (keys(%{$AddedInt_Virt{$Level}{$CName}})) |
| { |
| next if($Interface eq $AddedVFunc); |
| next if($VirtualReplacement{$AddedVFunc}); |
| my $VPos_Added = $VirtualTable{2}{$CName}{$AddedVFunc}; |
| if($CompSign{2}{$AddedVFunc}{"PureVirt"}) |
| { # pure virtual methods affect all others (virtual and non-virtual) |
| %{$CompatProblems{$Level}{$Interface}{"Added_Pure_Virtual_Method"}{$AddedVFunc}}=( |
| "Type_Name"=>$CName, |
| "Target"=>$AddedVFunc ); |
| $VTableChanged_M{$CName} = 1; |
| } |
| elsif(not defined $VirtualTable{1}{$CName} |
| or $VPos_Added>keys(%{$VirtualTable{1}{$CName}})) |
| { # added virtual function at the end of v-table |
| if(not keys(%{$VirtualTable_Model{1}{$CName}})) |
| { # became polymorphous class, added v-table pointer |
| %{$CompatProblems{$Level}{$Interface}{"Added_First_Virtual_Method"}{$AddedVFunc}}=( |
| "Type_Name"=>$CName, |
| "Target"=>$AddedVFunc ); |
| $VTableChanged_M{$CName} = 1; |
| } |
| else |
| { |
| my $VSize_Old = getVTable_Size($CName, 1); |
| my $VSize_New = getVTable_Size($CName, 2); |
| next if($VSize_Old==$VSize_New); # exception: register as removed and added virtual method |
| if(isCopyingClass($Class_Id, 1)) |
| { # class has no constructors and v-table will be copied by applications, this may affect all methods |
| my $ProblemType = "Added_Virtual_Method"; |
| if(isLeafClass($Class_Id, 1)) { |
| $ProblemType = "Added_Virtual_Method_At_End_Of_Leaf_Copying_Class"; |
| } |
| %{$CompatProblems{$Level}{$Interface}{$ProblemType}{getSignature($AddedVFunc, 2, "Class|Name|Qual")}}=( |
| "Type_Name"=>$CName, |
| "Target"=>$AddedVFunc ); |
| $VTableChanged_M{$CName} = 1; |
| } |
| else |
| { |
| my $ProblemType = "Added_Virtual_Method"; |
| if(isLeafClass($Class_Id, 1)) { |
| $ProblemType = "Added_Virtual_Method_At_End_Of_Leaf_Allocable_Class"; |
| } |
| %{$CompatProblems{$Level}{$Interface}{$ProblemType}{getSignature($AddedVFunc, 2, "Class|Name|Qual")}}=( |
| "Type_Name"=>$CName, |
| "Target"=>$AddedVFunc ); |
| $VTableChanged_M{$CName} = 1; |
| } |
| } |
| } |
| elsif($CompSign{1}{$Interface}{"Virt"} |
| or $CompSign{1}{$Interface}{"PureVirt"}) |
| { |
| if(defined $VirtualTable{1}{$CName} |
| and defined $VirtualTable{2}{$CName}) |
| { |
| my $VPos_Old = $VirtualTable{1}{$CName}{$Interface}; |
| my $VPos_New = $VirtualTable{2}{$CName}{$Interface}; |
| |
| if($VPos_Added<=$VPos_Old and $VPos_Old!=$VPos_New) |
| { |
| my @Affected = ($Interface, keys(%{$OverriddenMethods{1}{$Interface}})); |
| foreach my $ASymbol (@Affected) |
| { |
| if(not $CompSign{1}{$ASymbol}{"PureVirt"}) |
| { |
| if(not symbolFilter($ASymbol, $CompSign{1}{$ASymbol}, "Affected", $Level, 1)) { |
| next; |
| } |
| } |
| %{$CompatProblems{$Level}{$ASymbol}{"Added_Virtual_Method"}{getSignature($AddedVFunc, 2, "Class|Name|Qual")}}=( |
| "Type_Name"=>$CName, |
| "Target"=>$AddedVFunc ); |
| $VTableChanged_M{$TypeInfo{1}{$CompSign{1}{$ASymbol}{"Class"}}{"Name"}} = 1; |
| } |
| } |
| } |
| } |
| else { |
| # safe |
| } |
| } |
| |
| foreach my $RemovedVFunc (sort keys(%{$RemovedInt_Virt{$Level}{$CName}})) |
| { |
| next if($VirtualReplacement{$RemovedVFunc}); |
| if($RemovedVFunc eq $Interface |
| and $CompSign{1}{$RemovedVFunc}{"PureVirt"}) |
| { # This case is for removed virtual methods |
| # implemented in both versions of a library |
| next; |
| } |
| |
| if(not keys(%{$VirtualTable_Model{2}{$CName}})) |
| { # became non-polymorphous class, removed v-table pointer |
| %{$CompatProblems{$Level}{$Interface}{"Removed_Last_Virtual_Method"}{getSignature($RemovedVFunc, 1, "Class|Name|Qual")}}=( |
| "Type_Name"=>$CName, |
| "Target"=>$RemovedVFunc); |
| $VTableChanged_M{$CName} = 1; |
| } |
| elsif($CompSign{1}{$Interface}{"Virt"} |
| or $CompSign{1}{$Interface}{"PureVirt"}) |
| { |
| if(defined $VirtualTable{1}{$CName} |
| and defined $VirtualTable{2}{$CName}) |
| { |
| if(not defined $VirtualTable{1}{$CName}{$Interface}) { |
| next; |
| } |
| my $VPos_New = -1; |
| if(defined $VirtualTable{2}{$CName}{$Interface}) |
| { |
| $VPos_New = $VirtualTable{2}{$CName}{$Interface}; |
| } |
| else |
| { |
| if($Interface ne $RemovedVFunc) { |
| next; |
| } |
| } |
| my $VPos_Removed = $VirtualTable{1}{$CName}{$RemovedVFunc}; |
| my $VPos_Old = $VirtualTable{1}{$CName}{$Interface}; |
| if($VPos_Removed<=$VPos_Old and $VPos_Old!=$VPos_New) |
| { |
| my @Affected = ($Interface, keys(%{$OverriddenMethods{1}{$Interface}})); |
| foreach my $ASymbol (@Affected) |
| { |
| if(not $CompSign{1}{$ASymbol}{"PureVirt"}) |
| { |
| if(not symbolFilter($ASymbol, $CompSign{1}{$ASymbol}, "Affected", $Level, 1)) { |
| next; |
| } |
| } |
| my $ProblemType = "Removed_Virtual_Method"; |
| if($CompSign{1}{$RemovedVFunc}{"PureVirt"}) { |
| $ProblemType = "Removed_Pure_Virtual_Method"; |
| } |
| |
| %{$CompatProblems{$Level}{$ASymbol}{$ProblemType}{getSignature($RemovedVFunc, 1, "Class|Name|Qual")}}=( |
| "Type_Name"=>$CName, |
| "Target"=>$RemovedVFunc); |
| $VTableChanged_M{$TypeInfo{1}{$CompSign{1}{$ASymbol}{"Class"}}{"Name"}} = 1; |
| } |
| } |
| } |
| } |
| } |
| } |
| else |
| { # Source-level |
| foreach my $AddedVFunc (keys(%{$AddedInt_Virt{$Level}{$CName}})) |
| { |
| next if($Interface eq $AddedVFunc); |
| if($CompSign{2}{$AddedVFunc}{"PureVirt"}) |
| { |
| %{$CompatProblems{$Level}{$Interface}{"Added_Pure_Virtual_Method"}{$AddedVFunc}}=( |
| "Type_Name"=>$CName, |
| "Target"=>$AddedVFunc ); |
| } |
| } |
| foreach my $RemovedVFunc (keys(%{$RemovedInt_Virt{$Level}{$CName}})) |
| { |
| if($CompSign{1}{$RemovedVFunc}{"PureVirt"}) |
| { |
| %{$CompatProblems{$Level}{$Interface}{"Removed_Pure_Virtual_Method"}{$RemovedVFunc}}=( |
| "Type_Name"=>$CName, |
| "Target"=>$RemovedVFunc ); |
| } |
| } |
| } |
| } |
| |
| sub findMemPairByName($$) |
| { |
| my ($Mem, $PairType) = @_; |
| $Mem=~s/\A[_]+|[_]+\Z//g; |
| foreach my $Pair (sort {$a<=>$b} keys(%{$PairType->{"Memb"}})) |
| { |
| if(defined $PairType->{"Memb"}{$Pair}) |
| { |
| my $Name = $PairType->{"Memb"}{$Pair}{"name"}; |
| |
| if(index($Name, "_")!=-1) { |
| $Name=~s/\A[_]+|[_]+\Z//g; |
| } |
| |
| if($Name eq $Mem) { |
| return $Pair; |
| } |
| } |
| } |
| return "lost"; |
| } |
| |
| sub findMemPairByVal($$) |
| { |
| my ($Member_Value, $Pair_Type) = @_; |
| foreach my $MemberPair_Pos (sort {$a<=>$b} keys(%{$Pair_Type->{"Memb"}})) |
| { |
| if(defined $Pair_Type->{"Memb"}{$MemberPair_Pos} |
| and $Pair_Type->{"Memb"}{$MemberPair_Pos}{"value"} eq $Member_Value) { |
| return $MemberPair_Pos; |
| } |
| } |
| return "lost"; |
| } |
| |
| sub isRenamed($$$$$) |
| { |
| my ($MemPos, $Type1, $V1, $Type2, $V2) = @_; |
| my $Member_Name = $Type1->{"Memb"}{$MemPos}{"name"}; |
| my $MemberType_Id = $Type1->{"Memb"}{$MemPos}{"type"}; |
| my %MemberType_Pure = getPureType($MemberType_Id, $V1); |
| if(not defined $Type2->{"Memb"}{$MemPos}) { |
| return ""; |
| } |
| my $PairType_Id = $Type2->{"Memb"}{$MemPos}{"type"}; |
| my %PairType_Pure = getPureType($PairType_Id, $V2); |
| |
| my $Pair_Name = $Type2->{"Memb"}{$MemPos}{"name"}; |
| my $MemberPair_Pos_Rev = ($Member_Name eq $Pair_Name)?$MemPos:findMemPairByName($Pair_Name, $Type1); |
| if($MemberPair_Pos_Rev eq "lost") |
| { |
| if($MemberType_Pure{"Name"} eq $PairType_Pure{"Name"}) |
| { # base type match |
| return $Pair_Name; |
| } |
| if($TypeInfo{$V1}{$MemberType_Id}{"Name"} eq $TypeInfo{$V2}{$PairType_Id}{"Name"}) |
| { # exact type match |
| return $Pair_Name; |
| } |
| if($MemberType_Pure{"Size"} eq $PairType_Pure{"Size"}) |
| { # size match |
| return $Pair_Name; |
| } |
| if(isReserved($Pair_Name)) |
| { # reserved fields |
| return $Pair_Name; |
| } |
| } |
| return ""; |
| } |
| |
| sub isLastElem($$) |
| { |
| my ($Pos, $TypeRef) = @_; |
| my $Name = $TypeRef->{"Memb"}{$Pos}{"name"}; |
| if($Name=~/last|count|max|total|num/i) |
| { # GST_LEVEL_COUNT, GST_RTSP_ELAST |
| return 1; |
| } |
| elsif($Name=~/END|NLIMITS\Z/) |
| { # __RLIMIT_NLIMITS |
| return 1; |
| } |
| elsif($Name=~/\AN[A-Z](.+)[a-z]+s\Z/ |
| and $Pos+1==keys(%{$TypeRef->{"Memb"}})) |
| { # NImageFormats, NColorRoles |
| return 1; |
| } |
| return 0; |
| } |
| |
| sub nonComparable($$) |
| { |
| my ($T1, $T2) = @_; |
| |
| my $N1 = $T1->{"Name"}; |
| my $N2 = $T2->{"Name"}; |
| |
| $N1=~s/\A(struct|union|enum) //; |
| $N2=~s/\A(struct|union|enum) //; |
| |
| if($N1 ne $N2 and $N2!~/\A\Q$N1\E_v\d+\Z/ |
| and not isAnon($N1) |
| and not isAnon($N2)) |
| { # different names |
| # NOTE: compare versioned types (type vs type_v30) |
| if($T1->{"Type"} ne "Pointer" |
| or $T2->{"Type"} ne "Pointer") |
| { # compare base types |
| return 1; |
| } |
| |
| if($N1!~/\Avoid\s*\*/ |
| and $N2=~/\Avoid\s*\*/) |
| { |
| return 1; |
| } |
| } |
| elsif($T1->{"Type"} ne $T2->{"Type"}) |
| { # different types |
| if($T1->{"Type"} eq "Class" |
| and $T2->{"Type"} eq "Struct") |
| { # "class" to "struct" |
| return 0; |
| } |
| elsif($T2->{"Type"} eq "Class" |
| and $T1->{"Type"} eq "Struct") |
| { # "struct" to "class" |
| return 0; |
| } |
| else |
| { # "class" to "enum" |
| # "union" to "class" |
| # ... |
| return 1; |
| } |
| } |
| return 0; |
| } |
| |
| sub mergeTypes($$$) |
| { |
| my ($Type1_Id, $Type2_Id, $Level) = @_; |
| return {} if(not $Type1_Id or not $Type2_Id); |
| |
| if(defined $Cache{"mergeTypes"}{$Level}{$Type1_Id}{$Type2_Id}) |
| { # already merged |
| return $Cache{"mergeTypes"}{$Level}{$Type1_Id}{$Type2_Id}; |
| } |
| |
| my %Type1 = getType($Type1_Id, 1); |
| my %Type2 = getType($Type2_Id, 2); |
| if(not $Type1{"Name"} or not $Type2{"Name"}) { |
| return {}; |
| } |
| |
| my %Type1_Pure = getPureType($Type1_Id, 1); |
| my %Type2_Pure = getPureType($Type2_Id, 2); |
| |
| if(defined $UsedDump{1}{"DWARF"}) |
| { |
| if($Type1_Pure{"Name"} eq "__unknown__" |
| or $Type2_Pure{"Name"} eq "__unknown__") |
| { # Error ABI dump |
| return ($Cache{"mergeTypes"}{$Level}{$Type1_Id}{$Type2_Id} = {}); |
| } |
| } |
| |
| if(isPrivateABI($Type1_Id, 1)) { |
| return {}; |
| } |
| |
| if($Type1{"Type"}=~/Intrinsic|Class|Struct|Union|Enum|Ptr|Typedef/) { |
| $CheckedTypes{$Level}{$Type1{"Name"}} = 1; |
| } |
| |
| if($Type1_Pure{"Type"}=~/Intrinsic|Class|Struct|Union|Enum|Ptr|Typedef/) { |
| $CheckedTypes{$Level}{$Type1_Pure{"Name"}} = 1; |
| } |
| |
| my %SubProblems = (); |
| |
| if($Type1_Pure{"Name"} eq $Type2_Pure{"Name"}) |
| { |
| if($Type1_Pure{"Type"}=~/Struct|Union/ |
| and $Type2_Pure{"Type"}=~/Struct|Union/) |
| { |
| if(isOpaque(\%Type2_Pure) and not isOpaque(\%Type1_Pure)) |
| { |
| if(not defined $UsedDump{1}{"DWARF"} |
| and not defined $UsedDump{2}{"DWARF"}) |
| { |
| %{$SubProblems{"Type_Became_Opaque"}{$Type1_Pure{"Name"}}}=( |
| "Target"=>$Type1_Pure{"Name"}, |
| "Type_Name"=>$Type1_Pure{"Name"} ); |
| } |
| |
| return ($Cache{"mergeTypes"}{$Level}{$Type1_Id}{$Type2_Id} = \%SubProblems); |
| } |
| } |
| } |
| |
| if($Type1_Pure{"Size"} eq "" |
| or $Type2_Pure{"Size"} eq "") |
| { # NOTE: size of struct may be 0 bytes |
| if($Type1_Pure{"Type"}=~/Class|Struct|Union/) |
| { # including a case when "class Class { ... };" changed to "class Class;" |
| if(not defined $Type1_Pure{"Memb"} or not defined $Type2_Pure{"Memb"} |
| or index($Type1_Pure{"Name"}, "<")==-1 or index($Type2_Pure{"Name"}, "<")==-1) |
| { # NOTE: template instances have no size |
| return {}; |
| } |
| } |
| } |
| |
| if(isRecurType($Type1_Pure{"Tid"}, $Type2_Pure{"Tid"}, \@RecurTypes)) |
| { # skip recursive declarations |
| return {}; |
| } |
| return {} if(not $Type1_Pure{"Name"} or not $Type2_Pure{"Name"}); |
| return {} if($In::Desc{1}{"SkipTypes"}{$Type1_Pure{"Name"}}); |
| return {} if($In::Desc{1}{"SkipTypes"}{$Type1{"Name"}}); |
| |
| if($Type1_Pure{"Type"}=~/Class|Struct|Union|Enum|Typedef/ |
| and not isTargetType($Type1_Pure{"Tid"}, 1)) { |
| return {}; |
| } |
| |
| my %Typedef_1 = goToFirst($Type1{"Tid"}, 1, "Typedef"); |
| my %Typedef_2 = goToFirst($Type2{"Tid"}, 2, "Typedef"); |
| |
| if(%Typedef_1 and %Typedef_2 |
| and $Typedef_1{"Type"} eq "Typedef" and $Typedef_2{"Type"} eq "Typedef" |
| and $Typedef_1{"Name"} eq $Typedef_2{"Name"}) |
| { |
| my %Base_1 = getOneStepBaseType($Typedef_1{"Tid"}, 1); |
| my %Base_2 = getOneStepBaseType($Typedef_2{"Tid"}, 2); |
| if($Base_1{"Name"} ne $Base_2{"Name"}) |
| { |
| if($In::ABI{1}{"GccVersion"} ne $In::ABI{2}{"GccVersion"} |
| or $In::Opt{"SkipTypedefUncover"}) |
| { # different GCC versions or different dumps |
| $Base_1{"Name"} = uncoverTypedefs($Base_1{"Name"}, 1); |
| $Base_2{"Name"} = uncoverTypedefs($Base_2{"Name"}, 2); |
| # std::__va_list and __va_list |
| $Base_1{"Name"}=~s/\A(\w+::)+//; |
| $Base_2{"Name"}=~s/\A(\w+::)+//; |
| $Base_1{"Name"} = formatName($Base_1{"Name"}, "T"); |
| $Base_2{"Name"} = formatName($Base_2{"Name"}, "T"); |
| } |
| } |
| if($Base_1{"Name"}!~/anon\-/ and $Base_2{"Name"}!~/anon\-/ |
| and $Base_1{"Name"} ne $Base_2{"Name"}) |
| { |
| if($Level eq "Binary" |
| and $Type1{"Size"} and $Type2{"Size"} |
| and $Type1{"Size"} ne $Type2{"Size"}) |
| { |
| %{$SubProblems{"DataType_Size"}{$Typedef_1{"Name"}}}=( |
| "Target"=>$Typedef_1{"Name"}, |
| "Type_Name"=>$Typedef_1{"Name"}, |
| "Old_Size"=>$Type1{"Size"}*$BYTE, |
| "New_Size"=>$Type2{"Size"}*$BYTE ); |
| } |
| my %Base1_Pure = getPureType($Base_1{"Tid"}, 1); |
| my %Base2_Pure = getPureType($Base_2{"Tid"}, 2); |
| |
| if(defined $UsedDump{1}{"DWARF"}) |
| { |
| if($Base1_Pure{"Name"}=~/\b__unknown__\b/ |
| or $Base2_Pure{"Name"}=~/\b__unknown__\b/) |
| { # Error ABI dump |
| return ($Cache{"mergeTypes"}{$Level}{$Type1_Id}{$Type2_Id} = {}); |
| } |
| } |
| |
| if(tNameLock($Base_1{"Tid"}, $Base_2{"Tid"})) |
| { |
| if(diffTypes($Base1_Pure{"Tid"}, $Base2_Pure{"Tid"}, $Level)) |
| { |
| %{$SubProblems{"Typedef_BaseType_Format"}{$Typedef_1{"Name"}}}=( |
| "Target"=>$Typedef_1{"Name"}, |
| "Type_Name"=>$Typedef_1{"Name"}, |
| "Old_Value"=>$Base_1{"Name"}, |
| "New_Value"=>$Base_2{"Name"} ); |
| } |
| else |
| { |
| %{$SubProblems{"Typedef_BaseType"}{$Typedef_1{"Name"}}}=( |
| "Target"=>$Typedef_1{"Name"}, |
| "Type_Name"=>$Typedef_1{"Name"}, |
| "Old_Value"=>$Base_1{"Name"}, |
| "New_Value"=>$Base_2{"Name"} ); |
| } |
| } |
| } |
| } |
| if(nonComparable(\%Type1_Pure, \%Type2_Pure)) |
| { # different types (reported in detectTypeChange(...)) |
| my $TT1 = $Type1_Pure{"Type"}; |
| my $TT2 = $Type2_Pure{"Type"}; |
| |
| if($TT1 ne $TT2 |
| and $TT1!~/Intrinsic|Pointer|Ref|Typedef/) |
| { # different type of the type |
| my $Short1 = $Type1_Pure{"Name"}; |
| my $Short2 = $Type2_Pure{"Name"}; |
| |
| $Short1=~s/\A\Q$TT1\E //ig; |
| $Short2=~s/\A\Q$TT2\E //ig; |
| |
| if($Short1 eq $Short2) |
| { |
| %{$SubProblems{"DataType_Type"}{$Type1_Pure{"Name"}}}=( |
| "Target"=>$Type1_Pure{"Name"}, |
| "Type_Name"=>$Type1_Pure{"Name"}, |
| "Old_Value"=>lc($Type1_Pure{"Type"}), |
| "New_Value"=>lc($Type2_Pure{"Type"}) ); |
| } |
| } |
| return ($Cache{"mergeTypes"}{$Level}{$Type1_Id}{$Type2_Id} = \%SubProblems); |
| } |
| |
| pushType($Type1_Pure{"Tid"}, $Type2_Pure{"Tid"}, \@RecurTypes); |
| |
| if(($Type1_Pure{"Name"} eq $Type2_Pure{"Name"} |
| or (isAnon($Type1_Pure{"Name"}) and isAnon($Type2_Pure{"Name"}))) |
| and $Type1_Pure{"Type"}=~/\A(Struct|Class|Union)\Z/) |
| { # checking size |
| if($Level eq "Binary" |
| and $Type1_Pure{"Size"} and $Type2_Pure{"Size"} |
| and $Type1_Pure{"Size"} ne $Type2_Pure{"Size"}) |
| { |
| my $ProblemKind = "DataType_Size"; |
| if($Type1_Pure{"Type"} eq "Class" |
| and keys(%{$ClassMethods{$Level}{1}{$Type1_Pure{"Name"}}})) |
| { |
| if(isCopyingClass($Type1_Pure{"Tid"}, 1)) { |
| $ProblemKind = "Size_Of_Copying_Class"; |
| } |
| elsif($AllocableClass{1}{$Type1_Pure{"Name"}}) |
| { |
| if(int($Type2_Pure{"Size"})>int($Type1_Pure{"Size"})) { |
| $ProblemKind = "Size_Of_Allocable_Class_Increased"; |
| } |
| else |
| { |
| # descreased size of allocable class |
| # it has no special effects |
| } |
| } |
| } |
| %{$SubProblems{$ProblemKind}{$Type1_Pure{"Name"}}}=( |
| "Target"=>$Type1_Pure{"Name"}, |
| "Type_Name"=>$Type1_Pure{"Name"}, |
| "Old_Size"=>$Type1_Pure{"Size"}*$BYTE, |
| "New_Size"=>$Type2_Pure{"Size"}*$BYTE); |
| } |
| } |
| if(defined $Type1_Pure{"BaseType"} |
| and defined $Type2_Pure{"BaseType"}) |
| { # checking base types |
| my $Sub_SubProblems = mergeTypes($Type1_Pure{"BaseType"}, $Type2_Pure{"BaseType"}, $Level); |
| foreach my $Sub_SubProblemType (keys(%{$Sub_SubProblems})) |
| { |
| foreach my $Sub_SubLocation (keys(%{$Sub_SubProblems->{$Sub_SubProblemType}})) { |
| $SubProblems{$Sub_SubProblemType}{$Sub_SubLocation} = $Sub_SubProblems->{$Sub_SubProblemType}{$Sub_SubLocation}; |
| } |
| } |
| } |
| my (%AddedField, %RemovedField, %RenamedField, %RenamedField_Rev, %RelatedField, %RelatedField_Rev) = (); |
| my %NameToPosA = map {$Type1_Pure{"Memb"}{$_}{"name"}=>$_} keys(%{$Type1_Pure{"Memb"}}); |
| my %NameToPosB = map {$Type2_Pure{"Memb"}{$_}{"name"}=>$_} keys(%{$Type2_Pure{"Memb"}}); |
| foreach my $Member_Pos (sort {$a<=>$b} keys(%{$Type1_Pure{"Memb"}})) |
| { # detect removed and renamed fields |
| my $Member_Name = $Type1_Pure{"Memb"}{$Member_Pos}{"name"}; |
| next if(not $Member_Name); |
| my $MemberPair_Pos = (defined $Type2_Pure{"Memb"}{$Member_Pos} and $Type2_Pure{"Memb"}{$Member_Pos}{"name"} eq $Member_Name)?$Member_Pos:findMemPairByName($Member_Name, \%Type2_Pure); |
| if($MemberPair_Pos eq "lost") |
| { |
| if($Type2_Pure{"Type"}=~/\A(Struct|Class|Union)\Z/) |
| { |
| if(my $RenamedTo = isRenamed($Member_Pos, \%Type1_Pure, 1, \%Type2_Pure, 2)) |
| { # renamed |
| $RenamedField{$Member_Pos} = $RenamedTo; |
| $RenamedField_Rev{$NameToPosB{$RenamedTo}} = $Member_Name; |
| } |
| else |
| { # removed |
| $RemovedField{$Member_Pos} = 1; |
| } |
| } |
| elsif($Type1_Pure{"Type"} eq "Enum") |
| { |
| my $Member_Value1 = $Type1_Pure{"Memb"}{$Member_Pos}{"value"}; |
| next if($Member_Value1 eq ""); |
| $MemberPair_Pos = findMemPairByVal($Member_Value1, \%Type2_Pure); |
| if($MemberPair_Pos ne "lost") |
| { # renamed |
| my $RenamedTo = $Type2_Pure{"Memb"}{$MemberPair_Pos}{"name"}; |
| my $MemberPair_Pos_Rev = findMemPairByName($RenamedTo, \%Type1_Pure); |
| if($MemberPair_Pos_Rev eq "lost") |
| { |
| $RenamedField{$Member_Pos} = $RenamedTo; |
| $RenamedField_Rev{$NameToPosB{$RenamedTo}} = $Member_Name; |
| } |
| else { |
| $RemovedField{$Member_Pos} = 1; |
| } |
| } |
| else |
| { # removed |
| $RemovedField{$Member_Pos} = 1; |
| } |
| } |
| } |
| else |
| { # related |
| $RelatedField{$Member_Pos} = $MemberPair_Pos; |
| $RelatedField_Rev{$MemberPair_Pos} = $Member_Pos; |
| } |
| } |
| foreach my $Member_Pos (sort {$a<=>$b} keys(%{$Type2_Pure{"Memb"}})) |
| { # detect added fields |
| my $Member_Name = $Type2_Pure{"Memb"}{$Member_Pos}{"name"}; |
| next if(not $Member_Name); |
| my $MemberPair_Pos = (defined $Type1_Pure{"Memb"}{$Member_Pos} and $Type1_Pure{"Memb"}{$Member_Pos}{"name"} eq $Member_Name)?$Member_Pos:findMemPairByName($Member_Name, \%Type1_Pure); |
| if($MemberPair_Pos eq "lost") |
| { |
| if($Type2_Pure{"Type"}=~/\A(Struct|Class|Union|Enum)\Z/) |
| { |
| if(not $RenamedField_Rev{$Member_Pos}) |
| { # added |
| $AddedField{$Member_Pos}=1; |
| } |
| } |
| } |
| } |
| if($Type2_Pure{"Type"}=~/\A(Struct|Class)\Z/) |
| { # detect moved fields |
| my (%RelPos, %RelPosName, %AbsPos) = (); |
| my $Pos = 0; |
| foreach my $Member_Pos (sort {$a<=>$b} keys(%{$Type1_Pure{"Memb"}})) |
| { # relative positions in 1st version |
| my $Member_Name = $Type1_Pure{"Memb"}{$Member_Pos}{"name"}; |
| next if(not $Member_Name); |
| if(not $RemovedField{$Member_Pos}) |
| { # old type without removed fields |
| $RelPos{1}{$Member_Name} = $Pos; |
| $RelPosName{1}{$Pos} = $Member_Name; |
| $AbsPos{1}{$Pos++} = $Member_Pos; |
| } |
| } |
| $Pos = 0; |
| foreach my $Member_Pos (sort {$a<=>$b} keys(%{$Type2_Pure{"Memb"}})) |
| { # relative positions in 2nd version |
| my $Member_Name = $Type2_Pure{"Memb"}{$Member_Pos}{"name"}; |
| next if(not $Member_Name); |
| if(not $AddedField{$Member_Pos}) |
| { # new type without added fields |
| $RelPos{2}{$Member_Name} = $Pos; |
| $RelPosName{2}{$Pos} = $Member_Name; |
| $AbsPos{2}{$Pos++} = $Member_Pos; |
| } |
| } |
| foreach my $Member_Name (keys(%{$RelPos{1}})) |
| { |
| my $RPos1 = $RelPos{1}{$Member_Name}; |
| my $AbsPos1 = $NameToPosA{$Member_Name}; |
| my $Member_Name2 = $Member_Name; |
| if(my $RenamedTo = $RenamedField{$AbsPos1}) |
| { # renamed |
| $Member_Name2 = $RenamedTo; |
| } |
| my $RPos2 = $RelPos{2}{$Member_Name2}; |
| if($RPos2 ne "" and $RPos1 ne $RPos2) |
| { # different relative positions |
| my $AbsPos2 = $NameToPosB{$Member_Name2}; |
| if($AbsPos1 ne $AbsPos2) |
| { # different absolute positions |
| my $ProblemType = "Moved_Field"; |
| if(not isPublic(\%Type1_Pure, $AbsPos1)) |
| { # may change layout and size of type |
| if($Level eq "Source") { |
| next; |
| } |
| $ProblemType = "Moved_Private_Field"; |
| } |
| if($Level eq "Binary" |
| and $Type1_Pure{"Size"} ne $Type2_Pure{"Size"}) |
| { # affected size |
| my $MemSize1 = $TypeInfo{1}{$Type1_Pure{"Memb"}{$AbsPos1}{"type"}}{"Size"}; |
| my $MovedAbsPos = $AbsPos{1}{$RPos2}; |
| my $MemSize2 = $TypeInfo{1}{$Type1_Pure{"Memb"}{$MovedAbsPos}{"type"}}{"Size"}; |
| if($MemSize1 ne $MemSize2) { |
| $ProblemType .= "_And_Size"; |
| } |
| } |
| if($ProblemType eq "Moved_Private_Field") { |
| next; |
| } |
| %{$SubProblems{$ProblemType}{$Member_Name}}=( |
| "Target"=>$Member_Name, |
| "Type_Name"=>$Type1_Pure{"Name"}, |
| "Old_Value"=>$RPos1, |
| "New_Value"=>$RPos2 ); |
| } |
| } |
| } |
| } |
| foreach my $Member_Pos (sort {$a<=>$b} keys(%{$Type1_Pure{"Memb"}})) |
| { # check older fields, public and private |
| my $Member_Name = $Type1_Pure{"Memb"}{$Member_Pos}{"name"}; |
| next if(not $Member_Name); |
| next if($Member_Name eq "_vptr"); |
| if(my $RenamedTo = $RenamedField{$Member_Pos}) |
| { # renamed |
| if(defined $Constants{2}{$Member_Name}) |
| { |
| if($Constants{2}{$Member_Name}{"Value"} eq $RenamedTo) |
| { # define OLD NEW |
| next; # Safe |
| } |
| } |
| |
| if($Type2_Pure{"Type"}=~/\A(Struct|Class|Union)\Z/) |
| { |
| if(isPublic(\%Type1_Pure, $Member_Pos)) |
| { |
| %{$SubProblems{"Renamed_Field"}{$Member_Name}}=( |
| "Target"=>$Member_Name, |
| "Type_Name"=>$Type1_Pure{"Name"}, |
| "Old_Value"=>$Member_Name, |
| "New_Value"=>$RenamedTo ); |
| } |
| elsif(isReserved($Member_Name)) |
| { |
| %{$SubProblems{"Used_Reserved_Field"}{$Member_Name}}=( |
| "Target"=>$Member_Name, |
| "Type_Name"=>$Type1_Pure{"Name"}, |
| "Old_Value"=>$Member_Name, |
| "New_Value"=>$RenamedTo ); |
| } |
| } |
| elsif($Type1_Pure{"Type"} eq "Enum") |
| { |
| %{$SubProblems{"Enum_Member_Name"}{$Type1_Pure{"Memb"}{$Member_Pos}{"value"}}}=( |
| "Target"=>$Type1_Pure{"Memb"}{$Member_Pos}{"value"}, |
| "Type_Name"=>$Type1_Pure{"Name"}, |
| "Old_Value"=>$Member_Name, |
| "New_Value"=>$RenamedTo ); |
| } |
| } |
| elsif($RemovedField{$Member_Pos}) |
| { # removed |
| if($Type2_Pure{"Type"}=~/\A(Struct|Class)\Z/) |
| { |
| my $ProblemType = "Removed_Field"; |
| if(not isPublic(\%Type1_Pure, $Member_Pos) |
| or isUnnamed($Member_Name)) |
| { |
| if($Level eq "Source") { |
| next; |
| } |
| $ProblemType = "Removed_Private_Field"; |
| } |
| if($Level eq "Binary" |
| and not isMemPadded($Member_Pos, -1, \%Type1_Pure, \%RemovedField, 1)) |
| { |
| if(my $MNum = isAccessible(\%Type1_Pure, \%RemovedField, $Member_Pos+1, -1)) |
| { # affected fields |
| if(getOffset($MNum-1, \%Type1_Pure, 1)!=getOffset($RelatedField{$MNum-1}, \%Type2_Pure, 2)) |
| { # changed offset |
| $ProblemType .= "_And_Layout"; |
| } |
| } |
| if($Type1_Pure{"Size"} ne $Type2_Pure{"Size"}) |
| { # affected size |
| $ProblemType .= "_And_Size"; |
| } |
| } |
| if($ProblemType eq "Removed_Private_Field") { |
| next; |
| } |
| %{$SubProblems{$ProblemType}{$Member_Name}}=( |
| "Target"=>$Member_Name, |
| "Type_Name"=>$Type1_Pure{"Name"} ); |
| } |
| elsif($Type2_Pure{"Type"} eq "Union") |
| { |
| if($Level eq "Binary" |
| and $Type1_Pure{"Size"} ne $Type2_Pure{"Size"}) |
| { |
| %{$SubProblems{"Removed_Union_Field_And_Size"}{$Member_Name}}=( |
| "Target"=>$Member_Name, |
| "Type_Name"=>$Type1_Pure{"Name"} ); |
| } |
| else |
| { |
| %{$SubProblems{"Removed_Union_Field"}{$Member_Name}}=( |
| "Target"=>$Member_Name, |
| "Type_Name"=>$Type1_Pure{"Name"} ); |
| } |
| } |
| elsif($Type1_Pure{"Type"} eq "Enum") |
| { |
| %{$SubProblems{"Enum_Member_Removed"}{$Member_Name}}=( |
| "Target"=>$Member_Name, |
| "Type_Name"=>$Type1_Pure{"Name"}, |
| "Old_Value"=>$Member_Name ); |
| } |
| } |
| else |
| { # changed |
| my $MemberPair_Pos = $RelatedField{$Member_Pos}; |
| if($Type1_Pure{"Type"} eq "Enum") |
| { |
| my $Member_Value1 = $Type1_Pure{"Memb"}{$Member_Pos}{"value"}; |
| next if($Member_Value1 eq ""); |
| my $Member_Value2 = $Type2_Pure{"Memb"}{$MemberPair_Pos}{"value"}; |
| next if($Member_Value2 eq ""); |
| if($Member_Value1 ne $Member_Value2) |
| { |
| my $ProblemType = "Enum_Member_Value"; |
| if(isLastElem($Member_Pos, \%Type1_Pure)) { |
| $ProblemType = "Enum_Last_Member_Value"; |
| } |
| if($In::Desc{1}{"SkipConstants"}{$Member_Name}) { |
| $ProblemType = "Enum_Private_Member_Value"; |
| } |
| %{$SubProblems{$ProblemType}{$Member_Name}}=( |
| "Target"=>$Member_Name, |
| "Type_Name"=>$Type1_Pure{"Name"}, |
| "Old_Value"=>$Member_Value1, |
| "New_Value"=>$Member_Value2 ); |
| } |
| } |
| elsif($Type2_Pure{"Type"}=~/\A(Struct|Class|Union)\Z/) |
| { |
| my $Access1 = $Type1_Pure{"Memb"}{$Member_Pos}{"access"}; |
| my $Access2 = $Type2_Pure{"Memb"}{$MemberPair_Pos}{"access"}; |
| |
| if($Access1 ne "private" |
| and $Access2 eq "private") |
| { |
| %{$SubProblems{"Field_Became_Private"}{$Member_Name}}=( |
| "Target"=>$Member_Name, |
| "Type_Name"=>$Type1_Pure{"Name"}); |
| } |
| elsif($Access1 ne "protected" |
| and $Access1 ne "private" |
| and $Access2 eq "protected") |
| { |
| %{$SubProblems{"Field_Became_Protected"}{$Member_Name}}=( |
| "Target"=>$Member_Name, |
| "Type_Name"=>$Type1_Pure{"Name"}); |
| } |
| |
| my $MemberType1_Id = $Type1_Pure{"Memb"}{$Member_Pos}{"type"}; |
| my $MemberType2_Id = $Type2_Pure{"Memb"}{$MemberPair_Pos}{"type"}; |
| my $SizeV1 = $TypeInfo{1}{$MemberType1_Id}{"Size"}*$BYTE; |
| if(my $BSize1 = $Type1_Pure{"Memb"}{$Member_Pos}{"bitfield"}) { |
| $SizeV1 = $BSize1; |
| } |
| my $SizeV2 = $TypeInfo{2}{$MemberType2_Id}{"Size"}*$BYTE; |
| if(my $BSize2 = $Type2_Pure{"Memb"}{$MemberPair_Pos}{"bitfield"}) { |
| $SizeV2 = $BSize2; |
| } |
| |
| my $MemberType1_Name = $TypeInfo{1}{$MemberType1_Id}{"Name"}; |
| my $MemberType2_Name = $TypeInfo{2}{$MemberType2_Id}{"Name"}; |
| |
| if($Level eq "Binary" |
| and $SizeV1 ne "" and $SizeV2 ne "" |
| and $SizeV1 ne $SizeV2) |
| { |
| if($MemberType1_Name eq $MemberType2_Name or (isAnon($MemberType1_Name) and isAnon($MemberType2_Name)) |
| or ($Type1_Pure{"Memb"}{$Member_Pos}{"bitfield"} and $Type2_Pure{"Memb"}{$MemberPair_Pos}{"bitfield"})) |
| { # field size change (including anon-structures and unions) |
| # - same types |
| # - unnamed types |
| # - bitfields |
| my $ProblemType = "Field_Size"; |
| if(not isPublic(\%Type1_Pure, $Member_Pos) |
| or isUnnamed($Member_Name)) |
| { # should not be accessed by applications, goes to "Low Severity" |
| # example: "abidata" members in GStreamer types |
| $ProblemType = "Private_".$ProblemType; |
| } |
| if(not isMemPadded($Member_Pos, $TypeInfo{2}{$MemberType2_Id}{"Size"}*$BYTE, \%Type1_Pure, \%RemovedField, 1)) |
| { # check an effect |
| if($Type2_Pure{"Type"} ne "Union" |
| and my $MNum = isAccessible(\%Type1_Pure, \%RemovedField, $Member_Pos+1, -1)) |
| { # public fields after the current |
| if(getOffset($MNum-1, \%Type1_Pure, 1)!=getOffset($RelatedField{$MNum-1}, \%Type2_Pure, 2)) |
| { # changed offset |
| $ProblemType .= "_And_Layout"; |
| } |
| } |
| if($Type1_Pure{"Size"} ne $Type2_Pure{"Size"}) { |
| $ProblemType .= "_And_Type_Size"; |
| } |
| } |
| if($ProblemType eq "Private_Field_Size") |
| { # private field size with no effect |
| } |
| if($ProblemType eq "Field_Size") |
| { |
| if($Type1_Pure{"Type"}=~/Union|Struct/ and $SizeV1<$SizeV2) |
| { # Low severity |
| $ProblemType = "Struct_Field_Size_Increased"; |
| } |
| } |
| if($ProblemType) |
| { # register a problem |
| %{$SubProblems{$ProblemType}{$Member_Name}}=( |
| "Target"=>$Member_Name, |
| "Type_Name"=>$Type1_Pure{"Name"}, |
| "Old_Size"=>$SizeV1, |
| "New_Size"=>$SizeV2); |
| } |
| } |
| } |
| if($Type1_Pure{"Memb"}{$Member_Pos}{"bitfield"} |
| or $Type2_Pure{"Memb"}{$MemberPair_Pos}{"bitfield"}) |
| { # do NOT check bitfield type changes |
| next; |
| } |
| |
| if(not $Type1_Pure{"Memb"}{$Member_Pos}{"mutable"} |
| and $Type2_Pure{"Memb"}{$MemberPair_Pos}{"mutable"}) |
| { |
| %{$SubProblems{"Field_Became_Mutable"}{$Member_Name}}=( |
| "Target"=>$Member_Name, |
| "Type_Name"=>$Type1_Pure{"Name"}); |
| } |
| elsif($Type1_Pure{"Memb"}{$Member_Pos}{"mutable"} |
| and not $Type2_Pure{"Memb"}{$MemberPair_Pos}{"mutable"}) |
| { |
| %{$SubProblems{"Field_Became_Non_Mutable"}{$Member_Name}}=( |
| "Target"=>$Member_Name, |
| "Type_Name"=>$Type1_Pure{"Name"}); |
| } |
| |
| my %Sub_SubChanges = detectTypeChange($MemberType1_Id, $MemberType2_Id, "Field", $Level); |
| foreach my $ProblemType (keys(%Sub_SubChanges)) |
| { |
| my $Old_Value = $Sub_SubChanges{$ProblemType}{"Old_Value"}; |
| my $New_Value = $Sub_SubChanges{$ProblemType}{"New_Value"}; |
| |
| # quals |
| if($ProblemType eq "Field_Type" |
| or $ProblemType eq "Field_Type_And_Size" |
| or $ProblemType eq "Field_Type_Format") |
| { |
| if(addedQual($Old_Value, $New_Value, "volatile")) { |
| %{$Sub_SubChanges{"Field_Became_Volatile"}} = %{$Sub_SubChanges{$ProblemType}}; |
| } |
| elsif(removedQual($Old_Value, $New_Value, "volatile")) { |
| %{$Sub_SubChanges{"Field_Became_Non_Volatile"}} = %{$Sub_SubChanges{$ProblemType}}; |
| } |
| |
| if(my $RA = addedQual($Old_Value, $New_Value, "const")) |
| { |
| if($RA==2) { |
| %{$Sub_SubChanges{"Field_Added_Const"}} = %{$Sub_SubChanges{$ProblemType}}; |
| } |
| else { |
| %{$Sub_SubChanges{"Field_Became_Const"}} = %{$Sub_SubChanges{$ProblemType}}; |
| } |
| } |
| elsif(my $RR = removedQual($Old_Value, $New_Value, "const")) |
| { |
| if($RR==2) { |
| %{$Sub_SubChanges{"Field_Removed_Const"}} = %{$Sub_SubChanges{$ProblemType}}; |
| } |
| else { |
| %{$Sub_SubChanges{"Field_Became_Non_Const"}} = %{$Sub_SubChanges{$ProblemType}}; |
| } |
| } |
| } |
| } |
| |
| if($Level eq "Source") |
| { |
| foreach my $ProblemType (keys(%Sub_SubChanges)) |
| { |
| my $Old_Value = $Sub_SubChanges{$ProblemType}{"Old_Value"}; |
| my $New_Value = $Sub_SubChanges{$ProblemType}{"New_Value"}; |
| |
| if($ProblemType eq "Field_Type") |
| { |
| if(cmpBTypes($Old_Value, $New_Value, 1, 2)) { |
| delete($Sub_SubChanges{$ProblemType}); |
| } |
| } |
| } |
| } |
| |
| foreach my $ProblemType (keys(%Sub_SubChanges)) |
| { |
| my $ProblemType_Init = $ProblemType; |
| if($ProblemType eq "Field_Type_And_Size") |
| { # Binary |
| if(not isPublic(\%Type1_Pure, $Member_Pos) |
| or isUnnamed($Member_Name)) { |
| $ProblemType = "Private_".$ProblemType; |
| } |
| if(not isMemPadded($Member_Pos, $TypeInfo{2}{$MemberType2_Id}{"Size"}*$BYTE, \%Type1_Pure, \%RemovedField, 1)) |
| { # check an effect |
| if($Type2_Pure{"Type"} ne "Union" |
| and my $MNum = isAccessible(\%Type1_Pure, \%RemovedField, $Member_Pos+1, -1)) |
| { # public fields after the current |
| if(getOffset($MNum-1, \%Type1_Pure, 1)!=getOffset($RelatedField{$MNum-1}, \%Type2_Pure, 2)) |
| { # changed offset |
| $ProblemType .= "_And_Layout"; |
| } |
| } |
| if($Type1_Pure{"Size"} ne $Type2_Pure{"Size"}) { |
| $ProblemType .= "_And_Type_Size"; |
| } |
| } |
| } |
| else |
| { |
| # TODO: Private_Field_Type rule? |
| |
| if(not isPublic(\%Type1_Pure, $Member_Pos) |
| or isUnnamed($Member_Name)) { |
| next; |
| } |
| } |
| if($ProblemType eq "Private_Field_Type_And_Size") |
| { # private field change with no effect |
| } |
| %{$SubProblems{$ProblemType}{$Member_Name}}=( |
| "Target"=>$Member_Name, |
| "Type_Name"=>$Type1_Pure{"Name"}); |
| |
| foreach my $Attr (keys(%{$Sub_SubChanges{$ProblemType_Init}})) |
| { # other properties |
| $SubProblems{$ProblemType}{$Member_Name}{$Attr} = $Sub_SubChanges{$ProblemType_Init}{$Attr}; |
| } |
| } |
| if(not isPublic(\%Type1_Pure, $Member_Pos)) |
| { # do NOT check internal type changes |
| next; |
| } |
| if($MemberType1_Id and $MemberType2_Id) |
| { # checking member type changes |
| my $Sub_SubProblems = mergeTypes($MemberType1_Id, $MemberType2_Id, $Level); |
| |
| my %DupProblems = (); |
| |
| foreach my $Sub_SubProblemType (sort keys(%{$Sub_SubProblems})) |
| { |
| foreach my $Sub_SubLocation (sort {length($a)<=>length($b)} sort keys(%{$Sub_SubProblems->{$Sub_SubProblemType}})) |
| { |
| if(not defined $In::Opt{"AllAffected"}) |
| { |
| if(defined $DupProblems{$Sub_SubProblems->{$Sub_SubProblemType}{$Sub_SubLocation}}) { |
| next; |
| } |
| } |
| |
| my $NewLocation = ($Sub_SubLocation)?$Member_Name."->".$Sub_SubLocation:$Member_Name; |
| $SubProblems{$Sub_SubProblemType}{$NewLocation} = $Sub_SubProblems->{$Sub_SubProblemType}{$Sub_SubLocation}; |
| |
| if(not defined $In::Opt{"AllAffected"}) |
| { |
| $DupProblems{$Sub_SubProblems->{$Sub_SubProblemType}{$Sub_SubLocation}} = 1; |
| } |
| } |
| } |
| |
| %DupProblems = (); |
| } |
| } |
| } |
| } |
| foreach my $Member_Pos (sort {$a<=>$b} keys(%{$Type2_Pure{"Memb"}})) |
| { # checking added members, public and private |
| my $Member_Name = $Type2_Pure{"Memb"}{$Member_Pos}{"name"}; |
| next if(not $Member_Name); |
| next if($Member_Name eq "_vptr"); |
| if($AddedField{$Member_Pos}) |
| { # added |
| if($Type2_Pure{"Type"}=~/\A(Struct|Class)\Z/) |
| { |
| my $ProblemType = "Added_Field"; |
| if(not isPublic(\%Type2_Pure, $Member_Pos) |
| or isUnnamed($Member_Name)) |
| { |
| if($Level eq "Source") { |
| next; |
| } |
| $ProblemType = "Added_Private_Field"; |
| } |
| if($Level eq "Binary" |
| and not isMemPadded($Member_Pos, -1, \%Type2_Pure, \%AddedField, 2)) |
| { |
| if(my $MNum = isAccessible(\%Type2_Pure, \%AddedField, $Member_Pos, -1)) |
| { # public fields after the current |
| if(getOffset($MNum-1, \%Type2_Pure, 2)!=getOffset($RelatedField_Rev{$MNum-1}, \%Type1_Pure, 1)) |
| { # changed offset |
| $ProblemType .= "_And_Layout"; |
| } |
| } |
| if($Type1_Pure{"Size"} ne $Type2_Pure{"Size"}) { |
| $ProblemType .= "_And_Size"; |
| } |
| } |
| if($ProblemType eq "Added_Private_Field") |
| { # skip added private fields |
| next; |
| } |
| %{$SubProblems{$ProblemType}{$Member_Name}}=( |
| "Target"=>$Member_Name, |
| "Type_Name"=>$Type1_Pure{"Name"}); |
| } |
| elsif($Type2_Pure{"Type"} eq "Union") |
| { |
| if($Level eq "Binary" |
| and $Type1_Pure{"Size"} ne $Type2_Pure{"Size"}) |
| { |
| %{$SubProblems{"Added_Union_Field_And_Size"}{$Member_Name}}=( |
| "Target"=>$Member_Name, |
| "Type_Name"=>$Type1_Pure{"Name"}); |
| } |
| else |
| { |
| %{$SubProblems{"Added_Union_Field"}{$Member_Name}}=( |
| "Target"=>$Member_Name, |
| "Type_Name"=>$Type1_Pure{"Name"}); |
| } |
| } |
| elsif($Type2_Pure{"Type"} eq "Enum") |
| { |
| my $Member_Value = $Type2_Pure{"Memb"}{$Member_Pos}{"value"}; |
| next if($Member_Value eq ""); |
| %{$SubProblems{"Added_Enum_Member"}{$Member_Name}}=( |
| "Target"=>$Member_Name, |
| "Type_Name"=>$Type2_Pure{"Name"}, |
| "New_Value"=>$Member_Value); |
| } |
| } |
| } |
| |
| if($Type1_Pure{"Type"} eq "FuncPtr") |
| { |
| foreach my $PPos (sort {$a<=>$b} keys(%{$Type1_Pure{"Param"}})) |
| { |
| if(not defined $Type2_Pure{"Param"}{$PPos}) { |
| next; |
| } |
| |
| my $PT1 = $Type1_Pure{"Param"}{$PPos}{"type"}; |
| my $PT2 = $Type2_Pure{"Param"}{$PPos}{"type"}; |
| |
| my $PName = "p".$PPos; |
| |
| my $FP_SubProblems = mergeTypes($PT1, $PT2, $Level); |
| my %DupProblems = (); |
| |
| foreach my $FP_SubProblemType (keys(%{$FP_SubProblems})) |
| { |
| foreach my $FP_SubLocation (keys(%{$FP_SubProblems->{$FP_SubProblemType}})) |
| { |
| if(not defined $In::Opt{"AllAffected"}) |
| { |
| if(defined $DupProblems{$FP_SubProblems->{$FP_SubProblemType}{$FP_SubLocation}}) { |
| next; |
| } |
| } |
| |
| my $NewLocation = ($FP_SubLocation)?$PName."->".$FP_SubLocation:$PName; |
| $SubProblems{$FP_SubProblemType}{$NewLocation} = $FP_SubProblems->{$FP_SubProblemType}{$FP_SubLocation}; |
| |
| if(not defined $In::Opt{"AllAffected"}) |
| { |
| $DupProblems{$FP_SubProblems->{$FP_SubProblemType}{$FP_SubLocation}} = 1; |
| } |
| } |
| } |
| |
| %DupProblems = (); |
| } |
| } |
| |
| pop(@RecurTypes); |
| return ($Cache{"mergeTypes"}{$Level}{$Type1_Id}{$Type2_Id} = \%SubProblems); |
| } |
| |
| sub isUnnamed($) { |
| return $_[0]=~/\Aunnamed\d+\Z/; |
| } |
| |
| sub detectAdded($) |
| { |
| my $Level = $_[0]; |
| foreach my $Symbol (keys(%{$In::ABI{2}{"SymLib"}})) |
| { |
| if(linkSymbol($Symbol, 1, "+Deps")) |
| { # linker can find a new symbol |
| # in the old-version library |
| # So, it's not a new symbol |
| next; |
| } |
| if(my $VSym = $In::ABI{2}{"SymbolVersion"}{$Symbol} |
| and index($Symbol,"\@")==-1) { |
| next; |
| } |
| $AddedInt{$Level}{$Symbol} = 1; |
| } |
| } |
| |
| sub detectRemoved($) |
| { |
| my $Level = $_[0]; |
| foreach my $Symbol (keys(%{$In::ABI{1}{"SymLib"}})) |
| { |
| if(linkSymbol($Symbol, 2, "+Deps")) |
| { # linker can find an old symbol |
| # in the new-version library |
| next; |
| } |
| if(my $VSym = $In::ABI{1}{"SymbolVersion"}{$Symbol} |
| and index($Symbol,"\@")==-1) { |
| next; |
| } |
| $RemovedInt{$Level}{$Symbol} = 1; |
| } |
| } |
| |
| sub checkVtable($$$) |
| { |
| my ($Level, $Symbol, $V) = @_; |
| |
| # skip v-tables for templates, that should not be imported by applications |
| if(my $CName = $VTableClass{$V}{$Symbol}) |
| { |
| if(index($CName, "<")!=-1) { |
| return 0; |
| } |
| |
| if(not keys(%{$ClassMethods{$Level}{$V}{$CName}})) |
| { # do not show vtables for "private" classes |
| # use case: vtable for QDragManager (Qt 4.5.3 to 4.6.0) became HIDDEN symbol |
| return 0; |
| } |
| } |
| |
| if($In::Desc{$V}{"SkipSymbols"}{$Symbol}) |
| { # user defined symbols to ignore |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| sub mergeLibs($) |
| { |
| my $Level = $_[0]; |
| foreach my $Symbol (sort keys(%{$AddedInt{$Level}})) |
| { # checking added symbols |
| next if(not $CompSign{2}{$Symbol}{"Header"} and not $CompSign{2}{$Symbol}{"Source"}); |
| |
| if(index($Symbol, "_ZTV")==0) |
| { |
| if(not checkVtable($Level, $Symbol, 2)) { |
| next; |
| } |
| } |
| else { |
| next if(not symbolFilter($Symbol, $CompSign{2}{$Symbol}, "Affected + InlineVirt", $Level, 2)); |
| } |
| |
| if($CompSign{2}{$Symbol}{"PureVirt"}) |
| { # symbols for pure virtual methods cannot be called by clients |
| next; |
| } |
| |
| %{$CompatProblems{$Level}{$Symbol}{"Added_Symbol"}{""}} = (); |
| } |
| foreach my $Symbol (sort keys(%{$RemovedInt{$Level}})) |
| { # checking removed symbols |
| next if(not $CompSign{1}{$Symbol}{"Header"} and not $CompSign{1}{$Symbol}{"Source"}); |
| |
| if(index($Symbol, "_ZTV")==0) |
| { |
| if(not checkVtable($Level, $Symbol, 1)) { |
| next; |
| } |
| } |
| else { |
| next if(not symbolFilter($Symbol, $CompSign{1}{$Symbol}, "Affected + InlineVirt", $Level, 1)); |
| } |
| |
| if($CompSign{1}{$Symbol}{"PureVirt"}) |
| { # symbols for pure virtual methods cannot be called by clients |
| next; |
| } |
| |
| %{$CompatProblems{$Level}{$Symbol}{"Removed_Symbol"}{""}} = (); |
| } |
| } |
| |
| sub detectAdded_H($) |
| { |
| my $Level = $_[0]; |
| foreach my $Symbol (sort keys(%{$CompSign{2}})) |
| { |
| if($Level eq "Source") |
| { # remove symbol version |
| my ($SN, $SS, $SV) = symbolParts($Symbol); |
| $Symbol=$SN; |
| |
| if($CompSign{2}{$Symbol}{"Artificial"}) |
| { # skip artificial constructors |
| next; |
| } |
| } |
| |
| if(not $CompSign{2}{$Symbol}{"Header"} |
| and not $CompSign{2}{$Symbol}{"Source"}) { |
| next; |
| } |
| |
| if(not $CompSign{2}{$Symbol}{"MnglName"}) { |
| next; |
| } |
| |
| if($ExtendedSymbols{$Symbol}) { |
| next; |
| } |
| |
| if(not $CompSign{2}{$Symbol}{"Virt"} |
| and not $CompSign{2}{$Symbol}{"PureVirt"} |
| and $CompSign{2}{$Symbol}{"InLine"}) |
| { # sunch symbols are not properly detected |
| next; |
| } |
| |
| if(not defined $CompSign{1}{$Symbol} |
| or not $CompSign{1}{$Symbol}{"MnglName"}) |
| { |
| if($UsedDump{2}{"SrcBin"}) |
| { |
| if($UsedDump{1}{"BinOnly"}) |
| { # support for different ABI dumps |
| if(not $CompSign{2}{$Symbol}{"Virt"} |
| and not $CompSign{2}{$Symbol}{"PureVirt"}) |
| { |
| if($In::Opt{"CheckHeadersOnly"}) |
| { |
| if(my $Lang = $CompSign{2}{$Symbol}{"Lang"}) |
| { |
| if($Lang eq "C") |
| { # support for old ABI dumps: missed extern "C" functions |
| next; |
| } |
| } |
| } |
| else |
| { |
| if(not linkSymbol($Symbol, 2, "-Deps")) |
| { # skip added inline symbols and const global data |
| next; |
| } |
| } |
| } |
| } |
| } |
| $AddedInt{$Level}{$Symbol} = 1; |
| } |
| } |
| } |
| |
| sub detectRemoved_H($) |
| { |
| my $Level = $_[0]; |
| foreach my $Symbol (sort keys(%{$CompSign{1}})) |
| { |
| my $ISymbol = $Symbol; |
| |
| if($Level eq "Source") |
| { # remove symbol version |
| my ($SN, $SS, $SV) = symbolParts($Symbol); |
| $Symbol = $SN; |
| } |
| |
| if(not $CompSign{1}{$Symbol}{"Header"} |
| and not $CompSign{1}{$Symbol}{"Source"}) { |
| next; |
| } |
| |
| if(not $CompSign{1}{$Symbol}{"MnglName"}) { |
| next; |
| } |
| |
| if($ExtendedSymbols{$Symbol}) { |
| next; |
| } |
| |
| if(not $CompSign{1}{$Symbol}{"Virt"} |
| and not $CompSign{1}{$Symbol}{"PureVirt"} |
| and $CompSign{1}{$Symbol}{"InLine"}) |
| { # sunch symbols are not properly detected |
| next; |
| } |
| |
| if(not defined $CompSign{2}{$Symbol} |
| or not $CompSign{2}{$Symbol}{"MnglName"}) |
| { |
| if(defined $UsedDump{1}{"DWARF"} |
| and defined $UsedDump{2}{"DWARF"} |
| and $Level eq "Source") |
| { # not present in debug-info, |
| # but present in dynsym |
| if(linkSymbol($Symbol, 2, "-Deps")) { |
| next; |
| } |
| |
| if($ISymbol ne $Symbol) |
| { |
| if(linkSymbol($ISymbol, 2, "-Deps")) { |
| next; |
| } |
| } |
| |
| if(my $SVer = $In::ABI{2}{"SymbolVersion"}{$Symbol}) |
| { |
| if(linkSymbol($SVer, 2, "-Deps")) { |
| next; |
| } |
| } |
| |
| if(my $Alias = $CompSign{1}{$ISymbol}{"Alias"}) |
| { |
| if(linkSymbol($Alias, 2, "-Deps")) { |
| next; |
| } |
| |
| if(my $SAVer = $In::ABI{2}{"SymbolVersion"}{$Alias}) |
| { |
| if(linkSymbol($SAVer, 2, "-Deps")) { |
| next; |
| } |
| } |
| } |
| } |
| if($UsedDump{1}{"SrcBin"}) |
| { |
| if($UsedDump{2}{"BinOnly"}) |
| { # support for different ABI dumps |
| if(not $CompSign{1}{$Symbol}{"Virt"} |
| and not $CompSign{1}{$Symbol}{"PureVirt"}) |
| { |
| if($In::Opt{"CheckHeadersOnly"}) |
| { # skip all removed symbols |
| if(my $Lang = $CompSign{1}{$Symbol}{"Lang"}) |
| { |
| if($Lang eq "C") |
| { # support for old ABI dumps: missed extern "C" functions |
| next; |
| } |
| } |
| } |
| else |
| { |
| if(not linkSymbol($Symbol, 1, "-Deps")) |
| { # skip removed inline symbols |
| next; |
| } |
| } |
| } |
| } |
| } |
| |
| if(not $CompSign{1}{$Symbol}{"Class"}) |
| { |
| if(my $Short = $CompSign{1}{$Symbol}{"ShortName"}) |
| { |
| if(defined $Constants{2}{$Short}) |
| { |
| my $Val = $Constants{2}{$Short}{"Value"}; |
| if(defined $Func_ShortName{2}{$Val}) |
| { # old name defined to new |
| next; |
| } |
| } |
| } |
| |
| } |
| $RemovedInt{$Level}{$Symbol} = 1; |
| if($Level eq "Source") |
| { # search for a source-compatible equivalent |
| setAlternative($Symbol, $Level); |
| } |
| } |
| } |
| } |
| |
| sub mergeHeaders($) |
| { |
| my $Level = $_[0]; |
| foreach my $Symbol (sort keys(%{$AddedInt{$Level}})) |
| { # checking added symbols |
| next if($CompSign{2}{$Symbol}{"PureVirt"}); |
| next if(not symbolFilter($Symbol, $CompSign{2}{$Symbol}, "Affected", $Level, 2)); |
| if($Level eq "Binary") |
| { |
| if($CompSign{2}{$Symbol}{"InLine"}) |
| { |
| if(not $CompSign{2}{$Symbol}{"Virt"}) |
| { # skip inline non-virtual functions |
| next; |
| } |
| } |
| } |
| else |
| { # Source |
| if($SourceAlternative_B{$Symbol}) { |
| next; |
| } |
| } |
| %{$CompatProblems{$Level}{$Symbol}{"Added_Symbol"}{""}}=(); |
| } |
| foreach my $Symbol (sort keys(%{$RemovedInt{$Level}})) |
| { # checking removed symbols |
| next if($CompSign{1}{$Symbol}{"PureVirt"}); |
| next if(not symbolFilter($Symbol, $CompSign{1}{$Symbol}, "Affected", $Level, 1)); |
| if($Level eq "Binary") |
| { |
| if($CompSign{1}{$Symbol}{"InLine"}) |
| { |
| if(not $CompSign{1}{$Symbol}{"Virt"}) |
| { # skip inline non-virtual functions |
| next; |
| } |
| } |
| } |
| else |
| { # Source |
| if(my $Alt = $SourceAlternative{$Symbol}) |
| { |
| if(defined $CompSign{1}{$Alt} |
| and $CompSign{1}{$Symbol}{"Const"}) |
| { |
| my $Cid = $CompSign{1}{$Symbol}{"Class"}; |
| %{$CompatProblems{$Level}{$Symbol}{"Removed_Const_Overload"}{"this"}}=( |
| "Type_Name"=>$TypeInfo{1}{$Cid}{"Name"}, |
| "Target"=>$Alt); |
| } |
| else |
| { # do NOT show removed symbol |
| next; |
| } |
| } |
| } |
| %{$CompatProblems{$Level}{$Symbol}{"Removed_Symbol"}{""}}=(); |
| } |
| } |
| |
| sub addParamNames($) |
| { |
| my $LibraryVersion = $_[0]; |
| |
| if(not keys(%AddSymbolParams)) { |
| return; |
| } |
| |
| my $SecondVersion = $LibraryVersion==1?2:1; |
| foreach my $Interface (sort keys(%{$CompSign{$LibraryVersion}})) |
| { |
| if(not keys(%{$AddSymbolParams{$Interface}})) { |
| next; |
| } |
| |
| foreach my $ParamPos (sort {$a<=>$b} keys(%{$CompSign{$LibraryVersion}{$Interface}{"Param"}})) |
| { # add absent parameter names |
| my $ParamName = $CompSign{$LibraryVersion}{$Interface}{"Param"}{$ParamPos}{"name"}; |
| if($ParamName=~/\Ap\d+\Z/ and my $NewParamName = $AddSymbolParams{$Interface}{$ParamPos}) |
| { # names from the external file |
| if(defined $CompSign{$SecondVersion}{$Interface} |
| and defined $CompSign{$SecondVersion}{$Interface}{"Param"}{$ParamPos}) |
| { |
| if($CompSign{$SecondVersion}{$Interface}{"Param"}{$ParamPos}{"name"}=~/\Ap\d+\Z/) { |
| $CompSign{$LibraryVersion}{$Interface}{"Param"}{$ParamPos}{"name"} = $NewParamName; |
| } |
| } |
| else { |
| $CompSign{$LibraryVersion}{$Interface}{"Param"}{$ParamPos}{"name"} = $NewParamName; |
| } |
| } |
| } |
| } |
| } |
| |
| sub detectChangedTypedefs() |
| { # detect changed typedefs to show |
| # correct function signatures |
| foreach my $Typedef (keys(%{$In::ABI{1}{"TypedefBase"}})) |
| { |
| if(not $Typedef) { |
| next; |
| } |
| |
| my $BName1 = $In::ABI{1}{"TypedefBase"}{$Typedef}; |
| if(not $BName1 or isAnon($BName1)) { |
| next; |
| } |
| |
| my $BName2 = $In::ABI{2}{"TypedefBase"}{$Typedef}; |
| if(not $BName2 or isAnon($BName2)) { |
| next; |
| } |
| |
| $BName1 = uncoverTypedefs($BName1, 1); |
| $BName2 = uncoverTypedefs($BName2, 2); |
| |
| if($BName1 ne $BName2) { |
| $ChangedTypedef{$Typedef} = 1; |
| } |
| } |
| } |
| |
| sub symbolPrefix($$) |
| { |
| my ($Symbol, $LVer) = @_; |
| my $ShortName = $CompSign{$LVer}{$Symbol}{"ShortName"}; |
| if(my $ClassId = $CompSign{$LVer}{$Symbol}{"Class"}) |
| { # methods |
| $ShortName = $TypeInfo{$LVer}{$ClassId}{"Name"}."::".$ShortName; |
| } |
| return $ShortName; |
| } |
| |
| sub setAlternative($) |
| { |
| my $Symbol = $_[0]; |
| my $PSymbol = $Symbol; |
| if(not defined $CompSign{2}{$PSymbol} |
| or (not $CompSign{2}{$PSymbol}{"MnglName"} |
| and not $CompSign{2}{$PSymbol}{"ShortName"})) |
| { # search for a pair |
| if(my $ShortName = $CompSign{1}{$PSymbol}{"ShortName"}) |
| { |
| if($CompSign{1}{$PSymbol}{"Data"}) |
| { |
| if($PSymbol=~s/L(\d+$ShortName(E)\Z)/$1/ |
| or $PSymbol=~s/(\d+$ShortName(E)\Z)/L$1/) |
| { |
| if(defined $CompSign{2}{$PSymbol} |
| and $CompSign{2}{$PSymbol}{"MnglName"}) |
| { |
| $SourceAlternative{$Symbol} = $PSymbol; |
| $SourceAlternative_B{$PSymbol} = $Symbol; |
| if(not defined $CompSign{1}{$PSymbol} |
| or not $CompSign{1}{$PSymbol}{"MnglName"}) { |
| $SourceReplacement{$Symbol} = $PSymbol; |
| } |
| } |
| } |
| } |
| else |
| { |
| foreach my $Sp ("KV", "VK", "K", "V") |
| { |
| if($PSymbol=~s/\A_ZN$Sp/_ZN/ |
| or $PSymbol=~s/\A_ZN/_ZN$Sp/) |
| { |
| if(defined $CompSign{2}{$PSymbol} |
| and $CompSign{2}{$PSymbol}{"MnglName"}) |
| { |
| $SourceAlternative{$Symbol} = $PSymbol; |
| $SourceAlternative_B{$PSymbol} = $Symbol; |
| if(not defined $CompSign{1}{$PSymbol} |
| or not $CompSign{1}{$PSymbol}{"MnglName"}) { |
| $SourceReplacement{$Symbol} = $PSymbol; |
| } |
| } |
| } |
| $PSymbol = $Symbol; |
| } |
| } |
| } |
| } |
| return ""; |
| } |
| |
| sub getSymKind($$) |
| { |
| my ($Symbol, $LVer) = @_; |
| if($CompSign{$LVer}{$Symbol}{"Data"}) |
| { |
| return "Global_Data"; |
| } |
| elsif($CompSign{$LVer}{$Symbol}{"Class"}) |
| { |
| return "Method"; |
| } |
| return "Function"; |
| } |
| |
| sub isConstData($$) |
| { |
| my ($Symbol, $LVer) = @_; |
| |
| my $Return = $CompSign{$LVer}{$Symbol}{"Return"}; |
| my $RTName = uncoverTypedefs($TypeInfo{$LVer}{$Return}{"Name"}, $LVer); |
| |
| return ($RTName=~/\bconst\Z/); |
| } |
| |
| sub getConstDataSym($$) |
| { |
| my ($Symbol, $LVer) = @_; |
| |
| my $Short = $CompSign{$LVer}{$Symbol}{"ShortName"}; |
| $Symbol=~s/(\d+$Short)/L$1/; |
| return $Symbol; |
| } |
| |
| sub getNonConstDataSym($$) |
| { |
| my ($Symbol, $LVer) = @_; |
| |
| my $Short = $CompSign{$LVer}{$Symbol}{"ShortName"}; |
| $Symbol=~s/L(\d+$Short)/$1/; |
| return $Symbol; |
| } |
| |
| sub mergeSymbols($) |
| { |
| my $Level = $_[0]; |
| my %SubProblems = (); |
| |
| mergeBases($Level); |
| |
| my %AddedOverloads = (); |
| foreach my $Symbol (sort keys(%{$AddedInt{$Level}})) |
| { # check all added exported symbols |
| if(not $CompSign{2}{$Symbol}{"Header"} |
| and not $CompSign{2}{$Symbol}{"Source"}) { |
| next; |
| } |
| if(defined $CompSign{1}{$Symbol} |
| and ($CompSign{1}{$Symbol}{"Header"} or $CompSign{1}{$Symbol}{"Source"})) |
| { # double-check added symbol |
| next; |
| } |
| if($Symbol=~/\A(_Z|\?)/) |
| { # C++ |
| $AddedOverloads{symbolPrefix($Symbol, 2)}{getSignature($Symbol, 2, "Qual")} = $Symbol; |
| } |
| if(my $OverriddenMethod = $CompSign{2}{$Symbol}{"Override"}) |
| { # register virtual overridings |
| my $Cid = $CompSign{2}{$Symbol}{"Class"}; |
| my $AffectedClass_Name = $TypeInfo{2}{$Cid}{"Name"}; |
| if(defined $CompSign{1}{$OverriddenMethod} and $CompSign{1}{$OverriddenMethod}{"Virt"} |
| and not $CompSign{1}{$OverriddenMethod}{"Private"}) |
| { |
| if($TName_Tid{1}{$AffectedClass_Name}) |
| { # class should exist in previous version |
| if(not isCopyingClass($TName_Tid{1}{$AffectedClass_Name}, 1)) |
| { # old v-table is NOT copied by old applications |
| if(symbolFilter($OverriddenMethod, $CompSign{1}{$OverriddenMethod}, "Affected", $Level, 1)) |
| { |
| %{$CompatProblems{$Level}{$OverriddenMethod}{"Overridden_Virtual_Method"}{$Symbol}}=( |
| "Type_Name"=>$AffectedClass_Name, |
| "Target"=>$Symbol, |
| "Old_Value"=>$OverriddenMethod, |
| "New_Value"=>$Symbol); |
| } |
| } |
| } |
| } |
| } |
| } |
| foreach my $Symbol (sort keys(%{$RemovedInt{$Level}})) |
| { # check all removed exported symbols |
| if(not $CompSign{1}{$Symbol}{"Header"} |
| and not $CompSign{1}{$Symbol}{"Source"}) { |
| next; |
| } |
| if(defined $CompSign{2}{$Symbol} |
| and ($CompSign{2}{$Symbol}{"Header"} or $CompSign{2}{$Symbol}{"Source"})) |
| { # double-check removed symbol |
| next; |
| } |
| if(not symbolFilter($Symbol, $CompSign{1}{$Symbol}, "Affected", $Level, 1)) { |
| next; |
| } |
| |
| $CheckedSymbols{$Level}{$Symbol} = 1; |
| |
| if(my $OverriddenMethod = $CompSign{1}{$Symbol}{"Override"}) |
| { # register virtual overridings |
| my $Cid = $CompSign{1}{$Symbol}{"Class"}; |
| my $AffectedClass_Name = $TypeInfo{1}{$Cid}{"Name"}; |
| if(defined $CompSign{2}{$OverriddenMethod} |
| and $CompSign{2}{$OverriddenMethod}{"Virt"}) |
| { |
| if($TName_Tid{2}{$AffectedClass_Name}) |
| { # class should exist in newer version |
| if(not isCopyingClass($CompSign{1}{$Symbol}{"Class"}, 1)) |
| { # old v-table is NOT copied by old applications |
| %{$CompatProblems{$Level}{$Symbol}{"Overridden_Virtual_Method_B"}{$OverriddenMethod}}=( |
| "Type_Name"=>$AffectedClass_Name, |
| "Target"=>$OverriddenMethod, |
| "Old_Value"=>$Symbol, |
| "New_Value"=>$OverriddenMethod); |
| } |
| } |
| } |
| } |
| |
| if($Level eq "Binary" |
| and $In::Opt{"Target"} eq "windows") |
| { # register the reason of symbol name change |
| if(defined $CompSign{1}{$Symbol}{"Unmangled"} |
| and my $NewSym = getMangled_MSVC($CompSign{1}{$Symbol}{"Unmangled"}, 2)) |
| { |
| if($AddedInt{$Level}{$NewSym}) |
| { |
| if($CompSign{1}{$Symbol}{"Static"} ne $CompSign{2}{$NewSym}{"Static"}) |
| { |
| if($CompSign{2}{$NewSym}{"Static"}) |
| { |
| %{$CompatProblems{$Level}{$Symbol}{"Symbol_Became_Static"}{$Symbol}}=( |
| "Target"=>$Symbol, |
| "Old_Value"=>$Symbol, |
| "New_Value"=>$NewSym ); |
| } |
| else |
| { |
| %{$CompatProblems{$Level}{$Symbol}{"Symbol_Became_Non_Static"}{$Symbol}}=( |
| "Target"=>$Symbol, |
| "Old_Value"=>$Symbol, |
| "New_Value"=>$NewSym ); |
| } |
| } |
| if($CompSign{1}{$Symbol}{"Virt"} ne $CompSign{2}{$NewSym}{"Virt"}) |
| { |
| if($CompSign{2}{$NewSym}{"Virt"}) |
| { |
| %{$CompatProblems{$Level}{$Symbol}{"Symbol_Became_Virtual"}{$Symbol}}=( |
| "Target"=>$Symbol, |
| "Old_Value"=>$Symbol, |
| "New_Value"=>$NewSym ); |
| } |
| else |
| { |
| %{$CompatProblems{$Level}{$Symbol}{"Symbol_Became_Non_Virtual"}{$Symbol}}=( |
| "Target"=>$Symbol, |
| "Old_Value"=>$Symbol, |
| "New_Value"=>$NewSym ); |
| } |
| } |
| my $RTId1 = $CompSign{1}{$Symbol}{"Return"}; |
| my $RTId2 = $CompSign{2}{$NewSym}{"Return"}; |
| my $RTName1 = $TypeInfo{1}{$RTId1}{"Name"}; |
| my $RTName2 = $TypeInfo{2}{$RTId2}{"Name"}; |
| if($RTName1 ne $RTName2) |
| { |
| my $ProblemType = "Symbol_Changed_Return"; |
| if($CompSign{1}{$Symbol}{"Data"}) { |
| $ProblemType = "Global_Data_Symbol_Changed_Type"; |
| } |
| %{$CompatProblems{$Level}{$Symbol}{$ProblemType}{$Symbol}}=( |
| "Target"=>$Symbol, |
| "Old_Type"=>$RTName1, |
| "New_Type"=>$RTName2, |
| "Old_Value"=>$Symbol, |
| "New_Value"=>$NewSym ); |
| } |
| } |
| } |
| } |
| |
| if($Symbol=~/\A(_Z|\?)/) |
| { # C++ |
| my $Prefix = symbolPrefix($Symbol, 1); |
| if(my @Overloads = sort keys(%{$AddedOverloads{$Prefix}}) |
| and not $AddedOverloads{$Prefix}{getSignature($Symbol, 1, "Qual")}) |
| { # changed signature: params, "const"-qualifier |
| my $NewSym = $AddedOverloads{$Prefix}{$Overloads[0]}; |
| if($CompSign{1}{$Symbol}{"Constructor"}) |
| { |
| if($Symbol=~/(C[1-2][EI])/) |
| { |
| my $CtorType = $1; |
| $NewSym=~s/(C[1-2][EI])/$CtorType/g; |
| } |
| } |
| elsif($CompSign{1}{$Symbol}{"Destructor"}) |
| { |
| if($Symbol=~/(D[0-2][EI])/) |
| { |
| my $DtorType = $1; |
| $NewSym=~s/(D[0-2][EI])/$DtorType/g; |
| } |
| } |
| my $NS1 = $CompSign{1}{$Symbol}{"NameSpace"}; |
| my $NS2 = $CompSign{2}{$NewSym}{"NameSpace"}; |
| if((not $NS1 and not $NS2) or ($NS1 and $NS2 and $NS1 eq $NS2)) |
| { # from the same class and namespace |
| if($CompSign{1}{$Symbol}{"Const"} |
| and not $CompSign{2}{$NewSym}{"Const"}) |
| { # "const" to non-"const" |
| %{$CompatProblems{$Level}{$Symbol}{"Method_Became_Non_Const"}{$Symbol}}=( |
| "Type_Name"=>$TypeInfo{1}{$CompSign{1}{$Symbol}{"Class"}}{"Name"}, |
| "Target"=>$Symbol, |
| "New_Signature"=>$NewSym, |
| "Old_Value"=>$Symbol, |
| "New_Value"=>$NewSym ); |
| } |
| elsif(not $CompSign{1}{$Symbol}{"Const"} |
| and $CompSign{2}{$NewSym}{"Const"}) |
| { # non-"const" to "const" |
| %{$CompatProblems{$Level}{$Symbol}{"Method_Became_Const"}{$Symbol}}=( |
| "Target"=>$Symbol, |
| "New_Signature"=>$NewSym, |
| "Old_Value"=>$Symbol, |
| "New_Value"=>$NewSym ); |
| } |
| if($CompSign{1}{$Symbol}{"Volatile"} |
| and not $CompSign{2}{$NewSym}{"Volatile"}) |
| { # "volatile" to non-"volatile" |
| |
| %{$CompatProblems{$Level}{$Symbol}{"Method_Became_Non_Volatile"}{$Symbol}}=( |
| "Target"=>$Symbol, |
| "New_Signature"=>$NewSym, |
| "Old_Value"=>$Symbol, |
| "New_Value"=>$NewSym ); |
| } |
| elsif(not $CompSign{1}{$Symbol}{"Volatile"} |
| and $CompSign{2}{$NewSym}{"Volatile"}) |
| { # non-"volatile" to "volatile" |
| %{$CompatProblems{$Level}{$Symbol}{"Method_Became_Volatile"}{$Symbol}}=( |
| "Target"=>$Symbol, |
| "New_Signature"=>$NewSym, |
| "Old_Value"=>$Symbol, |
| "New_Value"=>$NewSym ); |
| } |
| if(getSignature($Symbol, 1, "Param") ne getSignature($NewSym, 2, "Param")) |
| { # params list |
| %{$CompatProblems{$Level}{$Symbol}{"Symbol_Changed_Parameters"}{$Symbol}}=( |
| "Target"=>$Symbol, |
| "New_Signature"=>$NewSym, |
| "Old_Value"=>$Symbol, |
| "New_Value"=>$NewSym ); |
| } |
| } |
| } |
| } |
| } |
| |
| foreach my $Symbol (sort keys(%{$CompSign{1}})) |
| { # checking symbols |
| my ($SN, $SS, $SV) = symbolParts($Symbol); |
| if($Level eq "Source") |
| { # remove symbol version |
| $Symbol = $SN; |
| } |
| else |
| { # Binary |
| if(not $SV) |
| { # symbol without version |
| if(my $VSym = $In::ABI{1}{"SymbolVersion"}{$Symbol}) |
| { # the symbol is linked with versioned symbol |
| if($CompSign{2}{$VSym}{"MnglName"}) |
| { # show report for symbol@ver only |
| next; |
| } |
| elsif(not linkSymbol($VSym, 2, "-Deps")) |
| { # changed version: sym@v1 to sym@v2 |
| # do NOT show report for symbol |
| next; |
| } |
| } |
| } |
| } |
| my $PSymbol = $Symbol; |
| if($Level eq "Source" |
| and my $S = $SourceReplacement{$Symbol}) |
| { # take a source-compatible replacement function |
| $PSymbol = $S; |
| } |
| if($CompSign{1}{$Symbol}{"Data"} |
| and not defined $CompSign{2}{$Symbol}) |
| { |
| if(isConstData($Symbol, 1)) |
| { |
| if(my $NonConstSymbol = getNonConstDataSym($Symbol, 1)) |
| { |
| if($CompSign{2}{$NonConstSymbol}) { |
| $PSymbol = $NonConstSymbol; |
| } |
| } |
| } |
| else |
| { |
| if(my $ConstSymbol = getConstDataSym($Symbol, 1)) |
| { |
| if($CompSign{2}{$ConstSymbol}) { |
| $PSymbol = $ConstSymbol; |
| } |
| } |
| } |
| } |
| |
| if($CompSign{1}{$Symbol}{"Private"}) |
| { # private symbols |
| next; |
| } |
| if(not defined $CompSign{1}{$Symbol} |
| or not defined $CompSign{2}{$PSymbol}) |
| { # no info |
| next; |
| } |
| if(not $CompSign{1}{$Symbol}{"MnglName"} |
| or not $CompSign{2}{$PSymbol}{"MnglName"}) |
| { # no mangled name |
| next; |
| } |
| if((not $CompSign{1}{$Symbol}{"Header"} and not $CompSign{1}{$Symbol}{"Source"}) |
| or (not $CompSign{2}{$PSymbol}{"Header"} and not $CompSign{2}{$PSymbol}{"Source"})) |
| { # without a header or source |
| next; |
| } |
| |
| if(not $CompSign{1}{$Symbol}{"PureVirt"} |
| and $CompSign{2}{$PSymbol}{"PureVirt"}) |
| { # became pure |
| next; |
| } |
| if($CompSign{1}{$Symbol}{"PureVirt"} |
| and not $CompSign{2}{$PSymbol}{"PureVirt"}) |
| { # became non-pure |
| next; |
| } |
| |
| if(not symbolFilter($Symbol, $CompSign{1}{$Symbol}, "Affected + InlineVirt", $Level, 1)) |
| { # exported, target, inline virtual and pure virtual |
| next; |
| } |
| |
| if($CompSign{1}{$Symbol}{"Data"} |
| and $CompSign{2}{$PSymbol}{"Data"}) |
| { |
| my $Value1 = $CompSign{1}{$Symbol}{"Value"}; |
| my $Value2 = $CompSign{2}{$PSymbol}{"Value"}; |
| if(defined $Value1) |
| { |
| $Value1 = showVal($Value1, $CompSign{1}{$Symbol}{"Return"}, 1); |
| if(defined $Value2) |
| { |
| $Value2 = showVal($Value2, $CompSign{2}{$PSymbol}{"Return"}, 2); |
| if($Value1 ne $Value2) |
| { |
| %{$CompatProblems{$Level}{$Symbol}{"Global_Data_Value_Changed"}{""}}=( |
| "Old_Value"=>$Value1, |
| "New_Value"=>$Value2, |
| "Target"=>$Symbol ); |
| } |
| } |
| } |
| } |
| |
| if($CompSign{2}{$PSymbol}{"Private"}) |
| { |
| %{$CompatProblems{$Level}{$Symbol}{getSymKind($Symbol, 1)."_Became_Private"}{""}}=( |
| "Target"=>$PSymbol ); |
| } |
| elsif(not $CompSign{1}{$Symbol}{"Protected"} |
| and $CompSign{2}{$PSymbol}{"Protected"}) |
| { |
| %{$CompatProblems{$Level}{$Symbol}{getSymKind($Symbol, 1)."_Became_Protected"}{""}}=( |
| "Target"=>$PSymbol ); |
| } |
| elsif($CompSign{1}{$Symbol}{"Protected"} |
| and not $CompSign{2}{$PSymbol}{"Protected"}) |
| { |
| %{$CompatProblems{$Level}{$Symbol}{getSymKind($Symbol, 1)."_Became_Public"}{""}}=( |
| "Target"=>$PSymbol ); |
| } |
| |
| # checking virtual table |
| mergeVirtualTables($Symbol, $Level); |
| |
| if($In::Opt{"CompileError"}) |
| { # if some errors occurred at the compiling stage |
| # then some false positives can be skipped here |
| if(not $CompSign{1}{$Symbol}{"Data"} and $CompSign{2}{$PSymbol}{"Data"} |
| and not $GlobalDataObject{2}{$Symbol}) |
| { # missed information about parameters in newer version |
| next; |
| } |
| if($CompSign{1}{$Symbol}{"Data"} and not $GlobalDataObject{1}{$Symbol} |
| and not $CompSign{2}{$PSymbol}{"Data"}) |
| { # missed information about parameters in older version |
| next; |
| } |
| } |
| my ($MnglName, $VersionSpec, $SymbolVersion) = symbolParts($Symbol); |
| |
| # check attributes |
| if($CompSign{2}{$PSymbol}{"Static"} |
| and not $CompSign{1}{$Symbol}{"Static"} and $Symbol=~/\A(_Z|\?)/) |
| { |
| %{$CompatProblems{$Level}{$Symbol}{"Method_Became_Static"}{""}}=( |
| "Target"=>$Symbol |
| ); |
| } |
| elsif(not $CompSign{2}{$PSymbol}{"Static"} |
| and $CompSign{1}{$Symbol}{"Static"} and $Symbol=~/\A(_Z|\?)/) |
| { |
| %{$CompatProblems{$Level}{$Symbol}{"Method_Became_Non_Static"}{""}}=( |
| "Target"=>$Symbol |
| ); |
| } |
| if(($CompSign{1}{$Symbol}{"Virt"} and $CompSign{2}{$PSymbol}{"Virt"}) |
| or ($CompSign{1}{$Symbol}{"PureVirt"} and $CompSign{2}{$PSymbol}{"PureVirt"})) |
| { # relative position of virtual and pure virtual methods |
| if($Level eq "Binary") |
| { |
| if(defined $CompSign{1}{$Symbol}{"RelPos"} and defined $CompSign{2}{$PSymbol}{"RelPos"} |
| and $CompSign{1}{$Symbol}{"RelPos"}!=$CompSign{2}{$PSymbol}{"RelPos"}) |
| { # top-level virtual methods only |
| my $Class_Id = $CompSign{1}{$Symbol}{"Class"}; |
| my $Class_Name = $TypeInfo{1}{$Class_Id}{"Name"}; |
| if(defined $VirtualTable{1}{$Class_Name} and defined $VirtualTable{2}{$Class_Name} |
| and $VirtualTable{1}{$Class_Name}{$Symbol}!=$VirtualTable{2}{$Class_Name}{$Symbol}) |
| { # check absolute position of a virtual method (including added and removed methods) |
| my %Class_Type = getType($Class_Id, 1); |
| my $ProblemType = "Virtual_Method_Position"; |
| if($CompSign{1}{$Symbol}{"PureVirt"}) { |
| $ProblemType = "Pure_Virtual_Method_Position"; |
| } |
| if(isUsedClass($Class_Id, 1, $Level)) |
| { |
| my @Affected = ($Symbol, keys(%{$OverriddenMethods{1}{$Symbol}})); |
| foreach my $ASymbol (@Affected) |
| { |
| if(not symbolFilter($ASymbol, $CompSign{1}{$ASymbol}, "Affected", $Level, 1)) { |
| next; |
| } |
| %{$CompatProblems{$Level}{$ASymbol}{$ProblemType}{$Symbol}}=( |
| "Type_Name"=>$Class_Type{"Name"}, |
| "Old_Value"=>$CompSign{1}{$Symbol}{"RelPos"}, |
| "New_Value"=>$CompSign{2}{$PSymbol}{"RelPos"}, |
| "Target"=>$Symbol); |
| } |
| $VTableChanged_M{$Class_Type{"Name"}} = 1; |
| } |
| } |
| } |
| } |
| } |
| if($CompSign{1}{$Symbol}{"PureVirt"} |
| or $CompSign{2}{$PSymbol}{"PureVirt"}) |
| { # do NOT check type changes in pure virtuals |
| next; |
| } |
| |
| $CheckedSymbols{$Level}{$Symbol} = 1; |
| |
| if($Symbol=~/\A(_Z|\?)/ |
| or keys(%{$CompSign{1}{$Symbol}{"Param"}})==keys(%{$CompSign{2}{$PSymbol}{"Param"}})) |
| { # C/C++: changes in parameters |
| foreach my $ParamPos (sort {$a<=>$b} keys(%{$CompSign{1}{$Symbol}{"Param"}})) |
| { # checking parameters |
| mergeParameters($Symbol, $PSymbol, $ParamPos, $ParamPos, $Level, 1); |
| } |
| } |
| else |
| { # C: added/removed parameters |
| foreach my $ParamPos (sort {$a<=>$b} keys(%{$CompSign{2}{$PSymbol}{"Param"}})) |
| { # checking added parameters |
| my $PType2_Id = $CompSign{2}{$PSymbol}{"Param"}{$ParamPos}{"type"}; |
| my $PType2_Name = $TypeInfo{2}{$PType2_Id}{"Name"}; |
| last if($PType2_Name eq "..."); |
| my $PName = $CompSign{2}{$PSymbol}{"Param"}{$ParamPos}{"name"}; |
| my $PName_Old = (defined $CompSign{1}{$Symbol}{"Param"}{$ParamPos})?$CompSign{1}{$Symbol}{"Param"}{$ParamPos}{"name"}:""; |
| my $ParamPos_Prev = "-1"; |
| if($PName=~/\Ap\d+\Z/i) |
| { # added unnamed parameter ( pN ) |
| my @Positions1 = findParamPairByTypeAndPos($PType2_Name, $ParamPos, "backward", $Symbol, 1); |
| my @Positions2 = findParamPairByTypeAndPos($PType2_Name, $ParamPos, "backward", $Symbol, 2); |
| if($#Positions1==-1 or $#Positions2>$#Positions1) { |
| $ParamPos_Prev = "lost"; |
| } |
| } |
| else { |
| $ParamPos_Prev = findParamPairByName($PName, $Symbol, 1); |
| } |
| if($ParamPos_Prev eq "lost") |
| { |
| if($ParamPos>keys(%{$CompSign{1}{$Symbol}{"Param"}})-1) |
| { |
| my $ProblemType = "Added_Parameter"; |
| if($PName=~/\Ap\d+\Z/) { |
| $ProblemType = "Added_Unnamed_Parameter"; |
| } |
| %{$CompatProblems{$Level}{$Symbol}{$ProblemType}{showPos($ParamPos)." Parameter"}}=( |
| "Target"=>$PName, |
| "Param_Pos"=>adjustParamPos($ParamPos, $Symbol, 2), |
| "Param_Type"=>$PType2_Name, |
| "New_Signature"=>$Symbol ); |
| } |
| else |
| { |
| my %ParamType_Pure = getPureType($PType2_Id, 2); |
| my $PairType_Id = $CompSign{1}{$Symbol}{"Param"}{$ParamPos}{"type"}; |
| my %PairType_Pure = getPureType($PairType_Id, 1); |
| if(($ParamType_Pure{"Name"} eq $PairType_Pure{"Name"} or $PType2_Name eq $TypeInfo{1}{$PairType_Id}{"Name"}) |
| and findParamPairByName($PName_Old, $Symbol, 2) eq "lost") |
| { |
| if($PName_Old!~/\Ap\d+\Z/ and $PName!~/\Ap\d+\Z/) |
| { |
| %{$CompatProblems{$Level}{$Symbol}{"Renamed_Parameter"}{showPos($ParamPos)." Parameter"}}=( |
| "Target"=>$PName_Old, |
| "Param_Pos"=>adjustParamPos($ParamPos, $Symbol, 2), |
| "Param_Type"=>$PType2_Name, |
| "Old_Value"=>$PName_Old, |
| "New_Value"=>$PName, |
| "New_Signature"=>$Symbol ); |
| } |
| } |
| else |
| { |
| my $ProblemType = "Added_Middle_Parameter"; |
| if($PName=~/\Ap\d+\Z/) { |
| $ProblemType = "Added_Middle_Unnamed_Parameter"; |
| } |
| %{$CompatProblems{$Level}{$Symbol}{$ProblemType}{showPos($ParamPos)." Parameter"}}=( |
| "Target"=>$PName, |
| "Param_Pos"=>adjustParamPos($ParamPos, $Symbol, 2), |
| "Param_Type"=>$PType2_Name, |
| "New_Signature"=>$Symbol ); |
| } |
| } |
| } |
| } |
| foreach my $ParamPos (sort {$a<=>$b} keys(%{$CompSign{1}{$Symbol}{"Param"}})) |
| { # check relevant parameters |
| my $PType1_Id = $CompSign{1}{$Symbol}{"Param"}{$ParamPos}{"type"}; |
| my $ParamName1 = $CompSign{1}{$Symbol}{"Param"}{$ParamPos}{"name"}; |
| # FIXME: find relevant parameter by name |
| if(defined $CompSign{2}{$PSymbol}{"Param"}{$ParamPos}) |
| { |
| my $PType2_Id = $CompSign{2}{$PSymbol}{"Param"}{$ParamPos}{"type"}; |
| my $ParamName2 = $CompSign{2}{$PSymbol}{"Param"}{$ParamPos}{"name"}; |
| if($TypeInfo{1}{$PType1_Id}{"Name"} eq $TypeInfo{2}{$PType2_Id}{"Name"} |
| or ($ParamName1!~/\Ap\d+\Z/i and $ParamName1 eq $ParamName2)) { |
| mergeParameters($Symbol, $PSymbol, $ParamPos, $ParamPos, $Level, 0); |
| } |
| } |
| } |
| foreach my $ParamPos (sort {$a<=>$b} keys(%{$CompSign{1}{$Symbol}{"Param"}})) |
| { # checking removed parameters |
| my $PType1_Id = $CompSign{1}{$Symbol}{"Param"}{$ParamPos}{"type"}; |
| my $PType1_Name = $TypeInfo{1}{$PType1_Id}{"Name"}; |
| last if($PType1_Name eq "..."); |
| my $PName = $CompSign{1}{$Symbol}{"Param"}{$ParamPos}{"name"}; |
| my $PName_New = (defined $CompSign{2}{$PSymbol}{"Param"}{$ParamPos})?$CompSign{2}{$PSymbol}{"Param"}{$ParamPos}{"name"}:""; |
| my $ParamPos_New = "-1"; |
| if($PName=~/\Ap\d+\Z/i) |
| { # removed unnamed parameter ( pN ) |
| my @Positions1 = findParamPairByTypeAndPos($PType1_Name, $ParamPos, "forward", $Symbol, 1); |
| my @Positions2 = findParamPairByTypeAndPos($PType1_Name, $ParamPos, "forward", $Symbol, 2); |
| if($#Positions2==-1 or $#Positions2<$#Positions1) { |
| $ParamPos_New = "lost"; |
| } |
| } |
| else { |
| $ParamPos_New = findParamPairByName($PName, $Symbol, 2); |
| } |
| if($ParamPos_New eq "lost") |
| { |
| if($ParamPos>keys(%{$CompSign{2}{$PSymbol}{"Param"}})-1) |
| { |
| my $ProblemType = "Removed_Parameter"; |
| if($PName=~/\Ap\d+\Z/) { |
| $ProblemType = "Removed_Unnamed_Parameter"; |
| } |
| %{$CompatProblems{$Level}{$Symbol}{$ProblemType}{showPos($ParamPos)." Parameter"}}=( |
| "Target"=>$PName, |
| "Param_Pos"=>adjustParamPos($ParamPos, $Symbol, 2), |
| "Param_Type"=>$PType1_Name, |
| "New_Signature"=>$Symbol); |
| } |
| elsif($ParamPos<keys(%{$CompSign{1}{$Symbol}{"Param"}})-1) |
| { |
| my %ParamType_Pure = getPureType($PType1_Id, 1); |
| my $PairType_Id = $CompSign{2}{$PSymbol}{"Param"}{$ParamPos}{"type"}; |
| my %PairType_Pure = getPureType($PairType_Id, 2); |
| if(($ParamType_Pure{"Name"} eq $PairType_Pure{"Name"} or $PType1_Name eq $TypeInfo{2}{$PairType_Id}{"Name"}) |
| and findParamPairByName($PName_New, $Symbol, 1) eq "lost") |
| { |
| if($PName_New!~/\Ap\d+\Z/ and $PName!~/\Ap\d+\Z/) |
| { |
| %{$CompatProblems{$Level}{$Symbol}{"Renamed_Parameter"}{showPos($ParamPos)." Parameter"}}=( |
| "Target"=>$PName, |
| "Param_Pos"=>adjustParamPos($ParamPos, $Symbol, 2), |
| "Param_Type"=>$PType1_Name, |
| "Old_Value"=>$PName, |
| "New_Value"=>$PName_New, |
| "New_Signature"=>$Symbol); |
| } |
| } |
| else |
| { |
| my $ProblemType = "Removed_Middle_Parameter"; |
| if($PName=~/\Ap\d+\Z/) { |
| $ProblemType = "Removed_Middle_Unnamed_Parameter"; |
| } |
| %{$CompatProblems{$Level}{$Symbol}{$ProblemType}{showPos($ParamPos)." Parameter"}}=( |
| "Target"=>$PName, |
| "Param_Pos"=>adjustParamPos($ParamPos, $Symbol, 2), |
| "Param_Type"=>$PType1_Name, |
| "New_Signature"=>$Symbol); |
| } |
| } |
| } |
| } |
| } |
| # checking return type |
| my $ReturnType1_Id = $CompSign{1}{$Symbol}{"Return"}; |
| my $ReturnType2_Id = $CompSign{2}{$PSymbol}{"Return"}; |
| my %RC_SubProblems = detectTypeChange($ReturnType1_Id, $ReturnType2_Id, "Return", $Level); |
| |
| foreach my $SubProblemType (keys(%RC_SubProblems)) |
| { |
| my $New_Value = $RC_SubProblems{$SubProblemType}{"New_Value"}; |
| my $Old_Value = $RC_SubProblems{$SubProblemType}{"Old_Value"}; |
| my %ProblemTypes = (); |
| |
| if($CompSign{1}{$Symbol}{"Data"}) |
| { |
| if($SubProblemType eq "Return_Type_And_Size") { |
| $ProblemTypes{"Global_Data_Type_And_Size"} = 1; |
| } |
| elsif($SubProblemType eq "Return_Type_Format") { |
| $ProblemTypes{"Global_Data_Type_Format"} = 1; |
| } |
| else { |
| $ProblemTypes{"Global_Data_Type"} = 1; |
| } |
| |
| # quals |
| if($SubProblemType eq "Return_Type" |
| or $SubProblemType eq "Return_Type_And_Size" |
| or $SubProblemType eq "Return_Type_Format") |
| { |
| if(my $RR = removedQual($Old_Value, $New_Value, "const")) |
| { # const to non-const |
| if($RR==2) { |
| $ProblemTypes{"Global_Data_Removed_Const"} = 1; |
| } |
| else { |
| $ProblemTypes{"Global_Data_Became_Non_Const"} = 1; |
| } |
| $ProblemTypes{"Global_Data_Type"} = 1; |
| } |
| elsif(my $RA = addedQual($Old_Value, $New_Value, "const")) |
| { # non-const to const |
| if($RA==2) { |
| $ProblemTypes{"Global_Data_Added_Const"} = 1; |
| } |
| else { |
| $ProblemTypes{"Global_Data_Became_Const"} = 1; |
| } |
| $ProblemTypes{"Global_Data_Type"} = 1; |
| } |
| } |
| } |
| else |
| { |
| # quals |
| if($SubProblemType eq "Return_Type" |
| or $SubProblemType eq "Return_Type_And_Size" |
| or $SubProblemType eq "Return_Type_Format") |
| { |
| if(addedQual($Old_Value, $New_Value, "volatile")) |
| { |
| $ProblemTypes{"Return_Value_Became_Volatile"} = 1; |
| if($Level ne "Source" |
| or not cmpBTypes($Old_Value, $New_Value, 1, 2)) { |
| $ProblemTypes{"Return_Type"} = 1; |
| } |
| } |
| |
| if(my $RA = addedQual($Old_Value, $New_Value, "const")) |
| { |
| if($RA==2) { |
| $ProblemTypes{"Return_Type_Added_Const"} = 1; |
| } |
| else { |
| $ProblemTypes{"Return_Type_Became_Const"} = 1; |
| } |
| if($Level ne "Source" |
| or not cmpBTypes($Old_Value, $New_Value, 1, 2)) { |
| $ProblemTypes{"Return_Type"} = 1; |
| } |
| } |
| } |
| } |
| if($Level eq "Binary" |
| and not $CompSign{1}{$Symbol}{"Data"}) |
| { |
| my (%Conv1, %Conv2) = (); |
| |
| if($UseConv_Real{1}{"R"} and $UseConv_Real{2}{"R"}) |
| { |
| %Conv1 = callingConvention_R_Real($CompSign{1}{$Symbol}); |
| %Conv2 = callingConvention_R_Real($CompSign{2}{$PSymbol}); |
| } |
| else |
| { |
| %Conv1 = callingConvention_R_Model($CompSign{1}{$Symbol}, 1); |
| %Conv2 = callingConvention_R_Model($CompSign{2}{$PSymbol}, 2); |
| } |
| |
| if($SubProblemType eq "Return_Type_Became_Void") |
| { |
| if(keys(%{$CompSign{1}{$Symbol}{"Param"}})) |
| { # parameters stack has been affected |
| if($Conv1{"Method"} eq "stack") { |
| $ProblemTypes{"Return_Type_Became_Void_And_Stack_Layout"} = 1; |
| } |
| elsif($Conv1{"Hidden"}) { |
| $ProblemTypes{"Return_Type_Became_Void_And_Register"} = 1; |
| } |
| } |
| } |
| elsif($SubProblemType eq "Return_Type_From_Void") |
| { |
| if(keys(%{$CompSign{1}{$Symbol}{"Param"}})) |
| { # parameters stack has been affected |
| if($Conv2{"Method"} eq "stack") { |
| $ProblemTypes{"Return_Type_From_Void_And_Stack_Layout"} = 1; |
| } |
| elsif($Conv2{"Hidden"}) { |
| $ProblemTypes{"Return_Type_From_Void_And_Register"} = 1; |
| } |
| } |
| } |
| elsif($SubProblemType eq "Return_Type" |
| or $SubProblemType eq "Return_Type_And_Size" |
| or $SubProblemType eq "Return_Type_Format") |
| { |
| if($Conv1{"Method"} ne $Conv2{"Method"}) |
| { |
| if($Conv1{"Method"} eq "stack") |
| { # returns in a register instead of a hidden first parameter |
| $ProblemTypes{"Return_Type_From_Stack_To_Register"} = 1; |
| } |
| else { |
| $ProblemTypes{"Return_Type_From_Register_To_Stack"} = 1; |
| } |
| } |
| else |
| { |
| if($Conv1{"Method"} eq "reg") |
| { |
| if($Conv1{"Registers"} ne $Conv2{"Registers"}) |
| { |
| if($Conv1{"Hidden"}) { |
| $ProblemTypes{"Return_Type_And_Register_Was_Hidden_Parameter"} = 1; |
| } |
| elsif($Conv2{"Hidden"}) { |
| $ProblemTypes{"Return_Type_And_Register_Became_Hidden_Parameter"} = 1; |
| } |
| else { |
| $ProblemTypes{"Return_Type_And_Register"} = 1; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| if(not keys(%ProblemTypes)) |
| { # default |
| $ProblemTypes{$SubProblemType} = 1; |
| } |
| |
| foreach my $ProblemType (keys(%ProblemTypes)) |
| { # additional |
| $CompatProblems{$Level}{$Symbol}{$ProblemType}{"retval"} = $RC_SubProblems{$SubProblemType}; |
| } |
| } |
| if($ReturnType1_Id and $ReturnType2_Id) |
| { |
| @RecurTypes = (); |
| my $Sub_SubProblems = mergeTypes($ReturnType1_Id, $ReturnType2_Id, $Level); |
| |
| my $AddProblems = {}; |
| |
| if($CompSign{1}{$Symbol}{"Data"}) |
| { |
| if($Level eq "Binary") |
| { |
| if(getPLevel($ReturnType1_Id, 1)==0) |
| { |
| if(defined $Sub_SubProblems->{"DataType_Size"}) |
| { # add "Global_Data_Size" problem |
| |
| foreach my $Loc (keys(%{$Sub_SubProblems->{"DataType_Size"}})) |
| { |
| if(index($Loc,"->")==-1) |
| { |
| if($Loc eq $Sub_SubProblems->{"DataType_Size"}{$Loc}{"Type_Name"}) |
| { |
| $AddProblems->{"Global_Data_Size"}{$Loc} = $Sub_SubProblems->{"DataType_Size"}{$Loc}; # add a new problem |
| last; |
| } |
| } |
| } |
| } |
| } |
| if(not defined $AddProblems->{"Global_Data_Size"}) |
| { |
| if(defined $GlobalDataObject{1}{$Symbol} |
| and defined $GlobalDataObject{2}{$Symbol}) |
| { |
| my $Old_Size = $GlobalDataObject{1}{$Symbol}; |
| my $New_Size = $GlobalDataObject{2}{$Symbol}; |
| if($Old_Size!=$New_Size) |
| { |
| $AddProblems->{"Global_Data_Size"}{"retval"} = { |
| "Old_Size"=>$Old_Size*$BYTE, |
| "New_Size"=>$New_Size*$BYTE }; |
| } |
| } |
| } |
| } |
| } |
| |
| foreach my $SubProblemType (keys(%{$AddProblems})) |
| { |
| foreach my $SubLocation (keys(%{$AddProblems->{$SubProblemType}})) |
| { |
| my $NewLocation = "retval"; |
| if($SubLocation and $SubLocation ne "retval") { |
| $NewLocation = "retval->".$SubLocation; |
| } |
| $CompatProblems{$Level}{$Symbol}{$SubProblemType}{$NewLocation} = $AddProblems->{$SubProblemType}{$SubLocation}; |
| } |
| } |
| |
| foreach my $SubProblemType (keys(%{$Sub_SubProblems})) |
| { |
| foreach my $SubLocation (keys(%{$Sub_SubProblems->{$SubProblemType}})) |
| { |
| my $NewLocation = "retval"; |
| if($SubLocation and $SubLocation ne "retval") { |
| $NewLocation = "retval->".$SubLocation; |
| } |
| $CompatProblems{$Level}{$Symbol}{$SubProblemType}{$NewLocation} = $Sub_SubProblems->{$SubProblemType}{$SubLocation}; |
| } |
| } |
| } |
| |
| # checking object type |
| my $ObjTId1 = $CompSign{1}{$Symbol}{"Class"}; |
| my $ObjTId2 = $CompSign{2}{$PSymbol}{"Class"}; |
| if($ObjTId1 and $ObjTId2 |
| and not $CompSign{1}{$Symbol}{"Static"} |
| and not $CompSign{1}{$Symbol}{"Data"}) |
| { |
| my ($ThisPtr1, $ThisPtr2) = (undef, undef); |
| if($CompSign{1}{$Symbol}{"Const"}) |
| { |
| $ThisPtr1 = getTypeIdByName($TypeInfo{1}{$ObjTId1}{"Name"}." const*const", 1); |
| $ThisPtr2 = getTypeIdByName($TypeInfo{2}{$ObjTId2}{"Name"}." const*const", 2); |
| } |
| else |
| { |
| $ThisPtr1 = getTypeIdByName($TypeInfo{1}{$ObjTId1}{"Name"}."*const", 1); |
| $ThisPtr2 = getTypeIdByName($TypeInfo{2}{$ObjTId2}{"Name"}."*const", 2); |
| } |
| |
| if($ThisPtr1 and $ThisPtr2) |
| { |
| @RecurTypes = (); |
| my $Sub_SubProblems = mergeTypes($ThisPtr1, $ThisPtr2, $Level); |
| foreach my $SubProblemType (keys(%{$Sub_SubProblems})) |
| { |
| foreach my $SubLocation (keys(%{$Sub_SubProblems->{$SubProblemType}})) |
| { |
| my $NewLocation = ($SubLocation)?"this->".$SubLocation:"this"; |
| $CompatProblems{$Level}{$Symbol}{$SubProblemType}{$NewLocation} = $Sub_SubProblems->{$SubProblemType}{$SubLocation}; |
| } |
| } |
| } |
| } |
| } |
| |
| if($Level eq "Binary") { |
| mergeVTables($Level); |
| } |
| |
| # mark all affected symbols as "checked" |
| foreach my $Symbol (keys(%{$CompatProblems{$Level}})) { |
| $CheckedSymbols{$Level}{$Symbol} = 1; |
| } |
| } |
| |
| sub rmQuals($$) |
| { |
| my ($Value, $Qual) = @_; |
| if(not $Qual) { |
| return $Value; |
| } |
| if($Qual eq "all") |
| { # all quals |
| $Qual = "const|volatile|restrict"; |
| } |
| while($Value=~s/\b$Qual\b//) { |
| $Value = formatName($Value, "T"); |
| } |
| return $Value; |
| } |
| |
| sub cmpBTypes($$$$) |
| { |
| my ($T1, $T2, $V1, $V2) = @_; |
| $T1 = uncoverTypedefs($T1, $V1); |
| $T2 = uncoverTypedefs($T2, $V2); |
| return (rmQuals($T1, "all") eq rmQuals($T2, "all")); |
| } |
| |
| sub addedQual($$$) |
| { |
| my ($Old_Value, $New_Value, $Qual) = @_; |
| return removedQual_I($New_Value, $Old_Value, 2, 1, $Qual); |
| } |
| |
| sub removedQual($$$) |
| { |
| my ($Old_Value, $New_Value, $Qual) = @_; |
| return removedQual_I($Old_Value, $New_Value, 1, 2, $Qual); |
| } |
| |
| sub removedQual_I($$$$$) |
| { |
| my ($Old_Value, $New_Value, $V1, $V2, $Qual) = @_; |
| $Old_Value = uncoverTypedefs($Old_Value, $V1); |
| $New_Value = uncoverTypedefs($New_Value, $V2); |
| |
| if($Old_Value eq $New_Value) |
| { # equal types |
| return 0; |
| } |
| if($Old_Value!~/\b$Qual\b/) |
| { # without a qual |
| return 0; |
| } |
| elsif($New_Value!~/\b$Qual\b/) |
| { # became non-qual |
| return 1; |
| } |
| else |
| { |
| my @BQ1 = getQualModel($Old_Value, $Qual); |
| my @BQ2 = getQualModel($New_Value, $Qual); |
| foreach (0 .. $#BQ1) |
| { # removed qual |
| if($BQ1[$_]==1 |
| and $BQ2[$_]!=1) |
| { |
| return 2; |
| } |
| } |
| } |
| return 0; |
| } |
| |
| sub getQualModel($$) |
| { |
| my ($Value, $Qual) = @_; |
| if(not $Qual) { |
| return $Value; |
| } |
| |
| # cleaning |
| while($Value=~/(\w+)/) |
| { |
| my $W = $1; |
| |
| if($W eq $Qual) { |
| $Value=~s/\b$W\b/\@/g; |
| } |
| else { |
| $Value=~s/\b$W\b//g; |
| } |
| } |
| |
| $Value=~s/\@/$Qual/g; |
| $Value=~s/[^\*\&\w]+//g; |
| |
| # modeling |
| # int*const*const == 011 |
| # int**const == 001 |
| my @Model = (); |
| my @Elems = split(/[\*\&]/, $Value); |
| if(not @Elems) { |
| return (0); |
| } |
| foreach (@Elems) |
| { |
| if($_ eq $Qual) { |
| push(@Model, 1); |
| } |
| else { |
| push(@Model, 0); |
| } |
| } |
| |
| return @Model; |
| } |
| |
| my %StringTypes = map {$_=>1} ( |
| "char*", |
| "char const*" |
| ); |
| |
| my %CharTypes = map {$_=>1} ( |
| "char", |
| "char const" |
| ); |
| |
| sub showVal($$$) |
| { |
| my ($Value, $TypeId, $LVer) = @_; |
| my %PureType = getPureType($TypeId, $LVer); |
| my $TName = uncoverTypedefs($PureType{"Name"}, $LVer); |
| |
| if(defined $StringTypes{$TName} or $TName=~/string/i) |
| { # strings |
| return "\"$Value\""; |
| } |
| elsif(defined $CharTypes{$TName}) |
| { # characters |
| return "\'$Value\'"; |
| } |
| if($Value eq "") |
| { # other |
| return "\'\'"; |
| } |
| |
| return $Value; |
| } |
| |
| sub getRegs($$$) |
| { |
| my ($LVer, $Symbol, $Pos) = @_; |
| |
| if(defined $CompSign{$LVer}{$Symbol}{"Reg"}) |
| { |
| my %Regs = (); |
| foreach my $Elem (sort keys(%{$CompSign{$LVer}{$Symbol}{"Reg"}})) |
| { |
| if(index($Elem, $Pos)==0 |
| and $Elem=~/\A$Pos([\.\+]|\Z)/) { |
| $Regs{$CompSign{$LVer}{$Symbol}{"Reg"}{$Elem}} = 1; |
| } |
| } |
| |
| return join(", ", sort keys(%Regs)); |
| } |
| elsif(defined $CompSign{$LVer}{$Symbol}{"Param"} |
| and defined $CompSign{$LVer}{$Symbol}{"Param"}{0} |
| and not defined $CompSign{$LVer}{$Symbol}{"Param"}{0}{"offset"}) |
| { |
| return "unknown"; |
| } |
| |
| return undef; |
| } |
| |
| sub mergeParameters($$$$$$) |
| { |
| my ($Symbol, $PSymbol, $ParamPos1, $ParamPos2, $Level, $CheckRenamed) = @_; |
| |
| my $PTid1 = $CompSign{1}{$Symbol}{"Param"}{$ParamPos1}{"type"}; |
| my $PTid2 = $CompSign{2}{$PSymbol}{"Param"}{$ParamPos2}{"type"}; |
| |
| if(not $PTid1 |
| or not $PTid2) { |
| return; |
| } |
| |
| my $PName1 = $CompSign{1}{$Symbol}{"Param"}{$ParamPos1}{"name"}; |
| my $PName2 = $CompSign{2}{$PSymbol}{"Param"}{$ParamPos2}{"name"}; |
| |
| if(index($Symbol, "_Z")==0 |
| or index($Symbol, "?")==0) |
| { # do not merge "this" |
| if($PName1 eq "this" or $PName2 eq "this") { |
| return; |
| } |
| } |
| |
| my %Type1 = getType($PTid1, 1); |
| my %Type2 = getType($PTid2, 2); |
| |
| my %PureType1 = getPureType($PTid1, 1); |
| |
| my %BaseType1 = getBaseType($PTid1, 1); |
| my %BaseType2 = getBaseType($PTid2, 2); |
| |
| my $ParamLoc = ($PName1)?$PName1:showPos($ParamPos1)." Parameter"; |
| |
| if($Level eq "Binary") |
| { |
| if($CompSign{1}{$Symbol}{"Param"}{$ParamPos1}{"reg"} |
| and not $CompSign{2}{$PSymbol}{"Param"}{$ParamPos2}{"reg"}) |
| { |
| %{$CompatProblems{$Level}{$Symbol}{"Parameter_Became_Non_Register"}{$ParamLoc}}=( |
| "Target"=>$PName1, |
| "Param_Pos"=>adjustParamPos($ParamPos1, $Symbol, 1) ); |
| } |
| elsif(not $CompSign{1}{$Symbol}{"Param"}{$ParamPos1}{"reg"} |
| and $CompSign{2}{$PSymbol}{"Param"}{$ParamPos2}{"reg"}) |
| { |
| %{$CompatProblems{$Level}{$Symbol}{"Parameter_Became_Register"}{$ParamLoc}}=( |
| "Target"=>$PName1, |
| "Param_Pos"=>adjustParamPos($ParamPos1, $Symbol, 1) ); |
| } |
| |
| if(defined $UsedDump{1}{"DWARF"} |
| and defined $UsedDump{2}{"DWARF"}) |
| { |
| my $Old_Regs = getRegs(1, $Symbol, $ParamPos1); |
| my $New_Regs = getRegs(2, $PSymbol, $ParamPos2); |
| |
| my $Old_Offset = $CompSign{1}{$Symbol}{"Param"}{$ParamPos1}{"offset"}; |
| my $New_Offset = $CompSign{2}{$PSymbol}{"Param"}{$ParamPos2}{"offset"}; |
| |
| if($Old_Regs ne "unknown" |
| and $New_Regs ne "unknown") |
| { |
| if($Old_Regs and $New_Regs) |
| { |
| if($Old_Regs ne $New_Regs) |
| { |
| %{$CompatProblems{$Level}{$Symbol}{"Parameter_Changed_Register"}{$ParamLoc}}=( |
| "Target"=>$PName1, |
| "Param_Pos"=>adjustParamPos($ParamPos1, $Symbol, 1), |
| "Old_Value"=>$Old_Regs, |
| "New_Value"=>$New_Regs ); |
| } |
| } |
| elsif($Old_Regs and not $New_Regs and $New_Offset ne "") |
| { |
| %{$CompatProblems{$Level}{$Symbol}{"Parameter_From_Register"}{$ParamLoc}}=( |
| "Target"=>$PName1, |
| "Param_Pos"=>adjustParamPos($ParamPos1, $Symbol, 1), |
| "Old_Value"=>$Old_Regs ); |
| } |
| elsif(not $Old_Regs and $Old_Offset ne "" and $New_Regs) |
| { |
| %{$CompatProblems{$Level}{$Symbol}{"Parameter_To_Register"}{$ParamLoc}}=( |
| "Target"=>$PName1, |
| "Param_Pos"=>adjustParamPos($ParamPos1, $Symbol, 1), |
| "New_Value"=>$New_Regs ); |
| } |
| } |
| |
| if($Old_Offset ne "" |
| and $New_Offset ne "") |
| { |
| if($Old_Offset ne $New_Offset) |
| { |
| my $Start1 = $CompSign{1}{$Symbol}{"Param"}{0}{"offset"}; |
| my $Start2 = $CompSign{2}{$PSymbol}{"Param"}{0}{"offset"}; |
| |
| $Old_Offset = $Old_Offset - $Start1; |
| $New_Offset = $New_Offset - $Start2; |
| |
| if($Old_Offset ne $New_Offset) |
| { |
| %{$CompatProblems{$Level}{$Symbol}{"Parameter_Changed_Offset"}{$ParamLoc}}=( |
| "Target"=>$PName1, |
| "Param_Pos"=>adjustParamPos($ParamPos1, $Symbol, 1), |
| "Old_Value"=>$Old_Offset, |
| "New_Value"=>$New_Offset ); |
| } |
| } |
| } |
| } |
| } |
| |
| my $Value_Old = $CompSign{1}{$Symbol}{"Param"}{$ParamPos1}{"default"}; |
| my $Value_New = $CompSign{2}{$PSymbol}{"Param"}{$ParamPos2}{"default"}; |
| |
| if(defined $Value_Old) |
| { |
| $Value_Old = showVal($Value_Old, $PTid1, 1); |
| if(defined $Value_New) |
| { |
| $Value_New = showVal($Value_New, $PTid2, 2); |
| if($Value_Old ne $Value_New) |
| { # FIXME: how to distinguish "0" and 0 (NULL) |
| %{$CompatProblems{$Level}{$Symbol}{"Parameter_Default_Value_Changed"}{$ParamLoc}}=( |
| "Target"=>$PName1, |
| "Param_Pos"=>adjustParamPos($ParamPos1, $Symbol, 1), |
| "Old_Value"=>$Value_Old, |
| "New_Value"=>$Value_New ); |
| } |
| } |
| else |
| { |
| %{$CompatProblems{$Level}{$Symbol}{"Parameter_Default_Value_Removed"}{$ParamLoc}}=( |
| "Target"=>$PName1, |
| "Param_Pos"=>adjustParamPos($ParamPos1, $Symbol, 1), |
| "Old_Value"=>$Value_Old ); |
| } |
| } |
| elsif(defined $Value_New) |
| { |
| $Value_New = showVal($Value_New, $PTid2, 2); |
| %{$CompatProblems{$Level}{$Symbol}{"Parameter_Default_Value_Added"}{$ParamLoc}}=( |
| "Target"=>$PName1, |
| "Param_Pos"=>adjustParamPos($ParamPos1, $Symbol, 1), |
| "New_Value"=>$Value_New ); |
| } |
| |
| if($CheckRenamed) |
| { |
| if($PName1 and $PName2 and $PName1 ne $PName2 |
| and $PTid1!=-1 and $PTid2!=-1 |
| and $PName1!~/\Ap\d+\Z/ and $PName2!~/\Ap\d+\Z/) |
| { # except unnamed "..." value list (Id=-1) |
| %{$CompatProblems{$Level}{$Symbol}{"Renamed_Parameter"}{showPos($ParamPos1)." Parameter"}}=( |
| "Target"=>$PName1, |
| "Param_Pos"=>adjustParamPos($ParamPos1, $Symbol, 1), |
| "Param_Type"=>$TypeInfo{1}{$PTid1}{"Name"}, |
| "Old_Value"=>$PName1, |
| "New_Value"=>$PName2, |
| "New_Signature"=>$Symbol); |
| } |
| } |
| |
| # checking type change (replace) |
| my %SubProblems = detectTypeChange($PTid1, $PTid2, "Parameter", $Level); |
| |
| foreach my $SubProblemType (keys(%SubProblems)) |
| { # add new problems, remove false alarms |
| my $New_Value = $SubProblems{$SubProblemType}{"New_Value"}; |
| my $Old_Value = $SubProblems{$SubProblemType}{"Old_Value"}; |
| |
| # quals |
| if($SubProblemType eq "Parameter_Type" |
| or $SubProblemType eq "Parameter_Type_And_Size" |
| or $SubProblemType eq "Parameter_Type_Format") |
| { |
| if(addedQual($Old_Value, $New_Value, "restrict")) { |
| %{$SubProblems{"Parameter_Became_Restrict"}} = %{$SubProblems{$SubProblemType}}; |
| } |
| elsif(removedQual($Old_Value, $New_Value, "restrict")) { |
| %{$SubProblems{"Parameter_Became_Non_Restrict"}} = %{$SubProblems{$SubProblemType}}; |
| } |
| |
| if(removedQual($Old_Value, $New_Value, "volatile")) { |
| %{$SubProblems{"Parameter_Became_Non_Volatile"}} = %{$SubProblems{$SubProblemType}}; |
| } |
| |
| if($Type2{"Type"} eq "Const" and $BaseType2{"Name"} eq $Type1{"Name"} |
| and $Type1{"Type"}=~/Intrinsic|Class|Struct|Union|Enum/) |
| { # int to "int const" |
| delete($SubProblems{$SubProblemType}); |
| } |
| elsif($Type1{"Type"} eq "Const" and $BaseType1{"Name"} eq $Type2{"Name"} |
| and $Type2{"Type"}=~/Intrinsic|Class|Struct|Union|Enum/) |
| { # "int const" to int |
| delete($SubProblems{$SubProblemType}); |
| } |
| elsif(my $RR = removedQual($Old_Value, $New_Value, "const")) |
| { # "const" to non-"const" |
| if($RR==2) { |
| %{$SubProblems{"Parameter_Removed_Const"}} = %{$SubProblems{$SubProblemType}}; |
| } |
| else { |
| %{$SubProblems{"Parameter_Became_Non_Const"}} = %{$SubProblems{$SubProblemType}}; |
| } |
| } |
| } |
| } |
| |
| if($Level eq "Source") |
| { |
| foreach my $SubProblemType (keys(%SubProblems)) |
| { |
| my $New_Value = $SubProblems{$SubProblemType}{"New_Value"}; |
| my $Old_Value = $SubProblems{$SubProblemType}{"Old_Value"}; |
| |
| if($SubProblemType eq "Parameter_Type") |
| { |
| if(cmpBTypes($Old_Value, $New_Value, 1, 2)) { |
| delete($SubProblems{$SubProblemType}); |
| } |
| } |
| } |
| } |
| |
| foreach my $SubProblemType (keys(%SubProblems)) |
| { # modify/register problems |
| my $New_Value = $SubProblems{$SubProblemType}{"New_Value"}; |
| my $Old_Value = $SubProblems{$SubProblemType}{"Old_Value"}; |
| my $New_Size = $SubProblems{$SubProblemType}{"New_Size"}; |
| my $Old_Size = $SubProblems{$SubProblemType}{"Old_Size"}; |
| |
| my $NewProblemType = $SubProblemType; |
| if($Old_Value eq "..." and $New_Value ne "...") |
| { # change from "..." to "int" |
| if($ParamPos1==0) |
| { # ISO C requires a named argument before "..." |
| next; |
| } |
| $NewProblemType = "Parameter_Became_Non_VaList"; |
| } |
| elsif($New_Value eq "..." and $Old_Value ne "...") |
| { # change from "int" to "..." |
| if($ParamPos2==0) |
| { # ISO C requires a named argument before "..." |
| next; |
| } |
| $NewProblemType = "Parameter_Became_VaList"; |
| } |
| elsif($Level eq "Binary" and ($SubProblemType eq "Parameter_Type_And_Size" |
| or $SubProblemType eq "Parameter_Type" or $SubProblemType eq "Parameter_Type_Format")) |
| { |
| my (%Conv1, %Conv2) = (); |
| |
| if($UseConv_Real{1}{"P"} and $UseConv_Real{2}{"P"}) |
| { # real |
| %Conv1 = callingConvention_P_Real($CompSign{1}{$Symbol}, $ParamPos1); |
| %Conv2 = callingConvention_P_Real($CompSign{2}{$PSymbol}, $ParamPos2); |
| } |
| else |
| { # model |
| %Conv1 = callingConvention_P_Model($CompSign{1}{$Symbol}, $ParamPos1, 1); |
| %Conv2 = callingConvention_P_Model($CompSign{2}{$PSymbol}, $ParamPos2, 2); |
| } |
| |
| my $ArrayType = ($Type1{"Type"} eq "Array" and $Type2{"Type"} eq "Array"); # Fortran |
| |
| if($Conv1{"Method"} eq $Conv2{"Method"}) |
| { |
| if($Conv1{"Method"} eq "stack") |
| { |
| if($Old_Size ne $New_Size and not $ArrayType) { # FIXME: isMemPadded, getOffset |
| $NewProblemType = "Parameter_Type_And_Stack"; |
| } |
| } |
| elsif($Conv1{"Method"} eq "reg") |
| { |
| if($Conv1{"Registers"} ne $Conv2{"Registers"}) { |
| $NewProblemType = "Parameter_Type_And_Register"; |
| } |
| } |
| } |
| elsif($Conv1{"Method"} ne "unknown" |
| and $Conv2{"Method"} ne "unknown") |
| { |
| if($Conv1{"Method"} eq "stack") { |
| $NewProblemType = "Parameter_Type_From_Stack_To_Register"; |
| } |
| elsif($Conv1{"Method"} eq "register") { |
| $NewProblemType = "Parameter_Type_From_Register_To_Stack"; |
| } |
| } |
| $SubProblems{$SubProblemType}{"Old_Reg"} = $Conv1{"Registers"}; |
| $SubProblems{$SubProblemType}{"New_Reg"} = $Conv2{"Registers"}; |
| } |
| %{$CompatProblems{$Level}{$Symbol}{$NewProblemType}{$ParamLoc}}=( |
| "Target"=>$PName1, |
| "Param_Pos"=>adjustParamPos($ParamPos1, $Symbol, 1), |
| "New_Signature"=>$Symbol); |
| @{$CompatProblems{$Level}{$Symbol}{$NewProblemType}{$ParamLoc}}{keys(%{$SubProblems{$SubProblemType}})} = values %{$SubProblems{$SubProblemType}}; |
| } |
| |
| @RecurTypes = (); |
| |
| # checking type definition changes |
| my $Sub_SubProblems = mergeTypes($PTid1, $PTid2, $Level); |
| foreach my $SubProblemType (keys(%{$Sub_SubProblems})) |
| { |
| foreach my $SubLoc (keys(%{$Sub_SubProblems->{$SubProblemType}})) |
| { |
| my $NewProblemType = $SubProblemType; |
| if($SubProblemType eq "DataType_Size") |
| { |
| if($PureType1{"Type"} ne "Pointer" |
| and $PureType1{"Type"} ne "Ref" |
| and index($SubLoc, "->")==-1) |
| { # stack has been affected |
| $NewProblemType = "DataType_Size_And_Stack"; |
| } |
| } |
| my $NewLoc = $ParamLoc; |
| if($SubLoc) { |
| $NewLoc .= "->".$SubLoc; |
| } |
| $CompatProblems{$Level}{$Symbol}{$NewProblemType}{$NewLoc} = $Sub_SubProblems->{$SubProblemType}{$SubLoc}; |
| } |
| } |
| } |
| |
| sub findParamPairByName($$$) |
| { |
| my ($Name, $Symbol, $LVer) = @_; |
| foreach my $ParamPos (sort {$a<=>$b} keys(%{$CompSign{$LVer}{$Symbol}{"Param"}})) |
| { |
| next if(not defined $CompSign{$LVer}{$Symbol}{"Param"}{$ParamPos}); |
| if($CompSign{$LVer}{$Symbol}{"Param"}{$ParamPos}{"name"} eq $Name) |
| { |
| return $ParamPos; |
| } |
| } |
| return "lost"; |
| } |
| |
| sub findParamPairByTypeAndPos($$$$$) |
| { |
| my ($TypeName, $MediumPos, $Order, $Symbol, $LVer) = @_; |
| my @Positions = (); |
| foreach my $ParamPos (sort {$a<=>$b} keys(%{$CompSign{$LVer}{$Symbol}{"Param"}})) |
| { |
| next if($Order eq "backward" and $ParamPos>$MediumPos); |
| next if($Order eq "forward" and $ParamPos<$MediumPos); |
| next if(not defined $CompSign{$LVer}{$Symbol}{"Param"}{$ParamPos}); |
| my $PTypeId = $CompSign{$LVer}{$Symbol}{"Param"}{$ParamPos}{"type"}; |
| if($TypeInfo{$LVer}{$PTypeId}{"Name"} eq $TypeName) { |
| push(@Positions, $ParamPos); |
| } |
| } |
| return @Positions; |
| } |
| |
| sub diffTypes($$$) |
| { |
| if(defined $Cache{"diffTypes"}{$_[2]}{$_[0]}{$_[1]}) { |
| return $Cache{"diffTypes"}{$_[2]}{$_[0]}{$_[1]}; |
| } |
| if(isRecurType($_[0], $_[1], \@RecurTypes_Diff)) |
| { # skip recursive declarations |
| return 0; |
| } |
| |
| pushType($_[0], $_[1], \@RecurTypes_Diff); |
| my $Diff = diffTypes_I(@_); |
| pop(@RecurTypes_Diff); |
| |
| return ($Cache{"diffTypes"}{$_[2]}{$_[0]}{$_[1]} = $Diff); |
| } |
| |
| sub diffTypes_I($$$) |
| { |
| my ($Type1_Id, $Type2_Id, $Level) = @_; |
| |
| my %Type1_Pure = getPureType($Type1_Id, 1); |
| my %Type2_Pure = getPureType($Type2_Id, 2); |
| |
| if($Type1_Pure{"Name"} eq $Type2_Pure{"Name"}) |
| { # equal types |
| return 0; |
| } |
| if($Type1_Pure{"Name"} eq "void") |
| { # from void* to something |
| return 0; |
| } |
| if($Type2_Pure{"Name"} eq "void") |
| { # from something to void* |
| return 0; |
| } |
| if($Type1_Pure{"Name"}=~/\*/ |
| or $Type2_Pure{"Name"}=~/\*/) |
| { # compared in detectTypeChange() |
| return 0; |
| } |
| |
| my %FloatType = map {$_=>1} ( |
| "float", |
| "double", |
| "long double" |
| ); |
| |
| my $T1 = $Type1_Pure{"Type"}; |
| my $T2 = $Type2_Pure{"Type"}; |
| |
| if($T1 eq "Struct" |
| and $T2 eq "Class") |
| { # compare as data structures |
| $T2 = "Struct"; |
| } |
| |
| if($T1 eq "Class" |
| and $T2 eq "Struct") |
| { # compare as data structures |
| $T1 = "Struct"; |
| } |
| |
| if($T1 ne $T2) |
| { # different types |
| if($T1 eq "Intrinsic" |
| and $T2 eq "Enum") |
| { # "int" to "enum" |
| return 0; |
| } |
| elsif($T2 eq "Intrinsic" |
| and $T1 eq "Enum") |
| { # "enum" to "int" |
| return 0; |
| } |
| else |
| { # union to struct |
| # ... |
| return 1; |
| } |
| } |
| else |
| { |
| if($T1 eq "Intrinsic") |
| { |
| if($FloatType{$Type1_Pure{"Name"}} |
| or $FloatType{$Type2_Pure{"Name"}}) |
| { # "float" to "double" |
| # "float" to "int" |
| if($Level eq "Source") |
| { # Safe |
| return 0; |
| } |
| else { |
| return 1; |
| } |
| } |
| } |
| elsif($T1=~/Class|Struct|Union|Enum/) |
| { |
| my @Membs1 = keys(%{$Type1_Pure{"Memb"}}); |
| my @Membs2 = keys(%{$Type2_Pure{"Memb"}}); |
| if(not @Membs1 |
| or not @Membs2) |
| { # private |
| return 0; |
| } |
| if($#Membs1!=$#Membs2) |
| { # different number of elements |
| return 1; |
| } |
| if($T1 eq "Enum") |
| { |
| foreach my $Pos (@Membs1) |
| { # compare elements by name and value |
| if($Type1_Pure{"Memb"}{$Pos}{"name"} ne $Type2_Pure{"Memb"}{$Pos}{"name"} |
| or $Type1_Pure{"Memb"}{$Pos}{"value"} ne $Type2_Pure{"Memb"}{$Pos}{"value"}) |
| { # different names |
| return 1; |
| } |
| } |
| } |
| else |
| { |
| foreach my $Pos (@Membs1) |
| { |
| if($Level eq "Source") |
| { |
| if($Type1_Pure{"Memb"}{$Pos}{"name"} ne $Type2_Pure{"Memb"}{$Pos}{"name"}) |
| { # different names |
| return 1; |
| } |
| } |
| |
| my %MT1 = %{$TypeInfo{1}{$Type1_Pure{"Memb"}{$Pos}{"type"}}}; |
| my %MT2 = %{$TypeInfo{2}{$Type2_Pure{"Memb"}{$Pos}{"type"}}}; |
| |
| if($MT1{"Name"} ne $MT2{"Name"} |
| or isAnon($MT1{"Name"}) or isAnon($MT2{"Name"})) |
| { |
| my $PL1 = getPLevel($MT1{"Tid"}, 1); |
| my $PL2 = getPLevel($MT2{"Tid"}, 2); |
| |
| if($PL1 ne $PL2) |
| { # different pointer level |
| return 1; |
| } |
| |
| # compare base types |
| my %BT1 = getBaseType($MT1{"Tid"}, 1); |
| my %BT2 = getBaseType($MT2{"Tid"}, 2); |
| |
| if(diffTypes($BT1{"Tid"}, $BT2{"Tid"}, $Level)) |
| { # different types |
| return 1; |
| } |
| } |
| } |
| } |
| } |
| else |
| { |
| # TODO: arrays, etc. |
| } |
| } |
| return 0; |
| } |
| |
| sub detectTypeChange($$$$) |
| { |
| my ($Type1_Id, $Type2_Id, $Prefix, $Level) = @_; |
| if(not $Type1_Id or not $Type2_Id) { |
| return (); |
| } |
| my %LocalProblems = (); |
| |
| my %Type1 = getType($Type1_Id, 1); |
| my %Type2 = getType($Type2_Id, 2); |
| |
| if(not $Type1{"Name"} or not $Type2{"Name"}) { |
| return (); |
| } |
| |
| my %Type1_Pure = getPureType($Type1_Id, 1); |
| my %Type2_Pure = getPureType($Type2_Id, 2); |
| |
| if(defined $In::Opt{"SkipTypedefUncover"}) |
| { |
| if($Type1_Pure{"Name"} eq $Type2_Pure{"Name"}) { |
| return (); |
| } |
| |
| if(cmpBTypes($Type1_Pure{"Name"}, $Type2_Pure{"Name"}, 1, 2)) { |
| return (); |
| } |
| } |
| |
| my %Type1_Base = ($Type1_Pure{"Type"} eq "Array")?getOneStepBaseType($Type1_Pure{"Tid"}, 1):getBaseType($Type1_Id, 1); |
| my %Type2_Base = ($Type2_Pure{"Type"} eq "Array")?getOneStepBaseType($Type2_Pure{"Tid"}, 2):getBaseType($Type2_Id, 2); |
| |
| if(not $Type1_Base{"Name"} or not $Type2_Base{"Name"}) { |
| return (); |
| } |
| |
| if(defined $UsedDump{1}{"DWARF"}) |
| { |
| if($Type1_Pure{"Name"} eq "__unknown__" |
| or $Type2_Pure{"Name"} eq "__unknown__" |
| or $Type1_Base{"Name"} eq "__unknown__" |
| or $Type2_Base{"Name"} eq "__unknown__") |
| { # Error ABI dump |
| return (); |
| } |
| } |
| |
| my $Type1_PLevel = getPLevel($Type1_Id, 1); |
| my $Type2_PLevel = getPLevel($Type2_Id, 2); |
| |
| if($Type1_PLevel eq "" or $Type2_PLevel eq "") { |
| return (); |
| } |
| |
| if($Type1_Base{"Name"} ne $Type2_Base{"Name"} |
| and ($Type1{"Name"} eq $Type2{"Name"} or ($Type1_PLevel>=1 and $Type1_PLevel==$Type2_PLevel |
| and $Type1_Base{"Name"} ne "void" and $Type2_Base{"Name"} ne "void"))) |
| { # base type change |
| if($Type1{"Name"} eq $Type2{"Name"}) |
| { |
| if($Type1{"Type"} eq "Typedef" and $Type2{"Type"} eq "Typedef") |
| { # will be reported in mergeTypes() as typedef problem |
| return (); |
| } |
| my %Typedef_1 = goToFirst($Type1{"Tid"}, 1, "Typedef"); |
| my %Typedef_2 = goToFirst($Type2{"Tid"}, 2, "Typedef"); |
| if(%Typedef_1 and %Typedef_2) |
| { |
| if($Typedef_1{"Name"} eq $Typedef_2{"Name"} |
| and $Typedef_1{"Type"} eq "Typedef" and $Typedef_2{"Type"} eq "Typedef") |
| { # const Typedef |
| return (); |
| } |
| } |
| } |
| if($Type1_Base{"Name"}!~/anon\-/ and $Type2_Base{"Name"}!~/anon\-/) |
| { |
| if($Level eq "Binary" |
| and $Type1_Base{"Size"} and $Type2_Base{"Size"} |
| and $Type1_Base{"Size"} ne $Type2_Base{"Size"}) |
| { |
| %{$LocalProblems{$Prefix."_BaseType_And_Size"}}=( |
| "Old_Value"=>$Type1_Base{"Name"}, |
| "New_Value"=>$Type2_Base{"Name"}, |
| "Old_Size"=>$Type1_Base{"Size"}*$BYTE, |
| "New_Size"=>$Type2_Base{"Size"}*$BYTE); |
| } |
| else |
| { |
| if(diffTypes($Type1_Base{"Tid"}, $Type2_Base{"Tid"}, $Level)) |
| { # format change |
| %{$LocalProblems{$Prefix."_BaseType_Format"}}=( |
| "Old_Value"=>$Type1_Base{"Name"}, |
| "New_Value"=>$Type2_Base{"Name"}, |
| "Old_Size"=>$Type1_Base{"Size"}*$BYTE, |
| "New_Size"=>$Type2_Base{"Size"}*$BYTE); |
| } |
| elsif(tNameLock($Type1_Base{"Tid"}, $Type2_Base{"Tid"})) |
| { |
| %{$LocalProblems{$Prefix."_BaseType"}}=( |
| "Old_Value"=>$Type1_Base{"Name"}, |
| "New_Value"=>$Type2_Base{"Name"}, |
| "Old_Size"=>$Type1_Base{"Size"}*$BYTE, |
| "New_Size"=>$Type2_Base{"Size"}*$BYTE); |
| } |
| } |
| } |
| } |
| elsif($Type1{"Name"} ne $Type2{"Name"}) |
| { # type change |
| if($Type1{"Name"}!~/anon\-/ and $Type2{"Name"}!~/anon\-/) |
| { |
| my $ArrayType = ($Type1{"Type"} eq "Array" and $Type2{"Type"} eq "Array"); # Fortran |
| |
| if($Prefix eq "Return" |
| and $Type1_Pure{"Name"} eq "void") |
| { |
| %{$LocalProblems{"Return_Type_From_Void"}}=( |
| "New_Value"=>$Type2{"Name"}, |
| "New_Size"=>$Type2{"Size"}*$BYTE); |
| } |
| elsif($Prefix eq "Return" |
| and $Type2_Pure{"Name"} eq "void") |
| { |
| %{$LocalProblems{"Return_Type_Became_Void"}}=( |
| "Old_Value"=>$Type1{"Name"}, |
| "Old_Size"=>$Type1{"Size"}*$BYTE); |
| } |
| else |
| { |
| if($Level eq "Binary" |
| and $Type1{"Size"} and $Type2{"Size"} |
| and $Type1{"Size"} ne $Type2{"Size"} |
| and not $ArrayType) |
| { |
| %{$LocalProblems{$Prefix."_Type_And_Size"}}=( |
| "Old_Value"=>$Type1{"Name"}, |
| "New_Value"=>$Type2{"Name"}, |
| "Old_Size"=>$Type1{"Size"}*$BYTE, |
| "New_Size"=>$Type2{"Size"}*$BYTE); |
| } |
| else |
| { |
| if(diffTypes($Type1_Id, $Type2_Id, $Level)) |
| { # format change |
| %{$LocalProblems{$Prefix."_Type_Format"}}=( |
| "Old_Value"=>$Type1{"Name"}, |
| "New_Value"=>$Type2{"Name"}, |
| "Old_Size"=>$Type1{"Size"}*$BYTE, |
| "New_Size"=>$Type2{"Size"}*$BYTE); |
| } |
| elsif(tNameLock($Type1_Id, $Type2_Id)) |
| { # FIXME: correct this condition |
| %{$LocalProblems{$Prefix."_Type"}}=( |
| "Old_Value"=>$Type1{"Name"}, |
| "New_Value"=>$Type2{"Name"}, |
| "Old_Size"=>$Type1{"Size"}*$BYTE, |
| "New_Size"=>$Type2{"Size"}*$BYTE); |
| } |
| } |
| } |
| } |
| } |
| if($Type1_PLevel!=$Type2_PLevel) |
| { |
| if($Type1{"Name"} ne "void" and $Type1{"Name"} ne "..." |
| and $Type2{"Name"} ne "void" and $Type2{"Name"} ne "...") |
| { |
| if($Level eq "Source") |
| { |
| %{$LocalProblems{$Prefix."_PointerLevel"}}=( |
| "Old_Value"=>$Type1_PLevel, |
| "New_Value"=>$Type2_PLevel); |
| } |
| else |
| { |
| if($Type2_PLevel>$Type1_PLevel) |
| { |
| %{$LocalProblems{$Prefix."_PointerLevel_Increased"}}=( |
| "Old_Value"=>$Type1_PLevel, |
| "New_Value"=>$Type2_PLevel); |
| } |
| else |
| { |
| %{$LocalProblems{$Prefix."_PointerLevel_Decreased"}}=( |
| "Old_Value"=>$Type1_PLevel, |
| "New_Value"=>$Type2_PLevel); |
| } |
| } |
| } |
| } |
| if($Type1_Pure{"Type"} eq "Array" |
| and $Type1_Pure{"BaseType"}) |
| { # base_type[N] -> base_type[N] |
| # base_type: older_structure -> typedef to newer_structure |
| my %SubProblems = detectTypeChange($Type1_Base{"Tid"}, $Type2_Base{"Tid"}, $Prefix, $Level); |
| foreach my $SubProblemType (keys(%SubProblems)) |
| { |
| $SubProblemType=~s/_Type/_BaseType/g; |
| next if(defined $LocalProblems{$SubProblemType}); |
| foreach my $Attr (keys(%{$SubProblems{$SubProblemType}})) { |
| $LocalProblems{$SubProblemType}{$Attr} = $SubProblems{$SubProblemType}{$Attr}; |
| } |
| } |
| } |
| return %LocalProblems; |
| } |
| |
| sub tNameLock($$) |
| { |
| my ($Tid1, $Tid2) = @_; |
| |
| my $TN1 = $TypeInfo{1}{$Tid1}{"Name"}; |
| my $TN2 = $TypeInfo{2}{$Tid2}{"Name"}; |
| |
| my $TT1 = $TypeInfo{1}{$Tid1}{"Type"}; |
| my $TT2 = $TypeInfo{2}{$Tid2}{"Type"}; |
| |
| if($In::ABI{1}{"GccVersion"} ne $In::ABI{2}{"GccVersion"}) |
| { # different formats |
| my %Base1 = getType($Tid1, 1); |
| while(defined $Base1{"Type"} and $Base1{"Type"} eq "Typedef") { |
| %Base1 = getOneStepBaseType($Base1{"Tid"}, 1); |
| } |
| my %Base2 = getType($Tid2, 2); |
| while(defined $Base2{"Type"} and $Base2{"Type"} eq "Typedef") { |
| %Base2 = getOneStepBaseType($Base2{"Tid"}, 2); |
| } |
| my $BName1 = uncoverTypedefs($Base1{"Name"}, 1); |
| my $BName2 = uncoverTypedefs($Base2{"Name"}, 2); |
| |
| if($BName1 eq $BName2) |
| { # equal base types |
| return 0; |
| } |
| } |
| else |
| { |
| # typedef struct {...} type_t |
| # typedef struct type_t {...} type_t |
| if(index($TN1, " ".$TN2)!=-1) |
| { |
| if($TN1=~/\A(struct|union|enum) \Q$TN2\E\Z/) { |
| return 0; |
| } |
| } |
| if(index($TN2, " ".$TN1)!=-1) |
| { |
| if($TN2=~/\A(struct|union|enum) \Q$TN1\E\Z/) { |
| return 0; |
| } |
| } |
| |
| if($TT1 eq "FuncPtr" |
| and $TT2 eq "FuncPtr") |
| { |
| my $TN1_C = $TN1; |
| my $TN2_C = $TN2; |
| |
| $TN1_C=~s/\b(struct|union) //g; |
| $TN2_C=~s/\b(struct|union) //g; |
| |
| if($TN1_C eq $TN2_C) { |
| return 0; |
| } |
| } |
| } |
| |
| my ($N1, $N2) = ($TN1, $TN2); |
| $N1=~s/\b(struct|union) //g; |
| $N2=~s/\b(struct|union) //g; |
| |
| if($N1 eq $N2) |
| { # QList<struct QUrl> and QList<QUrl> |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| sub showArch($) |
| { |
| my $Arch = $_[0]; |
| if($Arch eq "arm" |
| or $Arch eq "mips") { |
| return uc($Arch); |
| } |
| return $Arch; |
| } |
| |
| sub getReportTitle($) |
| { |
| my $Level = $_[0]; |
| |
| my $ArchInfo = " on <span style='color:Blue;'>".showArch($In::ABI{1}{"Arch"})."</span>"; |
| if($In::ABI{1}{"Arch"} ne $In::ABI{2}{"Arch"} |
| or $Level eq "Source") |
| { # don't show architecture in the header |
| $ArchInfo = ""; |
| } |
| my $Title = ""; |
| if($Level eq "Source") { |
| $Title .= "Source compatibility"; |
| } |
| elsif($Level eq "Binary") { |
| $Title .= "Binary compatibility"; |
| } |
| else { |
| $Title .= "API compatibility"; |
| } |
| |
| my $V1 = $In::Desc{1}{"Version"}; |
| my $V2 = $In::Desc{2}{"Version"}; |
| |
| if($UsedDump{1}{"DWARF"} and $UsedDump{2}{"DWARF"}) |
| { |
| my $M1 = $In::ABI{1}{"LibraryName"}; |
| my $M2 = $In::ABI{2}{"LibraryName"}; |
| |
| my $M1S = $M1; |
| my $M2S = $M2; |
| |
| $M1S=~s/(\.so|\.ko)\..+/$1/ig; |
| $M2S=~s/(\.so|\.ko)\..+/$1/ig; |
| |
| if($M1S eq $M2S |
| and $V1 ne "X" and $V2 ne "Y") |
| { |
| $Title .= " report for the <span style='color:Blue;'>$M1S</span> ".$In::Opt{"TargetComponent"}; |
| $Title .= " between <span style='color:Red;'>".$V1."</span> and <span style='color:Red;'>".$V2."</span> versions"; |
| } |
| else |
| { |
| $Title .= " report between <span style='color:Blue;'>$M1</span> (<span style='color:Red;'>".$V1."</span>)"; |
| $Title .= " and <span style='color:Blue;'>$M2</span> (<span style='color:Red;'>".$V2."</span>) objects"; |
| } |
| } |
| else |
| { |
| $Title .= " report for the <span style='color:Blue;'>".$In::Opt{"TargetTitle"}."</span> ".$In::Opt{"TargetComponent"}; |
| $Title .= " between <span style='color:Red;'>".$V1."</span> and <span style='color:Red;'>".$V2."</span> versions"; |
| } |
| |
| $Title .= $ArchInfo; |
| |
| if($In::Opt{"AppPath"}) { |
| $Title .= " (relating to the portability of application <span style='color:Blue;'>".getFilename($In::Opt{"AppPath"})."</span>)"; |
| } |
| $Title = "<h1>".$Title."</h1>\n"; |
| return $Title; |
| } |
| |
| sub getCheckedHeaders($) |
| { |
| my $LVer = $_[0]; |
| |
| my @Headers = (); |
| |
| foreach my $Path (keys(%{$In::ABI{$LVer}{"Headers"}})) |
| { |
| my $Name = getFilename($Path); |
| |
| if(not isTargetHeader($Name, $LVer)) { |
| next; |
| } |
| |
| if(skipHeader($Name, $LVer)) { |
| next; |
| } |
| |
| push(@Headers, $Path); |
| } |
| |
| return @Headers; |
| } |
| |
| sub getSourceInfo() |
| { |
| my ($CheckedHeaders, $CheckedSources, $CheckedLibs) = ("", ""); |
| |
| if(my @Headers = getCheckedHeaders(1)) |
| { |
| $CheckedHeaders = "<a name='Headers'></a>"; |
| if($In::Opt{"OldStyle"}) { |
| $CheckedHeaders .= "<h2>Header Files (".($#Headers+1).")</h2>"; |
| } |
| else { |
| $CheckedHeaders .= "<h2>Header Files <span class='gray'> ".($#Headers+1)." </span></h2>"; |
| } |
| $CheckedHeaders .= "<hr/>\n"; |
| $CheckedHeaders .= "<div class='h_list'>\n"; |
| foreach my $Path (sort {lc($a) cmp lc($b)} @Headers) { |
| $CheckedHeaders .= getFilename($Path)."<br/>\n"; |
| } |
| $CheckedHeaders .= "</div>\n"; |
| $CheckedHeaders .= "<br/>$TOP_REF<br/>\n"; |
| } |
| |
| if(my @Sources = keys(%{$In::ABI{1}{"Sources"}})) |
| { |
| $CheckedSources = "<a name='Sources'></a>"; |
| if($In::Opt{"OldStyle"}) { |
| $CheckedSources .= "<h2>Source Files (".($#Sources+1).")</h2>"; |
| } |
| else { |
| $CheckedSources .= "<h2>Source Files <span class='gray'> ".($#Sources+1)." </span></h2>"; |
| } |
| $CheckedSources .= "<hr/>\n"; |
| $CheckedSources .= "<div class='h_list'>\n"; |
| foreach my $Path (sort {lc($a) cmp lc($b)} @Sources) { |
| $CheckedSources .= getFilename($Path)."<br/>\n"; |
| } |
| $CheckedSources .= "</div>\n"; |
| $CheckedSources .= "<br/>$TOP_REF<br/>\n"; |
| } |
| |
| if(not $In::Opt{"CheckHeadersOnly"}) |
| { |
| $CheckedLibs = "<a name='Libs'></a>"; |
| if($In::Opt{"OldStyle"}) { |
| $CheckedLibs .= "<h2>".getObjTitle()." (".keys(%{$In::ABI{1}{"Symbols"}}).")</h2>"; |
| } |
| else { |
| $CheckedLibs .= "<h2>".getObjTitle()." <span class='gray'> ".keys(%{$In::ABI{1}{"Symbols"}})." </span></h2>"; |
| } |
| $CheckedLibs .= "<hr/>\n"; |
| $CheckedLibs .= "<div class='lib_list'>\n"; |
| foreach my $Library (sort {lc($a) cmp lc($b)} keys(%{$In::ABI{1}{"Symbols"}})) { |
| $CheckedLibs .= $Library."<br/>\n"; |
| } |
| $CheckedLibs .= "</div>\n"; |
| $CheckedLibs .= "<br/>$TOP_REF<br/>\n"; |
| } |
| |
| return $CheckedHeaders.$CheckedSources.$CheckedLibs; |
| } |
| |
| sub getObjTitle() |
| { |
| if(defined $UsedDump{1}{"DWARF"}) { |
| return "Objects"; |
| } |
| |
| return "Libraries"; |
| } |
| |
| sub getTypeProblemsCount($$) |
| { |
| my ($TargetSeverity, $Level) = @_; |
| my $Count = 0; |
| |
| foreach my $Type_Name (sort keys(%{$TypeChanges{$Level}})) |
| { |
| my %Kinds_Target = (); |
| foreach my $Kind (keys(%{$TypeChanges{$Level}{$Type_Name}})) |
| { |
| if($CompatRules{$Level}{$Kind}{"Severity"} ne $TargetSeverity) { |
| next; |
| } |
| |
| foreach my $Loc (keys(%{$TypeChanges{$Level}{$Type_Name}{$Kind}})) |
| { |
| my $Target = $TypeChanges{$Level}{$Type_Name}{$Kind}{$Loc}{"Target"}; |
| |
| if($Kinds_Target{$Kind}{$Target}) { |
| next; |
| } |
| |
| $Kinds_Target{$Kind}{$Target} = 1; |
| $Count += 1; |
| } |
| } |
| } |
| return $Count; |
| } |
| |
| sub getSummary($) |
| { |
| my $Level = $_[0]; |
| my ($Added, $Removed, $I_Problems_High, $I_Problems_Medium, $I_Problems_Low, $T_Problems_High, |
| $C_Problems_Low, $T_Problems_Medium, $T_Problems_Low, $I_Other, $T_Other, $C_Other) = (0,0,0,0,0,0,0,0,0,0,0,0); |
| %{$RESULT{$Level}} = ( |
| "Problems"=>0, |
| "Warnings"=>0, |
| "Affected"=>0); |
| # check rules |
| foreach my $Symbol (sort keys(%{$CompatProblems{$Level}})) |
| { |
| foreach my $Kind (keys(%{$CompatProblems{$Level}{$Symbol}})) |
| { |
| if(not defined $CompatRules{$Level}{$Kind}) |
| { # unknown rule |
| if(not $UnknownRules{$Level}{$Kind}) |
| { # only one warning |
| printMsg("WARNING", "unknown rule \"$Kind\" (\"$Level\")"); |
| $UnknownRules{$Level}{$Kind}=1; |
| } |
| delete($CompatProblems{$Level}{$Symbol}{$Kind}); |
| } |
| } |
| } |
| foreach my $Constant (sort keys(%{$CompatProblems_Constants{$Level}})) |
| { |
| foreach my $Kind (keys(%{$CompatProblems_Constants{$Level}{$Constant}})) |
| { |
| if(not defined $CompatRules{$Level}{$Kind}) |
| { # unknown rule |
| if(not $UnknownRules{$Level}{$Kind}) |
| { # only one warning |
| printMsg("WARNING", "unknown rule \"$Kind\" (\"$Level\")"); |
| $UnknownRules{$Level}{$Kind}=1; |
| } |
| delete($CompatProblems_Constants{$Level}{$Constant}{$Kind}); |
| } |
| } |
| } |
| foreach my $Symbol (sort keys(%{$CompatProblems{$Level}})) |
| { |
| foreach my $Kind (sort keys(%{$CompatProblems{$Level}{$Symbol}})) |
| { |
| if($CompatRules{$Level}{$Kind}{"Kind"} eq "Symbols") |
| { |
| my $Severity = $CompatRules{$Level}{$Kind}{"Severity"}; |
| foreach my $Loc (sort keys(%{$CompatProblems{$Level}{$Symbol}{$Kind}})) |
| { |
| if($Kind eq "Added_Symbol") { |
| $Added += 1; |
| } |
| elsif($Kind eq "Removed_Symbol") |
| { |
| $Removed += 1; |
| $TotalAffected{$Level}{$Symbol} = $Severity; |
| } |
| else |
| { |
| if($Severity eq "Safe") { |
| $I_Other += 1; |
| } |
| elsif($Severity eq "High") { |
| $I_Problems_High += 1; |
| } |
| elsif($Severity eq "Medium") { |
| $I_Problems_Medium += 1; |
| } |
| elsif($Severity eq "Low") { |
| $I_Problems_Low += 1; |
| } |
| if(($Severity ne "Low" or $In::Opt{"StrictCompat"}) |
| and $Severity ne "Safe") { |
| $TotalAffected{$Level}{$Symbol} = $Severity; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| my %MethodTypeIndex = (); |
| |
| foreach my $Symbol (sort keys(%{$CompatProblems{$Level}})) |
| { |
| foreach my $Kind (sort keys(%{$CompatProblems{$Level}{$Symbol}})) |
| { |
| if($CompatRules{$Level}{$Kind}{"Kind"} eq "Types") |
| { |
| my $Severity = $CompatRules{$Level}{$Kind}{"Severity"}; |
| if(($Severity ne "Low" or $In::Opt{"StrictCompat"}) |
| and $Severity ne "Safe") |
| { |
| if(my $Sev = $TotalAffected{$Level}{$Symbol}) |
| { |
| if($Severity_Val{$Severity}>$Severity_Val{$Sev}) { |
| $TotalAffected{$Level}{$Symbol} = $Severity; |
| } |
| } |
| else { |
| $TotalAffected{$Level}{$Symbol} = $Severity; |
| } |
| } |
| |
| my $LSK = $CompatProblems{$Level}{$Symbol}{$Kind}; |
| my (@Locs1, @Locs2) = (); |
| foreach my $Loc (sort keys(%{$LSK})) |
| { |
| if(index($Loc, "retval")==0 or index($Loc, "this")==0) { |
| push(@Locs2, $Loc); |
| } |
| else { |
| push(@Locs1, $Loc); |
| } |
| } |
| |
| foreach my $Loc (@Locs1, @Locs2) |
| { |
| my $Type = $LSK->{$Loc}{"Type_Name"}; |
| my $Target = $LSK->{$Loc}{"Target"}; |
| |
| if(defined $MethodTypeIndex{$Symbol}{$Type}{$Kind}{$Target}) |
| { # one location for one type and target |
| next; |
| } |
| $MethodTypeIndex{$Symbol}{$Type}{$Kind}{$Target} = 1; |
| |
| $TypeChanges{$Level}{$Type}{$Kind}{$Loc} = $LSK->{$Loc}; |
| $TypeProblemsIndex{$Level}{$Type}{$Kind}{$Loc}{$Symbol} = 1; |
| } |
| } |
| } |
| } |
| |
| # clean memory |
| %MethodTypeIndex = (); |
| |
| $T_Problems_High = getTypeProblemsCount("High", $Level); |
| $T_Problems_Medium = getTypeProblemsCount("Medium", $Level); |
| $T_Problems_Low = getTypeProblemsCount("Low", $Level); |
| $T_Other = getTypeProblemsCount("Safe", $Level); |
| |
| # changed and removed public symbols |
| my $SCount = keys(%{$CheckedSymbols{$Level}}); |
| if($In::Opt{"ExtendedCheck"}) |
| { # don't count external_func_0 for constants |
| $SCount-=1; |
| } |
| if($SCount) |
| { |
| my %Weight = ( |
| "High" => 100, |
| "Medium" => 50, |
| "Low" => 25 |
| ); |
| foreach (keys(%{$TotalAffected{$Level}})) { |
| $RESULT{$Level}{"Affected"}+=$Weight{$TotalAffected{$Level}{$_}}; |
| } |
| $RESULT{$Level}{"Affected"} = $RESULT{$Level}{"Affected"}/$SCount; |
| } |
| else { |
| $RESULT{$Level}{"Affected"} = 0; |
| } |
| |
| $RESULT{$Level}{"Affected"} = showNum($RESULT{$Level}{"Affected"}); |
| if($RESULT{$Level}{"Affected"}>=100) { |
| $RESULT{$Level}{"Affected"} = 100; |
| } |
| |
| $RESULT{$Level}{"Problems"} += $Removed; |
| $RESULT{$Level}{"Problems"} += $T_Problems_High + $I_Problems_High; |
| $RESULT{$Level}{"Problems"} += $T_Problems_Medium + $I_Problems_Medium; |
| if($In::Opt{"StrictCompat"}) { |
| $RESULT{$Level}{"Problems"} += $T_Problems_Low + $I_Problems_Low; |
| } |
| else { |
| $RESULT{$Level}{"Warnings"} += $T_Problems_Low + $I_Problems_Low; |
| } |
| if($In::Opt{"WarnNewSym"}) { |
| $RESULT{$Level}{"Problems"} += $Added; |
| } |
| |
| foreach my $Constant (keys(%{$CompatProblems_Constants{$Level}})) |
| { |
| foreach my $Kind (keys(%{$CompatProblems_Constants{$Level}{$Constant}})) |
| { |
| my $Severity = $CompatRules{$Level}{$Kind}{"Severity"}; |
| if($Severity eq "Safe") |
| { |
| $C_Other+=1; |
| } |
| elsif($Severity eq "Low") |
| { |
| $C_Problems_Low+=1; |
| } |
| } |
| } |
| |
| if($C_Problems_Low) |
| { |
| if($In::Opt{"StrictCompat"}) { |
| $RESULT{$Level}{"Problems"} += $C_Problems_Low; |
| } |
| else { |
| $RESULT{$Level}{"Warnings"} += $C_Problems_Low; |
| } |
| } |
| if($RESULT{$Level}{"Problems"} |
| and $RESULT{$Level}{"Affected"}) { |
| $RESULT{$Level}{"Verdict"} = "incompatible"; |
| } |
| else { |
| $RESULT{$Level}{"Verdict"} = "compatible"; |
| } |
| |
| my $TotalTypes = keys(%{$CheckedTypes{$Level}}); |
| if(not $TotalTypes) |
| { # list all the types |
| $TotalTypes = keys(%{$TName_Tid{1}}); |
| } |
| |
| my $TotalSymbols = keys(%{$CheckedSymbols{$Level}}); |
| |
| if($In::Opt{"ExtendedCheck"}) { |
| $TotalSymbols -= keys(%ExtendedSymbols); |
| } |
| |
| my $AnyChanged = ($Added or $Removed or $I_Problems_High or $I_Problems_Medium or $I_Problems_Low or $T_Problems_High or |
| $C_Problems_Low or $T_Problems_Medium or $T_Problems_Low or $I_Other or $T_Other or $C_Other); |
| |
| my ($Arch1, $Arch2) = ($In::ABI{1}{"Arch"}, $In::ABI{2}{"Arch"}); |
| my ($GccV1, $GccV2) = ($In::ABI{1}{"GccVersion"}, $In::ABI{2}{"GccVersion"}); |
| my ($ClangV1, $ClangV2) = ($In::ABI{1}{"ClangVersion"}, $In::ABI{2}{"ClangVersion"}); |
| |
| my ($TestInfo, $TestResults, $Problem_Summary) = (); |
| |
| if($In::Opt{"ReportFormat"} eq "xml") |
| { # XML |
| # test info |
| $TestInfo .= " <library>".$In::Opt{"TargetLib"}."</library>\n"; |
| $TestInfo .= " <version1>\n"; |
| $TestInfo .= " <number>".$In::Desc{1}{"Version"}."</number>\n"; |
| $TestInfo .= " <arch>$Arch1</arch>\n"; |
| if($GccV1) { |
| $TestInfo .= " <gcc>$GccV1</gcc>\n"; |
| } |
| elsif($ClangV1) { |
| $TestInfo .= " <clang>$ClangV1</clang>\n"; |
| } |
| $TestInfo .= " </version1>\n"; |
| |
| $TestInfo .= " <version2>\n"; |
| $TestInfo .= " <number>".$In::Desc{2}{"Version"}."</number>\n"; |
| $TestInfo .= " <arch>$Arch2</arch>\n"; |
| if($GccV2) { |
| $TestInfo .= " <gcc>$GccV2</gcc>\n"; |
| } |
| elsif($ClangV2) { |
| $TestInfo .= " <clang>$ClangV2</clang>\n"; |
| } |
| $TestInfo .= " </version2>\n"; |
| $TestInfo = "<test_info>\n".$TestInfo."</test_info>\n\n"; |
| |
| # test results |
| if(my @Headers = keys(%{$In::ABI{1}{"Headers"}})) |
| { |
| $TestResults .= " <headers>\n"; |
| foreach my $Name (sort {lc($a) cmp lc($b)} @Headers) { |
| $TestResults .= " <name>".getFilename($Name)."</name>\n"; |
| } |
| $TestResults .= " </headers>\n"; |
| } |
| |
| if(my @Sources = keys(%{$In::ABI{1}{"Sources"}})) |
| { |
| $TestResults .= " <sources>\n"; |
| foreach my $Name (sort {lc($a) cmp lc($b)} @Sources) { |
| $TestResults .= " <name>".getFilename($Name)."</name>\n"; |
| } |
| $TestResults .= " </sources>\n"; |
| } |
| |
| $TestResults .= " <libs>\n"; |
| foreach my $Library (sort {lc($a) cmp lc($b)} keys(%{$In::ABI{1}{"Symbols"}})) |
| { |
| $TestResults .= " <name>$Library</name>\n"; |
| } |
| $TestResults .= " </libs>\n"; |
| |
| $TestResults .= " <symbols>".$TotalSymbols."</symbols>\n"; |
| $TestResults .= " <types>".$TotalTypes."</types>\n"; |
| |
| $TestResults .= " <verdict>".$RESULT{$Level}{"Verdict"}."</verdict>\n"; |
| $TestResults .= " <affected>".$RESULT{$Level}{"Affected"}."</affected>\n"; |
| $TestResults = "<test_results>\n".$TestResults."</test_results>\n\n"; |
| |
| # problem summary |
| $Problem_Summary .= " <added_symbols>".$Added."</added_symbols>\n"; |
| $Problem_Summary .= " <removed_symbols>".$Removed."</removed_symbols>\n"; |
| |
| $Problem_Summary .= " <problems_with_types>\n"; |
| $Problem_Summary .= " <high>$T_Problems_High</high>\n"; |
| $Problem_Summary .= " <medium>$T_Problems_Medium</medium>\n"; |
| $Problem_Summary .= " <low>$T_Problems_Low</low>\n"; |
| $Problem_Summary .= " <safe>$T_Other</safe>\n"; |
| $Problem_Summary .= " </problems_with_types>\n"; |
| |
| $Problem_Summary .= " <problems_with_symbols>\n"; |
| $Problem_Summary .= " <high>$I_Problems_High</high>\n"; |
| $Problem_Summary .= " <medium>$I_Problems_Medium</medium>\n"; |
| $Problem_Summary .= " <low>$I_Problems_Low</low>\n"; |
| $Problem_Summary .= " <safe>$I_Other</safe>\n"; |
| $Problem_Summary .= " </problems_with_symbols>\n"; |
| |
| $Problem_Summary .= " <problems_with_constants>\n"; |
| $Problem_Summary .= " <low>$C_Problems_Low</low>\n"; |
| $Problem_Summary .= " </problems_with_constants>\n"; |
| |
| $Problem_Summary = "<problem_summary>\n".$Problem_Summary."</problem_summary>\n\n"; |
| |
| return ($TestInfo.$TestResults.$Problem_Summary, "", $AnyChanged); |
| } |
| else |
| { # HTML |
| # test info |
| $TestInfo = "<h2>Test Info</h2><hr/>\n"; |
| $TestInfo .= "<table class='summary'>\n"; |
| |
| if($In::Opt{"TargetComponent"} eq "library") { |
| $TestInfo .= "<tr><th>Library Name</th><td>".$In::Opt{"TargetTitle"}."</td></tr>\n"; |
| } |
| else { |
| $TestInfo .= "<tr><th>Module Name</th><td>".$In::Opt{"TargetTitle"}."</td></tr>\n"; |
| } |
| |
| my (@VInf1, @VInf2, $AddTestInfo) = (); |
| |
| # CPU arch |
| if($Arch1 eq $Arch2) |
| { # go to the separate section |
| $AddTestInfo .= "<tr><th>Arch</th><td>".showArch($Arch1)."</td></tr>\n"; |
| } |
| else |
| { # go to the version number |
| push(@VInf1, showArch($Arch1)); |
| push(@VInf2, showArch($Arch2)); |
| } |
| |
| if($Level eq "Binary" |
| and $In::Opt{"Target"} ne "windows") |
| { |
| if($GccV1 and $GccV2) |
| { # GCC version |
| if($GccV1 eq $GccV2) |
| { # go to the separate section |
| $AddTestInfo .= "<tr><th>GCC Version</th><td>$GccV1</td></tr>\n"; |
| } |
| else |
| { # go to the version number |
| push(@VInf1, "gcc ".$GccV1); |
| push(@VInf2, "gcc ".$GccV2); |
| } |
| } |
| elsif($ClangV1 and $ClangV2) |
| { # Clang version |
| if($ClangV1 eq $ClangV2) |
| { # go to the separate section |
| $AddTestInfo .= "<tr><th>Clang Version</th><td>$ClangV1</td></tr>\n"; |
| } |
| else |
| { # go to the version number |
| push(@VInf1, "clang ".$ClangV1); |
| push(@VInf2, "clang ".$ClangV2); |
| } |
| } |
| elsif($GccV1 and $ClangV2) |
| { |
| push(@VInf1, "gcc ".$GccV1); |
| push(@VInf2, "clang ".$ClangV2); |
| } |
| elsif($ClangV1 and $GccV2) |
| { |
| push(@VInf1, "clang ".$ClangV1); |
| push(@VInf2, "gcc ".$GccV2); |
| } |
| } |
| # show long version names with GCC version and CPU architecture name (if different) |
| $TestInfo .= "<tr><th>Version #1</th><td>".$In::Desc{1}{"Version"}.(@VInf1?" (".join(", ", reverse(@VInf1)).")":"")."</td></tr>\n"; |
| $TestInfo .= "<tr><th>Version #2</th><td>".$In::Desc{2}{"Version"}.(@VInf2?" (".join(", ", reverse(@VInf2)).")":"")."</td></tr>\n"; |
| $TestInfo .= $AddTestInfo; |
| |
| if($In::Opt{"ExtendedCheck"}) { |
| $TestInfo .= "<tr><th>Mode</th><td>Extended</td></tr>\n"; |
| } |
| if($In::Opt{"JoinReport"}) |
| { |
| if($Level eq "Binary") { |
| $TestInfo .= "<tr><th>Subject</th><td width='150px'>Binary Compatibility</td></tr>\n"; # Run-time |
| } |
| elsif($Level eq "Source") { |
| $TestInfo .= "<tr><th>Subject</th><td width='150px'>Source Compatibility</td></tr>\n"; # Build-time |
| } |
| } |
| $TestInfo .= "</table>\n"; |
| |
| # test results |
| $TestResults = "<h2>Test Results</h2><hr/>\n"; |
| $TestResults .= "<table class='summary'>"; |
| |
| if(my @Headers = getCheckedHeaders(1)) |
| { |
| my $Headers_Link = "<a href='#Headers' style='color:Blue;'>".($#Headers + 1)."</a>"; |
| $TestResults .= "<tr><th>Total Header Files</th><td>".$Headers_Link."</td></tr>\n"; |
| } |
| |
| if(my @Sources = keys(%{$In::ABI{1}{"Sources"}})) |
| { |
| my $Src_Link = "<a href='#Sources' style='color:Blue;'>".($#Sources + 1)."</a>"; |
| $TestResults .= "<tr><th>Total Source Files</th><td>".$Src_Link."</td></tr>\n"; |
| } |
| |
| if(not $In::Opt{"ExtendedCheck"}) |
| { |
| my $Libs_Link = "0"; |
| $Libs_Link = "<a href='#Libs' style='color:Blue;'>".keys(%{$In::ABI{1}{"Symbols"}})."</a>" if(keys(%{$In::ABI{1}{"Symbols"}})>0); |
| $TestResults .= "<tr><th>Total ".getObjTitle()."</th><td>".($In::Opt{"CheckHeadersOnly"}?"0 (not analyzed)":$Libs_Link)."</td></tr>\n"; |
| } |
| |
| $TestResults .= "<tr><th>Total Symbols / Types</th><td>".$TotalSymbols." / ".$TotalTypes."</td></tr>\n"; |
| |
| my $META_DATA = "verdict:".$RESULT{$Level}{"Verdict"}.";"; |
| if($In::Opt{"JoinReport"}) { |
| $META_DATA = "kind:".lc($Level).";".$META_DATA; |
| } |
| |
| my $BC_Rate = showNum(100 - $RESULT{$Level}{"Affected"}); |
| |
| $TestResults .= "<tr><th>Compatibility</th>\n"; |
| if($RESULT{$Level}{"Verdict"} eq "incompatible") |
| { |
| my $Cl = "incompatible"; |
| if($BC_Rate>=90) { |
| $Cl = "warning"; |
| } |
| elsif($BC_Rate>=80) { |
| $Cl = "almost_compatible"; |
| } |
| |
| $TestResults .= "<td class=\'$Cl\'>".$BC_Rate."%</td>\n"; |
| } |
| else { |
| $TestResults .= "<td class=\'compatible\'>100%</td>\n"; |
| } |
| $TestResults .= "</tr>\n"; |
| $TestResults .= "</table>\n"; |
| |
| $META_DATA .= "affected:".$RESULT{$Level}{"Affected"}.";";# in percents |
| # problem summary |
| $Problem_Summary = "<h2>Problem Summary</h2><hr/>\n"; |
| $Problem_Summary .= "<table class='summary'>"; |
| $Problem_Summary .= "<tr><th></th><th style='text-align:center;'>Severity</th><th style='text-align:center;'>Count</th></tr>"; |
| |
| my $Added_Link = "0"; |
| if($Added>0) |
| { |
| if($In::Opt{"JoinReport"}) { |
| $Added_Link = "<a href='#".$Level."_Added' style='color:Blue;'>$Added</a>"; |
| } |
| else { |
| $Added_Link = "<a href='#Added' style='color:Blue;'>$Added</a>"; |
| } |
| } |
| $META_DATA .= "added:$Added;"; |
| $Problem_Summary .= "<tr><th>Added Symbols</th><td>-</td><td".getStyle("I", "Added", $Added).">$Added_Link</td></tr>\n"; |
| |
| my $Removed_Link = "0"; |
| if($Removed>0) |
| { |
| if($In::Opt{"JoinReport"}) { |
| $Removed_Link = "<a href='#".$Level."_Removed' style='color:Blue;'>$Removed</a>" |
| } |
| else { |
| $Removed_Link = "<a href='#Removed' style='color:Blue;'>$Removed</a>" |
| } |
| } |
| $META_DATA .= "removed:$Removed;"; |
| $Problem_Summary .= "<tr><th>Removed Symbols</th>"; |
| $Problem_Summary .= "<td>High</td><td".getStyle("I", "Removed", $Removed).">$Removed_Link</td></tr>\n"; |
| |
| my $TH_Link = "0"; |
| $TH_Link = "<a href='#".getAnchor("Type", $Level, "High")."' style='color:Blue;'>$T_Problems_High</a>" if($T_Problems_High>0); |
| $META_DATA .= "type_problems_high:$T_Problems_High;"; |
| $Problem_Summary .= "<tr><th rowspan='3'>Problems with<br/>Data Types</th>"; |
| $Problem_Summary .= "<td>High</td><td".getStyle("T", "High", $T_Problems_High).">$TH_Link</td></tr>\n"; |
| |
| my $TM_Link = "0"; |
| $TM_Link = "<a href='#".getAnchor("Type", $Level, "Medium")."' style='color:Blue;'>$T_Problems_Medium</a>" if($T_Problems_Medium>0); |
| $META_DATA .= "type_problems_medium:$T_Problems_Medium;"; |
| $Problem_Summary .= "<tr><td>Medium</td><td".getStyle("T", "Medium", $T_Problems_Medium).">$TM_Link</td></tr>\n"; |
| |
| my $TL_Link = "0"; |
| $TL_Link = "<a href='#".getAnchor("Type", $Level, "Low")."' style='color:Blue;'>$T_Problems_Low</a>" if($T_Problems_Low>0); |
| $META_DATA .= "type_problems_low:$T_Problems_Low;"; |
| $Problem_Summary .= "<tr><td>Low</td><td".getStyle("T", "Low", $T_Problems_Low).">$TL_Link</td></tr>\n"; |
| |
| my $IH_Link = "0"; |
| $IH_Link = "<a href='#".getAnchor("Symbol", $Level, "High")."' style='color:Blue;'>$I_Problems_High</a>" if($I_Problems_High>0); |
| $META_DATA .= "interface_problems_high:$I_Problems_High;"; |
| $Problem_Summary .= "<tr><th rowspan='3'>Problems with<br/>Symbols</th>"; |
| $Problem_Summary .= "<td>High</td><td".getStyle("I", "High", $I_Problems_High).">$IH_Link</td></tr>\n"; |
| |
| my $IM_Link = "0"; |
| $IM_Link = "<a href='#".getAnchor("Symbol", $Level, "Medium")."' style='color:Blue;'>$I_Problems_Medium</a>" if($I_Problems_Medium>0); |
| $META_DATA .= "interface_problems_medium:$I_Problems_Medium;"; |
| $Problem_Summary .= "<tr><td>Medium</td><td".getStyle("I", "Medium", $I_Problems_Medium).">$IM_Link</td></tr>\n"; |
| |
| my $IL_Link = "0"; |
| $IL_Link = "<a href='#".getAnchor("Symbol", $Level, "Low")."' style='color:Blue;'>$I_Problems_Low</a>" if($I_Problems_Low>0); |
| $META_DATA .= "interface_problems_low:$I_Problems_Low;"; |
| $Problem_Summary .= "<tr><td>Low</td><td".getStyle("I", "Low", $I_Problems_Low).">$IL_Link</td></tr>\n"; |
| |
| my $ChangedConstants_Link = "0"; |
| if(keys(%{$CheckedSymbols{$Level}}) and $C_Problems_Low) { |
| $ChangedConstants_Link = "<a href='#".getAnchor("Constant", $Level, "Low")."' style='color:Blue;'>$C_Problems_Low</a>"; |
| } |
| $META_DATA .= "changed_constants:$C_Problems_Low;"; |
| $Problem_Summary .= "<tr><th>Problems with<br/>Constants</th><td>Low</td><td".getStyle("C", "Low", $C_Problems_Low).">$ChangedConstants_Link</td></tr>\n"; |
| |
| # Safe Changes |
| if($T_Other) |
| { |
| my $TS_Link = "<a href='#".getAnchor("Type", $Level, "Safe")."' style='color:Blue;'>$T_Other</a>"; |
| $Problem_Summary .= "<tr><th>Other Changes<br/>in Data Types</th><td>-</td><td".getStyle("T", "Safe", $T_Other).">$TS_Link</td></tr>\n"; |
| $META_DATA .= "type_changes_other:$T_Other;"; |
| } |
| |
| if($I_Other) |
| { |
| my $IS_Link = "<a href='#".getAnchor("Symbol", $Level, "Safe")."' style='color:Blue;'>$I_Other</a>"; |
| $Problem_Summary .= "<tr><th>Other Changes<br/>in Symbols</th><td>-</td><td".getStyle("I", "Safe", $I_Other).">$IS_Link</td></tr>\n"; |
| $META_DATA .= "interface_changes_other:$I_Other;"; |
| } |
| |
| if($C_Other) |
| { |
| my $CS_Link = "<a href='#".getAnchor("Constant", $Level, "Safe")."' style='color:Blue;'>$C_Other</a>"; |
| $Problem_Summary .= "<tr><th>Other Changes<br/>in Constants</th><td>-</td><td".getStyle("C", "Safe", $C_Other).">$CS_Link</td></tr>\n"; |
| $META_DATA .= "constant_changes_other:$C_Other;"; |
| } |
| |
| $META_DATA .= "tool_version:$TOOL_VERSION"; |
| $Problem_Summary .= "</table>\n"; |
| |
| return ($TestInfo.$TestResults.$Problem_Summary, $META_DATA, $AnyChanged); |
| } |
| } |
| |
| sub getStyle($$$) |
| { |
| my ($Subj, $Act, $Num) = @_; |
| my %Style = ( |
| "Added"=>"new", |
| "Removed"=>"failed", |
| "Safe"=>"passed", |
| "Low"=>"warning", |
| "Medium"=>"failed", |
| "High"=>"failed" |
| ); |
| |
| if($Num>0) { |
| return " class='".$Style{$Act}."'"; |
| } |
| |
| return ""; |
| } |
| |
| sub getReportChangedConstants($$) |
| { |
| my ($TargetSeverity, $Level) = @_; |
| my $CHANGED_CONSTANTS = ""; |
| |
| my %ReportMap = (); |
| foreach my $Constant (keys(%{$CompatProblems_Constants{$Level}})) |
| { |
| my $Header = $Constants{1}{$Constant}{"Header"}; |
| if(not $Header) { |
| $Header = $Constants{1}{$Constant}{"Source"}; |
| } |
| if(not $Header) |
| { # added |
| $Header = $Constants{2}{$Constant}{"Header"}; |
| if(not $Header) { |
| $Header = $Constants{2}{$Constant}{"Source"} |
| } |
| } |
| |
| foreach my $Kind (sort {lc($a) cmp lc($b)} keys(%{$CompatProblems_Constants{$Level}{$Constant}})) |
| { |
| if(not defined $CompatRules{$Level}{$Kind}) { |
| next; |
| } |
| if($TargetSeverity ne $CompatRules{$Level}{$Kind}{"Severity"}) { |
| next; |
| } |
| $ReportMap{$Header}{$Constant}{$Kind} = 1; |
| } |
| } |
| |
| if($In::Opt{"ReportFormat"} eq "xml") |
| { # XML |
| foreach my $HeaderName (sort {lc($a) cmp lc($b)} keys(%ReportMap)) |
| { |
| $CHANGED_CONSTANTS .= " <header name=\"$HeaderName\">\n"; |
| foreach my $Constant (sort {lc($a) cmp lc($b)} keys(%{$ReportMap{$HeaderName}})) |
| { |
| $CHANGED_CONSTANTS .= " <constant name=\"$Constant\">\n"; |
| foreach my $Kind (sort {lc($a) cmp lc($b)} keys(%{$ReportMap{$HeaderName}{$Constant}})) |
| { |
| my $Change = $CompatRules{$Level}{$Kind}{"Change"}; |
| my $Effect = $CompatRules{$Level}{$Kind}{"Effect"}; |
| my $Overcome = $CompatRules{$Level}{$Kind}{"Overcome"}; |
| |
| $CHANGED_CONSTANTS .= " <problem id=\"$Kind\">\n"; |
| $CHANGED_CONSTANTS .= " <change".getXmlParams($Change, $CompatProblems_Constants{$Level}{$Constant}{$Kind}).">$Change</change>\n"; |
| $CHANGED_CONSTANTS .= " <effect".getXmlParams($Effect, $CompatProblems_Constants{$Level}{$Constant}{$Kind}).">$Effect</effect>\n"; |
| if($Overcome) { |
| $CHANGED_CONSTANTS .= " <overcome".getXmlParams($Overcome, $CompatProblems_Constants{$Level}{$Constant}{$Kind}).">$Overcome</overcome>\n"; |
| } |
| $CHANGED_CONSTANTS .= " </problem>\n"; |
| } |
| $CHANGED_CONSTANTS .= " </constant>\n"; |
| } |
| $CHANGED_CONSTANTS .= " </header>\n"; |
| } |
| $CHANGED_CONSTANTS = "<problems_with_constants severity=\"Low\">\n".$CHANGED_CONSTANTS."</problems_with_constants>\n\n"; |
| } |
| else |
| { # HTML |
| my $ProblemsNum = 0; |
| foreach my $HeaderName (sort {lc($a) cmp lc($b)} keys(%ReportMap)) |
| { |
| $CHANGED_CONSTANTS .= "<span class='h_name'>$HeaderName</span><br/>\n"; |
| foreach my $Constant (sort {lc($a) cmp lc($b)} keys(%{$ReportMap{$HeaderName}})) |
| { |
| my $Report = ""; |
| |
| foreach my $Kind (sort {lc($a) cmp lc($b)} keys(%{$ReportMap{$HeaderName}{$Constant}})) |
| { |
| my $Change = applyMacroses($Level, $Kind, $CompatRules{$Level}{$Kind}{"Change"}, $CompatProblems_Constants{$Level}{$Constant}{$Kind}); |
| my $Effect = $CompatRules{$Level}{$Kind}{"Effect"}; |
| $Report .= "<tr>\n<th>1</th>\n<td>".$Change."</td>\n<td>$Effect</td>\n</tr>\n"; |
| $ProblemsNum += 1; |
| } |
| if($Report) |
| { |
| $Report = $ContentDivStart."<table class='ptable'>\n<tr>\n<th class='pn'></th>\n<th class='chg'>Change</th>\n<th>Effect</th>\n</tr>\n".$Report."</table>\n<br/>\n$ContentDivEnd\n"; |
| $Report = $ContentSpanStart."<span class='ext'>[+]</span> ".$Constant.$ContentSpanEnd."<br/>\n".$Report; |
| $Report = insertIDs($Report); |
| } |
| $CHANGED_CONSTANTS .= $Report; |
| } |
| $CHANGED_CONSTANTS .= "<br/>\n"; |
| } |
| if($CHANGED_CONSTANTS) |
| { |
| my $Title = "Problems with Constants, $TargetSeverity Severity"; |
| if($TargetSeverity eq "Safe") |
| { # Safe Changes |
| $Title = "Other Changes in Constants"; |
| } |
| if($In::Opt{"OldStyle"}) { |
| $CHANGED_CONSTANTS = "<h2>$Title ($ProblemsNum)</h2><hr/>\n".$CHANGED_CONSTANTS; |
| } |
| else { |
| $CHANGED_CONSTANTS = "<h2>$Title <span".getStyle("C", $TargetSeverity, $ProblemsNum)."> $ProblemsNum </span></h2><hr/>\n".$CHANGED_CONSTANTS; |
| } |
| $CHANGED_CONSTANTS = "<a name='".getAnchor("Constant", $Level, $TargetSeverity)."'></a>\n".$CHANGED_CONSTANTS.$TOP_REF."<br/>\n"; |
| } |
| } |
| return $CHANGED_CONSTANTS; |
| } |
| |
| sub getTitle($$$) |
| { |
| my ($Header, $Library, $NameSpace) = @_; |
| my $Title = ""; |
| |
| if($Header and $Library) |
| { |
| $Title .= "<span class='h_name'>$Header</span>"; |
| $Title .= ", <span class='lib_name'>$Library</span><br/>\n"; |
| } |
| elsif($Library) { |
| $Title .= "<span class='lib_name'>$Library</span><br/>\n"; |
| } |
| elsif($Header) { |
| $Title .= "<span class='h_name'>$Header</span><br/>\n"; |
| } |
| |
| if($NameSpace) { |
| $Title .= "<span class='ns'>namespace <b>$NameSpace</b></span><br/>\n"; |
| } |
| |
| return $Title; |
| } |
| |
| sub getReportAdded($) |
| { |
| my $Level = $_[0]; |
| my $ADDED_INTERFACES = ""; |
| my %ReportMap = (); |
| foreach my $Symbol (sort keys(%{$CompatProblems{$Level}})) |
| { |
| foreach my $Kind (sort keys(%{$CompatProblems{$Level}{$Symbol}})) |
| { |
| if($Kind eq "Added_Symbol") |
| { |
| my $HeaderName = $CompSign{2}{$Symbol}{"Header"}; |
| if(not $HeaderName) { |
| $HeaderName = $CompSign{2}{$Symbol}{"Source"}; |
| } |
| |
| my $DyLib = $In::ABI{2}{"SymLib"}{$Symbol}; |
| if($Level eq "Source" and $In::Opt{"ReportFormat"} eq "html") |
| { # do not show library name in the HTML report |
| $DyLib = ""; |
| } |
| $ReportMap{$HeaderName}{$DyLib}{$Symbol} = 1; |
| } |
| } |
| } |
| if($In::Opt{"ReportFormat"} eq "xml") |
| { # XML |
| foreach my $HeaderName (sort {lc($a) cmp lc($b)} keys(%ReportMap)) |
| { |
| $ADDED_INTERFACES .= " <header name=\"$HeaderName\">\n"; |
| foreach my $DyLib (sort {lc($a) cmp lc($b)} keys(%{$ReportMap{$HeaderName}})) |
| { |
| $ADDED_INTERFACES .= " <library name=\"$DyLib\">\n"; |
| foreach my $Symbol (keys(%{$ReportMap{$HeaderName}{$DyLib}})) { |
| $ADDED_INTERFACES .= " <name>$Symbol</name>\n"; |
| } |
| $ADDED_INTERFACES .= " </library>\n"; |
| } |
| $ADDED_INTERFACES .= " </header>\n"; |
| } |
| $ADDED_INTERFACES = "<added_symbols>\n".$ADDED_INTERFACES."</added_symbols>\n\n"; |
| } |
| else |
| { # HTML |
| my $Added_Number = 0; |
| foreach my $HeaderName (sort {lc($a) cmp lc($b)} keys(%ReportMap)) |
| { |
| foreach my $DyLib (sort {lc($a) cmp lc($b)} keys(%{$ReportMap{$HeaderName}})) |
| { |
| my %NameSpaceSymbols = (); |
| foreach my $Symbol (keys(%{$ReportMap{$HeaderName}{$DyLib}})) { |
| $NameSpaceSymbols{selectSymbolNs($Symbol, 2)}{$Symbol} = 1; |
| } |
| foreach my $NameSpace (sort keys(%NameSpaceSymbols)) |
| { |
| $ADDED_INTERFACES .= getTitle($HeaderName, $DyLib, $NameSpace); |
| my @SortedInterfaces = sort {lc($CompSign{2}{$a}{"Unmangled"}) cmp lc($CompSign{2}{$b}{"Unmangled"})} sort {lc($a) cmp lc($b)} keys(%{$NameSpaceSymbols{$NameSpace}}); |
| foreach my $Symbol (@SortedInterfaces) |
| { |
| $Added_Number += 1; |
| my $Signature = highLight_ItalicColor($Symbol, 2); |
| if($NameSpace) { |
| $Signature = cutNs($Signature, $NameSpace); |
| } |
| |
| if($Symbol=~/\A(_Z|\?)/) { |
| $ADDED_INTERFACES .= insertIDs($ContentSpanStart.$Signature.$ContentSpanEnd."<br/>\n".$ContentDivStart."<span class='mngl'>$Symbol</span>\n<br/>\n<br/>\n".$ContentDivEnd."\n"); |
| } |
| else { |
| $ADDED_INTERFACES .= "<span class=\"iname\">".$Signature."</span><br/>\n"; |
| } |
| } |
| $ADDED_INTERFACES .= "<br/>\n"; |
| } |
| } |
| } |
| if($ADDED_INTERFACES) |
| { |
| my $Anchor = "<a name='Added'></a>"; |
| if($In::Opt{"JoinReport"}) { |
| $Anchor = "<a name='".$Level."_Added'></a>"; |
| } |
| if($In::Opt{"OldStyle"}) { |
| $ADDED_INTERFACES = "<h2>Added Symbols ($Added_Number)</h2><hr/>\n".$ADDED_INTERFACES; |
| } |
| else { |
| $ADDED_INTERFACES = "<h2>Added Symbols <span".getStyle("I", "Added", $Added_Number)."> $Added_Number </span></h2><hr/>\n".$ADDED_INTERFACES; |
| } |
| $ADDED_INTERFACES = $Anchor.$ADDED_INTERFACES.$TOP_REF."<br/>\n"; |
| } |
| } |
| return $ADDED_INTERFACES; |
| } |
| |
| sub getReportRemoved($) |
| { |
| my $Level = $_[0]; |
| my $REMOVED_INTERFACES = ""; |
| my %ReportMap = (); |
| foreach my $Symbol (sort keys(%{$CompatProblems{$Level}})) |
| { |
| foreach my $Kind (sort keys(%{$CompatProblems{$Level}{$Symbol}})) |
| { |
| if($Kind eq "Removed_Symbol") |
| { |
| my $HeaderName = $CompSign{1}{$Symbol}{"Header"}; |
| if(not $HeaderName) { |
| $HeaderName = $CompSign{1}{$Symbol}{"Source"}; |
| } |
| |
| my $DyLib = $In::ABI{1}{"SymLib"}{$Symbol}; |
| if($Level eq "Source" and $In::Opt{"ReportFormat"} eq "html") |
| { # do not show library name in HTML report |
| $DyLib = ""; |
| } |
| $ReportMap{$HeaderName}{$DyLib}{$Symbol} = 1; |
| } |
| } |
| } |
| if($In::Opt{"ReportFormat"} eq "xml") |
| { # XML |
| foreach my $HeaderName (sort {lc($a) cmp lc($b)} keys(%ReportMap)) |
| { |
| $REMOVED_INTERFACES .= " <header name=\"$HeaderName\">\n"; |
| foreach my $DyLib (sort {lc($a) cmp lc($b)} keys(%{$ReportMap{$HeaderName}})) |
| { |
| $REMOVED_INTERFACES .= " <library name=\"$DyLib\">\n"; |
| foreach my $Symbol (keys(%{$ReportMap{$HeaderName}{$DyLib}})) { |
| $REMOVED_INTERFACES .= " <name>$Symbol</name>\n"; |
| } |
| $REMOVED_INTERFACES .= " </library>\n"; |
| } |
| $REMOVED_INTERFACES .= " </header>\n"; |
| } |
| $REMOVED_INTERFACES = "<removed_symbols>\n".$REMOVED_INTERFACES."</removed_symbols>\n\n"; |
| } |
| else |
| { # HTML |
| my $Removed_Number = 0; |
| foreach my $HeaderName (sort {lc($a) cmp lc($b)} keys(%ReportMap)) |
| { |
| foreach my $DyLib (sort {lc($a) cmp lc($b)} keys(%{$ReportMap{$HeaderName}})) |
| { |
| my %NameSpaceSymbols = (); |
| foreach my $Interface (keys(%{$ReportMap{$HeaderName}{$DyLib}})) { |
| $NameSpaceSymbols{selectSymbolNs($Interface, 1)}{$Interface} = 1; |
| } |
| foreach my $NameSpace (sort keys(%NameSpaceSymbols)) |
| { |
| $REMOVED_INTERFACES .= getTitle($HeaderName, $DyLib, $NameSpace); |
| my @SortedInterfaces = sort {lc($CompSign{1}{$a}{"Unmangled"}) cmp lc($CompSign{1}{$b}{"Unmangled"})} sort {lc($a) cmp lc($b)} keys(%{$NameSpaceSymbols{$NameSpace}}); |
| foreach my $Symbol (@SortedInterfaces) |
| { |
| $Removed_Number += 1; |
| my $Signature = highLight_ItalicColor($Symbol, 1); |
| if($NameSpace) { |
| $Signature = cutNs($Signature, $NameSpace); |
| } |
| if($Symbol=~/\A(_Z|\?)/) { |
| $REMOVED_INTERFACES .= insertIDs($ContentSpanStart.$Signature.$ContentSpanEnd."<br/>\n".$ContentDivStart."<span class='mngl'>$Symbol</span>\n<br/>\n<br/>\n".$ContentDivEnd."\n"); |
| } |
| else { |
| $REMOVED_INTERFACES .= "<span class=\"iname\">".$Signature."</span><br/>\n"; |
| } |
| } |
| } |
| $REMOVED_INTERFACES .= "<br/>\n"; |
| } |
| } |
| if($REMOVED_INTERFACES) |
| { |
| my $Anchor = "<a name='Removed'></a><a name='Withdrawn'></a>"; |
| if($In::Opt{"JoinReport"}) { |
| $Anchor = "<a name='".$Level."_Removed'></a><a name='".$Level."_Withdrawn'></a>"; |
| } |
| if($In::Opt{"OldStyle"}) { |
| $REMOVED_INTERFACES = "<h2>Removed Symbols ($Removed_Number)</h2><hr/>\n".$REMOVED_INTERFACES; |
| } |
| else { |
| $REMOVED_INTERFACES = "<h2>Removed Symbols <span".getStyle("I", "Removed", $Removed_Number)."> $Removed_Number </span></h2><hr/>\n".$REMOVED_INTERFACES; |
| } |
| |
| $REMOVED_INTERFACES = $Anchor.$REMOVED_INTERFACES.$TOP_REF."<br/>\n"; |
| } |
| } |
| return $REMOVED_INTERFACES; |
| } |
| |
| sub getXmlParams($$) |
| { |
| my ($Content, $Problem) = @_; |
| |
| my %XMLparams = (); |
| foreach my $Attr (sort {$b cmp $a} keys(%{$Problem})) |
| { |
| my $Macro = "\@".lc($Attr); |
| if($Content=~/\Q$Macro\E/) |
| { |
| my $Value = $Problem->{$Attr}; |
| |
| if($Attr eq "Param_Pos") { |
| $Value = showPos($Value); |
| } |
| |
| $XMLparams{lc($Attr)} = $Value; |
| } |
| } |
| my @PString = (); |
| foreach my $P (sort {$b cmp $a} keys(%XMLparams)) { |
| push(@PString, $P."=\"".xmlSpecChars($XMLparams{$P})."\""); |
| } |
| if(@PString) { |
| return " ".join(" ", @PString); |
| } |
| return ""; |
| } |
| |
| sub addMarkup($) |
| { |
| my $Content = $_[0]; |
| |
| # auto-markup |
| $Content=~s/\n[ ]*//; # spaces |
| $Content=~s!(\@\w+\s*\(\@\w+\))!<nowrap>$1</nowrap>!g; # @old_type (@old_size) |
| $Content=~s!(... \(\w+\))!<nowrap><b>$1</b></nowrap>!g; # ... (va_list) |
| $Content=~s!<nowrap>(.+?)</nowrap>!<span class='nowrap'>$1</span>!g; |
| $Content=~s!([2-9]\))!<br/>$1!g; # 1), 2), ... |
| |
| if($Content=~/\ANOTE:/) |
| { # notes |
| $Content=~s!(NOTE):!<b>$1</b>:!g; |
| } |
| else { |
| $Content=~s!(NOTE):!<br/><br/><b>$1</b>:!g; |
| } |
| $Content=~s! (out)-! <b>$1</b>-!g; # out-parameters |
| |
| my @Keywords = ( |
| "void", |
| "const", |
| "static", |
| "restrict", |
| "volatile", |
| "register", |
| "virtual" |
| ); |
| my $MKeys = join("|", @Keywords); |
| foreach (@Keywords) { |
| $MKeys .= "|non-".$_; |
| } |
| $Content=~s!(added\s*|to\s*|from\s*|became\s*)($MKeys)([^\w-]|\Z)!$1<b>$2</b>$3!ig; # intrinsic types, modifiers |
| |
| # Markdown |
| $Content=~s!\*\*([\w\-]+?)\*\*!<b>$1</b>!ig; |
| $Content=~s!\*([\w\-]+?)\*!<i>$1</i>!ig; |
| |
| return $Content; |
| } |
| |
| sub applyMacroses($$$$) |
| { |
| my ($Level, $Kind, $Content, $Problem) = @_; |
| |
| $Problem->{"Word_Size"} = $In::ABI{2}{"WordSize"}; |
| $Content = addMarkup($Content); |
| |
| # macros |
| foreach my $Attr (sort {$b cmp $a} keys(%{$Problem})) |
| { |
| my $Macro = "\@".lc($Attr); |
| my $Value = $Problem->{$Attr}; |
| if(not defined $Value |
| or $Value eq "") { |
| next; |
| } |
| |
| if(index($Content, $Macro)==-1) { |
| next; |
| } |
| |
| if($Attr eq "Param_Pos") { |
| $Value = showPos($Value); |
| } |
| |
| if($Value=~/\s/) { |
| $Value = "<span class='value'>".specChars($Value)."</span>"; |
| } |
| elsif($Value=~/\A\d+\Z/ |
| and ($Attr eq "Old_Size" or $Attr eq "New_Size")) |
| { # bits to bytes |
| if($Value % $BYTE) |
| { # bits |
| if($Value==1) { |
| $Value = "<b>".$Value."</b> bit"; |
| } |
| else { |
| $Value = "<b>".$Value."</b> bits"; |
| } |
| } |
| else |
| { # bytes |
| $Value /= $BYTE; |
| if($Value==1) { |
| $Value = "<b>".$Value."</b> byte"; |
| } |
| else { |
| $Value = "<b>".$Value."</b> bytes"; |
| } |
| } |
| } |
| else |
| { |
| my $Fmt = "Class|Name|Qual|HTML|Desc"; |
| if($Kind!~/Overridden/) { |
| $Fmt = "Name|Qual|HTML|Desc"; |
| } |
| |
| my $V1 = (defined $CompSign{1}{$Value} and defined $CompSign{1}{$Value}{"ShortName"}); |
| my $V2 = (defined $CompSign{2}{$Value} and defined $CompSign{2}{$Value}{"ShortName"}); |
| |
| if($Kind!~/Symbol_Became|Symbol_Changed|Method_Became/ |
| and ($V1 or $V2)) |
| { # symbols |
| if($V1) { |
| $Value = blackName(getSignature($Value, 1, $Fmt)); |
| } |
| else { |
| $Value = blackName(getSignature($Value, 2, $Fmt)); |
| } |
| } |
| else |
| { |
| $Value = "<b>".specChars($Value)."</b>"; |
| } |
| } |
| $Content=~s/\Q$Macro\E/$Value/g; |
| } |
| |
| if($Content=~/(\A|[^\@\w])\@\w/) |
| { |
| if(not $IncompleteRules{$Level}{$Kind}) |
| { # only one warning |
| printMsg("WARNING", "incomplete rule \"$Kind\" (\"$Level\")"); |
| $IncompleteRules{$Level}{$Kind} = 1; |
| } |
| } |
| return $Content; |
| } |
| |
| sub getReportSymbolProblems($$) |
| { |
| my ($TargetSeverity, $Level) = @_; |
| my $INTERFACE_PROBLEMS = ""; |
| my (%ReportMap, %SymbolChanges) = (); |
| |
| foreach my $Symbol (sort keys(%{$CompatProblems{$Level}})) |
| { |
| my ($SN, $SS, $SV) = symbolParts($Symbol); |
| if($SV and defined $CompatProblems{$Level}{$SN}) { |
| next; |
| } |
| |
| if(not defined $CompSign{1}{$Symbol}) |
| { # added symbols |
| next; |
| } |
| |
| my $HeaderName = $CompSign{1}{$Symbol}{"Header"}; |
| if(not $HeaderName) { |
| $HeaderName = $CompSign{1}{$Symbol}{"Source"}; |
| } |
| |
| my $DyLib = $In::ABI{1}{"SymLib"}{$Symbol}; |
| if(not $DyLib and my $VSym = $In::ABI{1}{"SymbolVersion"}{$Symbol}) |
| { # Symbol with Version |
| $DyLib = $In::ABI{1}{"SymLib"}{$VSym}; |
| } |
| if(not $DyLib) |
| { # const global data |
| $DyLib = ""; |
| } |
| if($Level eq "Source" and $In::Opt{"ReportFormat"} eq "html") |
| { # do not show library name in HTML report |
| $DyLib = ""; |
| } |
| |
| foreach my $Kind (sort keys(%{$CompatProblems{$Level}{$Symbol}})) |
| { |
| if($CompatRules{$Level}{$Kind}{"Kind"} eq "Symbols" |
| and $Kind ne "Added_Symbol" and $Kind ne "Removed_Symbol") |
| { |
| my $Severity = $CompatRules{$Level}{$Kind}{"Severity"}; |
| if($Severity eq $TargetSeverity) |
| { |
| $SymbolChanges{$Symbol}{$Kind} = $CompatProblems{$Level}{$Symbol}{$Kind}; |
| $ReportMap{$HeaderName}{$DyLib}{$Symbol} = 1; |
| } |
| } |
| } |
| } |
| |
| if($In::Opt{"ReportFormat"} eq "xml") |
| { # XML |
| foreach my $HeaderName (sort {lc($a) cmp lc($b)} keys(%ReportMap)) |
| { |
| $INTERFACE_PROBLEMS .= " <header name=\"$HeaderName\">\n"; |
| foreach my $DyLib (sort {lc($a) cmp lc($b)} keys(%{$ReportMap{$HeaderName}})) |
| { |
| $INTERFACE_PROBLEMS .= " <library name=\"$DyLib\">\n"; |
| |
| my @SortedInterfaces = sort {lc($CompSign{1}{$a}{"Unmangled"}) cmp lc($CompSign{1}{$b}{"Unmangled"})} sort {lc($a) cmp lc($b)} keys(%{$ReportMap{$HeaderName}{$DyLib}}); |
| foreach my $Symbol (@SortedInterfaces) |
| { |
| $INTERFACE_PROBLEMS .= " <symbol name=\"$Symbol\">\n"; |
| foreach my $Kind (sort keys(%{$SymbolChanges{$Symbol}})) |
| { |
| foreach my $Loc (sort keys(%{$SymbolChanges{$Symbol}{$Kind}})) |
| { |
| my $ProblemAttr = $SymbolChanges{$Symbol}{$Kind}{$Loc}; |
| |
| $INTERFACE_PROBLEMS .= " <problem id=\"$Kind\">\n"; |
| my $Change = $CompatRules{$Level}{$Kind}{"Change"}; |
| $INTERFACE_PROBLEMS .= " <change".getXmlParams($Change, $ProblemAttr).">$Change</change>\n"; |
| my $Effect = $CompatRules{$Level}{$Kind}{"Effect"}; |
| $INTERFACE_PROBLEMS .= " <effect".getXmlParams($Effect, $ProblemAttr).">$Effect</effect>\n"; |
| if(my $Overcome = $CompatRules{$Level}{$Kind}{"Overcome"}) { |
| $INTERFACE_PROBLEMS .= " <overcome".getXmlParams($Overcome, $ProblemAttr).">$Overcome</overcome>\n"; |
| } |
| $INTERFACE_PROBLEMS .= " </problem>\n"; |
| } |
| } |
| $INTERFACE_PROBLEMS .= " </symbol>\n"; |
| } |
| $INTERFACE_PROBLEMS .= " </library>\n"; |
| } |
| $INTERFACE_PROBLEMS .= " </header>\n"; |
| } |
| $INTERFACE_PROBLEMS = "<problems_with_symbols severity=\"$TargetSeverity\">\n".$INTERFACE_PROBLEMS."</problems_with_symbols>\n\n"; |
| } |
| else |
| { # HTML |
| my $ProblemsNum = 0; |
| foreach my $HeaderName (sort {lc($a) cmp lc($b)} keys(%ReportMap)) |
| { |
| foreach my $DyLib (sort {lc($a) cmp lc($b)} keys(%{$ReportMap{$HeaderName}})) |
| { |
| my (%NameSpaceSymbols, %NewSignature) = (); |
| foreach my $Symbol (keys(%{$ReportMap{$HeaderName}{$DyLib}})) { |
| $NameSpaceSymbols{selectSymbolNs($Symbol, 1)}{$Symbol} = 1; |
| } |
| foreach my $NameSpace (sort keys(%NameSpaceSymbols)) |
| { |
| $INTERFACE_PROBLEMS .= getTitle($HeaderName, $DyLib, $NameSpace); |
| |
| my @SortedInterfaces = sort {lc($CompSign{1}{$a}{"Unmangled"}) cmp lc($CompSign{1}{$b}{"Unmangled"})} sort {lc($a) cmp lc($b)} keys(%{$NameSpaceSymbols{$NameSpace}}); |
| foreach my $Symbol (@SortedInterfaces) |
| { |
| my $SYMBOL_REPORT = ""; |
| my $ProblemNum = 1; |
| foreach my $Kind (sort keys(%{$SymbolChanges{$Symbol}})) |
| { |
| foreach my $Loc (sort keys(%{$SymbolChanges{$Symbol}{$Kind}})) |
| { |
| my $ProblemAttr = $SymbolChanges{$Symbol}{$Kind}{$Loc}; |
| |
| if(my $NSign = $ProblemAttr->{"New_Signature"}) { |
| $NewSignature{$Symbol} = $NSign; |
| } |
| |
| if(my $Change = applyMacroses($Level, $Kind, $CompatRules{$Level}{$Kind}{"Change"}, $ProblemAttr)) |
| { |
| my $Effect = applyMacroses($Level, $Kind, $CompatRules{$Level}{$Kind}{"Effect"}, $ProblemAttr); |
| $SYMBOL_REPORT .= "<tr>\n<th>$ProblemNum</th>\n<td>".$Change."</td>\n<td>".$Effect."</td>\n</tr>\n"; |
| $ProblemNum += 1; |
| $ProblemsNum += 1; |
| } |
| } |
| } |
| $ProblemNum -= 1; |
| if($SYMBOL_REPORT) |
| { |
| my $ShowSymbol = highLight_ItalicColor($Symbol, 1); |
| |
| if($NameSpace) |
| { |
| $SYMBOL_REPORT = cutNs($SYMBOL_REPORT, $NameSpace); |
| $ShowSymbol = cutNs($ShowSymbol, $NameSpace); |
| } |
| |
| $INTERFACE_PROBLEMS .= $ContentSpanStart."<span class='ext'>[+]</span> ".$ShowSymbol; |
| if($In::Opt{"OldStyle"}) { |
| $INTERFACE_PROBLEMS .= " ($ProblemNum)"; |
| } |
| else { |
| $INTERFACE_PROBLEMS .= " <span".getStyle("I", $TargetSeverity, $ProblemNum)."> $ProblemNum </span>"; |
| } |
| $INTERFACE_PROBLEMS .= $ContentSpanEnd."<br/>\n"; |
| $INTERFACE_PROBLEMS .= $ContentDivStart."\n"; |
| |
| if(my $NSign = $NewSignature{$Symbol}) |
| { # argument list changed to |
| $NSign = highLight_ItalicColor($NSign, 2); |
| if($NameSpace) { |
| $NSign = cutNs($NSign, $NameSpace); |
| } |
| $INTERFACE_PROBLEMS .= "\n<span class='new_sign_lbl'>⇣</span>\n<br/>\n<span class='new_sign'>".$NSign."</span><br/>\n"; |
| } |
| |
| if($Symbol=~/\A(_Z|\?)/) { |
| $INTERFACE_PROBLEMS .= "<span class='mngl pleft'>$Symbol</span><br/>\n"; |
| } |
| |
| $INTERFACE_PROBLEMS .= "<table class='ptable'>\n<tr>\n<th class='pn'></th>\n<th class='chg'>Change</th>\n<th>Effect</th>\n</tr>\n$SYMBOL_REPORT</table>\n<br/>\n"; |
| $INTERFACE_PROBLEMS .= $ContentDivEnd; |
| } |
| } |
| $INTERFACE_PROBLEMS .= "<br/>\n"; |
| } |
| } |
| } |
| |
| if($INTERFACE_PROBLEMS) |
| { |
| $INTERFACE_PROBLEMS = insertIDs($INTERFACE_PROBLEMS); |
| my $Title = "Problems with Symbols, $TargetSeverity Severity"; |
| if($TargetSeverity eq "Safe") |
| { # Safe Changes |
| $Title = "Other Changes in Symbols"; |
| } |
| if($In::Opt{"OldStyle"}) { |
| $INTERFACE_PROBLEMS = "<h2>$Title ($ProblemsNum)</h2><hr/>\n".$INTERFACE_PROBLEMS; |
| } |
| else { |
| $INTERFACE_PROBLEMS = "<h2>$Title <span".getStyle("I", $TargetSeverity, $ProblemsNum)."> $ProblemsNum </span></h2><hr/>\n".$INTERFACE_PROBLEMS; |
| } |
| $INTERFACE_PROBLEMS = "<a name=\'".getAnchor("Symbol", $Level, $TargetSeverity)."\'></a><a name=\'".getAnchor("Interface", $Level, $TargetSeverity)."\'></a>\n".$INTERFACE_PROBLEMS.$TOP_REF."<br/>\n"; |
| } |
| } |
| return $INTERFACE_PROBLEMS; |
| } |
| |
| sub cutNs($$) |
| { |
| my ($N, $Ns) = @_; |
| $N=~s/\b\Q$Ns\E:://g; |
| return $N; |
| } |
| |
| sub getReportTypeProblems($$) |
| { |
| my ($TargetSeverity, $Level) = @_; |
| my $TYPE_PROBLEMS = ""; |
| |
| my %ReportMap = (); |
| my %TypeChanges_Sev = (); |
| |
| foreach my $TypeName (keys(%{$TypeChanges{$Level}})) |
| { |
| my $Tid = $TName_Tid{1}{$TypeName}; |
| my $HeaderName = $TypeInfo{1}{$Tid}{"Header"}; |
| if(not $HeaderName) { |
| $HeaderName = $TypeInfo{1}{$Tid}{"Source"}; |
| } |
| |
| foreach my $Kind (keys(%{$TypeChanges{$Level}{$TypeName}})) |
| { |
| if($CompatRules{$Level}{$Kind}{"Severity"} ne $TargetSeverity) { |
| next; |
| } |
| |
| foreach my $Loc (keys(%{$TypeChanges{$Level}{$TypeName}{$Kind}})) |
| { |
| $ReportMap{$HeaderName}{$TypeName} = 1; |
| $TypeChanges_Sev{$TypeName}{$Kind}{$Loc} = $TypeChanges{$Level}{$TypeName}{$Kind}{$Loc}; |
| } |
| } |
| } |
| |
| if($In::Opt{"ReportFormat"} eq "xml") |
| { # XML |
| foreach my $HeaderName (sort {lc($a) cmp lc($b)} keys(%ReportMap)) |
| { |
| $TYPE_PROBLEMS .= " <header name=\"$HeaderName\">\n"; |
| foreach my $TypeName (keys(%{$ReportMap{$HeaderName}})) |
| { |
| my (%Kinds_Locations, %Kinds_Target) = (); |
| $TYPE_PROBLEMS .= " <type name=\"".xmlSpecChars($TypeName)."\">\n"; |
| foreach my $Kind (sort {$b=~/Size/ <=> $a=~/Size/} sort keys(%{$TypeChanges_Sev{$TypeName}})) |
| { |
| foreach my $Loc (sort {cmpLocations($b, $a)} sort keys(%{$TypeChanges_Sev{$TypeName}{$Kind}})) |
| { |
| $Kinds_Locations{$Kind}{$Loc} = 1; |
| |
| my $Target = $TypeChanges_Sev{$TypeName}{$Kind}{$Loc}{"Target"}; |
| if($Kinds_Target{$Kind}{$Target}) { |
| next; |
| } |
| $Kinds_Target{$Kind}{$Target} = 1; |
| |
| my $ProblemAttr = $TypeChanges_Sev{$TypeName}{$Kind}{$Loc}; |
| $TYPE_PROBLEMS .= " <problem id=\"$Kind\">\n"; |
| my $Change = $CompatRules{$Level}{$Kind}{"Change"}; |
| $TYPE_PROBLEMS .= " <change".getXmlParams($Change, $ProblemAttr).">$Change</change>\n"; |
| my $Effect = $CompatRules{$Level}{$Kind}{"Effect"}; |
| $TYPE_PROBLEMS .= " <effect".getXmlParams($Effect, $ProblemAttr).">$Effect</effect>\n"; |
| if(my $Overcome = $CompatRules{$Level}{$Kind}{"Overcome"}) { |
| $TYPE_PROBLEMS .= " <overcome".getXmlParams($Overcome, $ProblemAttr).">$Overcome</overcome>\n"; |
| } |
| $TYPE_PROBLEMS .= " </problem>\n"; |
| } |
| } |
| $TYPE_PROBLEMS .= getAffectedSymbols($Level, $TypeName, \%Kinds_Locations); |
| if($Level eq "Binary" and grep {$_=~/Virtual|Base_Class/} keys(%Kinds_Locations)) { |
| $TYPE_PROBLEMS .= showVTables($TypeName); |
| } |
| $TYPE_PROBLEMS .= " </type>\n"; |
| } |
| $TYPE_PROBLEMS .= " </header>\n"; |
| } |
| $TYPE_PROBLEMS = "<problems_with_types severity=\"$TargetSeverity\">\n".$TYPE_PROBLEMS."</problems_with_types>\n\n"; |
| } |
| else |
| { # HTML |
| my $ProblemsNum = 0; |
| foreach my $HeaderName (sort {lc($a) cmp lc($b)} keys(%ReportMap)) |
| { |
| my (%NameSpace_Type) = (); |
| foreach my $TypeName (keys(%{$ReportMap{$HeaderName}})) { |
| $NameSpace_Type{selectTypeNs($TypeName, 1)}{$TypeName} = 1; |
| } |
| foreach my $NameSpace (sort keys(%NameSpace_Type)) |
| { |
| $TYPE_PROBLEMS .= getTitle($HeaderName, "", $NameSpace); |
| my @SortedTypes = sort {lc(showType($a, 0, 1)) cmp lc(showType($b, 0, 1))} keys(%{$NameSpace_Type{$NameSpace}}); |
| foreach my $TypeName (@SortedTypes) |
| { |
| my $ProblemNum = 1; |
| my $TYPE_REPORT = ""; |
| my (%Kinds_Locations, %Kinds_Target) = (); |
| |
| foreach my $Kind (sort {(index($b, "Size")!=-1) cmp (index($a, "Size")!=-1)} sort keys(%{$TypeChanges_Sev{$TypeName}})) |
| { |
| foreach my $Loc (sort {cmpLocations($b, $a)} sort keys(%{$TypeChanges_Sev{$TypeName}{$Kind}})) |
| { |
| $Kinds_Locations{$Kind}{$Loc} = 1; |
| |
| my $Target = $TypeChanges_Sev{$TypeName}{$Kind}{$Loc}{"Target"}; |
| if($Kinds_Target{$Kind}{$Target}) { |
| next; |
| } |
| $Kinds_Target{$Kind}{$Target} = 1; |
| |
| my $ProblemAttr = $TypeChanges_Sev{$TypeName}{$Kind}{$Loc}; |
| if(my $Change = applyMacroses($Level, $Kind, $CompatRules{$Level}{$Kind}{"Change"}, $ProblemAttr)) |
| { |
| my $Effect = applyMacroses($Level, $Kind, $CompatRules{$Level}{$Kind}{"Effect"}, $ProblemAttr); |
| $TYPE_REPORT .= "<tr>\n<th>$ProblemNum</th>\n<td>".$Change."</td>\n<td>$Effect</td>\n</tr>\n"; |
| $ProblemNum += 1; |
| $ProblemsNum += 1; |
| } |
| } |
| } |
| $ProblemNum -= 1; |
| if($TYPE_REPORT) |
| { |
| my $Affected = getAffectedSymbols($Level, $TypeName, \%Kinds_Locations); |
| my $ShowVTables = ""; |
| if($Level eq "Binary" and grep {$_=~/Virtual|Base_Class/} keys(%Kinds_Locations)) { |
| $ShowVTables = showVTables($TypeName); |
| } |
| |
| my $ShowType = showType($TypeName, 1, 1); |
| |
| if($NameSpace) |
| { |
| $TYPE_REPORT = cutNs($TYPE_REPORT, $NameSpace); |
| $ShowType = cutNs($ShowType, $NameSpace); |
| $Affected = cutNs($Affected, $NameSpace); |
| $ShowVTables = cutNs($ShowVTables, $NameSpace); |
| } |
| |
| $TYPE_PROBLEMS .= $ContentSpanStart."<span class='ext'>[+]</span> ".$ShowType; |
| if($In::Opt{"OldStyle"}) { |
| $TYPE_PROBLEMS .= " ($ProblemNum)"; |
| } |
| else { |
| $TYPE_PROBLEMS .= " <span".getStyle("T", $TargetSeverity, $ProblemNum)."> $ProblemNum </span>"; |
| } |
| $TYPE_PROBLEMS .= $ContentSpanEnd; |
| $TYPE_PROBLEMS .= "<br/>\n".$ContentDivStart."<table class='ptable'><tr>\n"; |
| $TYPE_PROBLEMS .= "<th class='pn'></th>\n<th class='chg'>Change</th>\n"; |
| $TYPE_PROBLEMS .= "<th>Effect</th></tr>".$TYPE_REPORT."</table>\n"; |
| $TYPE_PROBLEMS .= $ShowVTables.$Affected."<br/><br/>".$ContentDivEnd."\n"; |
| } |
| } |
| $TYPE_PROBLEMS .= "<br/>\n"; |
| } |
| } |
| |
| if($TYPE_PROBLEMS) |
| { |
| $TYPE_PROBLEMS = insertIDs($TYPE_PROBLEMS); |
| my $Title = "Problems with Data Types, $TargetSeverity Severity"; |
| if($TargetSeverity eq "Safe") |
| { # Safe Changes |
| $Title = "Other Changes in Data Types"; |
| } |
| if($In::Opt{"OldStyle"}) { |
| $TYPE_PROBLEMS = "<h2>$Title ($ProblemsNum)</h2><hr/>\n".$TYPE_PROBLEMS; |
| } |
| else { |
| $TYPE_PROBLEMS = "<h2>$Title <span".getStyle("T", $TargetSeverity, $ProblemsNum)."> $ProblemsNum </span></h2><hr/>\n".$TYPE_PROBLEMS; |
| } |
| $TYPE_PROBLEMS = "<a name=\'".getAnchor("Type", $Level, $TargetSeverity)."\'></a>\n".$TYPE_PROBLEMS.$TOP_REF."<br/>\n"; |
| } |
| } |
| return $TYPE_PROBLEMS; |
| } |
| |
| sub showType($$$) |
| { |
| my ($Name, $Html, $LVer) = @_; |
| my $TType = $TypeInfo{$LVer}{$TName_Tid{$LVer}{$Name}}{"Type"}; |
| $TType = lc($TType); |
| if($TType=~/struct|union|enum/) { |
| $Name=~s/\A\Q$TType\E //g; |
| } |
| if($Html) { |
| $Name = "<span class='ttype'>".$TType."</span> ".specChars($Name); |
| } |
| else { |
| $Name = $TType." ".$Name; |
| } |
| return $Name; |
| } |
| |
| sub getAnchor($$$) |
| { |
| my ($Kind, $Level, $Severity) = @_; |
| if($In::Opt{"JoinReport"}) |
| { |
| if($Severity eq "Safe") { |
| return "Other_".$Level."_Changes_In_".$Kind."s"; |
| } |
| else { |
| return $Kind."_".$Level."_Problems_".$Severity; |
| } |
| } |
| else |
| { |
| if($Severity eq "Safe") { |
| return "Other_Changes_In_".$Kind."s"; |
| } |
| else { |
| return $Kind."_Problems_".$Severity; |
| } |
| } |
| } |
| |
| sub showVTables($) |
| { |
| my $TypeName = $_[0]; |
| my $TypeId1 = $TName_Tid{1}{$TypeName}; |
| my %Type1 = getType($TypeId1, 1); |
| if(defined $Type1{"VTable"} |
| and keys(%{$Type1{"VTable"}})) |
| { |
| my $TypeId2 = $TName_Tid{2}{$TypeName}; |
| my %Type2 = getType($TypeId2, 2); |
| if(defined $Type2{"VTable"} |
| and keys(%{$Type2{"VTable"}})) |
| { |
| my %Indexes = map {$_=>1} (keys(%{$Type1{"VTable"}}), keys(%{$Type2{"VTable"}})); |
| my %Entries = (); |
| foreach my $Index (sort {$a<=>$b} (keys(%Indexes))) |
| { |
| $Entries{$Index}{"E1"} = simpleVEntry($Type1{"VTable"}{$Index}); |
| $Entries{$Index}{"E2"} = simpleVEntry($Type2{"VTable"}{$Index}); |
| } |
| my $VTABLES = ""; |
| if($In::Opt{"ReportFormat"} eq "xml") |
| { # XML |
| $VTABLES .= " <vtable>\n"; |
| foreach my $Index (sort {$a<=>$b} (keys(%Entries))) |
| { |
| $VTABLES .= " <entry offset=\"".$Index."\">\n"; |
| $VTABLES .= " <old>".xmlSpecChars($Entries{$Index}{"E1"})."</old>\n"; |
| $VTABLES .= " <new>".xmlSpecChars($Entries{$Index}{"E2"})."</new>\n"; |
| $VTABLES .= " </entry>\n"; |
| } |
| $VTABLES .= " </vtable>\n\n"; |
| } |
| else |
| { # HTML |
| $VTABLES .= "<table class='vtable'>"; |
| $VTABLES .= "<tr><th>Offset</th>"; |
| $VTABLES .= "<th>Virtual Table (Old) - ".(keys(%{$Type1{"VTable"}}))." entries</th>"; |
| $VTABLES .= "<th>Virtual Table (New) - ".(keys(%{$Type2{"VTable"}}))." entries</th></tr>"; |
| foreach my $Index (sort {$a<=>$b} (keys(%Entries))) |
| { |
| my ($Color1, $Color2) = ("", ""); |
| |
| my $E1 = $Entries{$Index}{"E1"}; |
| my $E2 = $Entries{$Index}{"E2"}; |
| |
| if($E1 ne $E2 |
| and $E1!~/ 0x/ |
| and $E2!~/ 0x/) |
| { |
| if($Entries{$Index}{"E1"}) |
| { |
| $Color1 = " class='failed'"; |
| $Color2 = " class='failed'"; |
| } |
| else { |
| $Color2 = " class='warning'"; |
| } |
| } |
| $VTABLES .= "<tr><th>".$Index."</th>\n"; |
| $VTABLES .= "<td$Color1>".specChars($Entries{$Index}{"E1"})."</td>\n"; |
| $VTABLES .= "<td$Color2>".specChars($Entries{$Index}{"E2"})."</td></tr>\n"; |
| } |
| $VTABLES .= "</table><br/>\n"; |
| $VTABLES = $ContentDivStart.$VTABLES.$ContentDivEnd; |
| $VTABLES = $ContentSpanStart_Info."[+] show v-table (old and new)".$ContentSpanEnd."<br/>\n".$VTABLES; |
| } |
| return $VTABLES; |
| } |
| } |
| return ""; |
| } |
| |
| sub simpleVEntry($) |
| { |
| my $VEntry = $_[0]; |
| if(not defined $VEntry |
| or $VEntry eq "") { |
| return ""; |
| } |
| |
| $VEntry=~s/ \[.+?\]\Z//; # support for ABI Dumper |
| $VEntry=~s/\A(.+)::(_ZThn.+)\Z/$2/; # thunks |
| $VEntry=~s/_ZTI\w+/typeinfo/g; # typeinfo |
| if($VEntry=~/\A_ZThn.+\Z/) { |
| $VEntry = "non-virtual thunk"; |
| } |
| $VEntry=~s/\A\(int \(\*\)\(...\)\)\s*([a-z_])/$1/i; |
| # support for old GCC versions |
| $VEntry=~s/\A0u\Z/(int (*)(...))0/; |
| $VEntry=~s/\A4294967268u\Z/(int (*)(...))-0x000000004/; |
| $VEntry=~s/\A&_Z\Z/& _Z/; |
| $VEntry=~s/([^:]+)::\~([^:]+)\Z/~$1/; # destructors |
| return $VEntry; |
| } |
| |
| sub adjustParamPos($$$) |
| { |
| my ($Pos, $Symbol, $LVer) = @_; |
| if(defined $CompSign{$LVer}{$Symbol}) |
| { |
| if(not $CompSign{$LVer}{$Symbol}{"Static"} |
| and $CompSign{$LVer}{$Symbol}{"Class"}) |
| { |
| return $Pos-1; |
| } |
| |
| return $Pos; |
| } |
| |
| return undef; |
| } |
| |
| sub getParamPos($$$) |
| { |
| my ($Name, $Symbol, $LVer) = @_; |
| |
| if(defined $CompSign{$LVer}{$Symbol} |
| and defined $CompSign{$LVer}{$Symbol}{"Param"}) |
| { |
| my $Info = $CompSign{$LVer}{$Symbol}; |
| foreach (keys(%{$Info->{"Param"}})) |
| { |
| if($Info->{"Param"}{$_}{"name"} eq $Name) |
| { |
| return $_; |
| } |
| } |
| } |
| |
| return undef; |
| } |
| |
| sub getParamName($) |
| { |
| my $Loc = $_[0]; |
| $Loc=~s/\->.*//g; |
| return $Loc; |
| } |
| |
| sub getAffectedSymbols($$$) |
| { |
| my ($Level, $Target_TypeName, $Kinds_Locations) = @_; |
| |
| my $LIMIT = 10; |
| if(defined $In::Opt{"AffectLimit"}) { |
| $LIMIT = $In::Opt{"AffectLimit"}; |
| } |
| |
| my %SymSel = (); |
| |
| foreach my $Kind (sort keys(%{$Kinds_Locations})) |
| { |
| my @Locs = sort {(index($a, "retval")!=-1) cmp (index($b, "retval")!=-1)} sort {length($a)<=>length($b)} sort keys(%{$Kinds_Locations->{$Kind}}); |
| |
| foreach my $Loc (@Locs) |
| { |
| foreach my $Symbol (keys(%{$TypeProblemsIndex{$Level}{$Target_TypeName}{$Kind}{$Loc}})) |
| { |
| if(index($Symbol, "_Z")==0 |
| and $Symbol=~/(C4|C2|D4|D2|D0)[EI]/) |
| { # duplicated problems for C2/C4 constructors, D2/D4 and D0 destructors |
| next; |
| } |
| |
| if(index($Symbol, "\@")!=-1 |
| or index($Symbol, "\$")!=-1) |
| { |
| my ($SN, $SS, $SV) = symbolParts($Symbol); |
| |
| if($Level eq "Source") |
| { # remove symbol version |
| $Symbol = $SN; |
| } |
| |
| if($SV and defined $CompatProblems{$Level}{$SN}) |
| { # duplicated problems for versioned symbols |
| next; |
| } |
| } |
| |
| if(not defined $SymSel{$Symbol}) |
| { |
| $SymSel{$Symbol}{"Kind"} = $Kind; |
| $SymSel{$Symbol}{"Loc"} = $Loc; |
| } |
| } |
| } |
| } |
| |
| my $Affected = ""; |
| my $SNum = 0; |
| |
| if($In::Opt{"ReportFormat"} eq "xml") |
| { # XML |
| $Affected .= " <affected>\n"; |
| |
| foreach my $Symbol (sort {lc($a) cmp lc($b)} keys(%SymSel)) |
| { |
| my $Kind = $SymSel{$Symbol}{"Kind"}; |
| my $Loc = $SymSel{$Symbol}{"Loc"}; |
| |
| my $PName = getParamName($Loc); |
| my $Des = getAffectDesc($Level, $Symbol, $Kind, $Loc); |
| |
| my $Target = ""; |
| if($PName) |
| { |
| $Target .= " param=\"$PName\""; |
| $Des=~s/parameter $PName /parameter \@param /; |
| } |
| elsif($Loc=~/\Aretval(\-|\Z)/i) { |
| $Target .= " affected=\"retval\""; |
| } |
| elsif($Loc=~/\Athis(\-|\Z)/i) { |
| $Target .= " affected=\"this\""; |
| } |
| |
| if($Des=~s/\AField ([^\s]+) /Field \@field /) { |
| $Target .= " field=\"$1\""; |
| } |
| |
| $Affected .= " <symbol name=\"$Symbol\"$Target>\n"; |
| $Affected .= " <comment>".xmlSpecChars($Des)."</comment>\n"; |
| $Affected .= " </symbol>\n"; |
| |
| if(++$SNum>=$LIMIT) { |
| last; |
| } |
| } |
| $Affected .= " </affected>\n"; |
| } |
| else |
| { # HTML |
| foreach my $Symbol (sort {lc($a) cmp lc($b)} keys(%SymSel)) |
| { |
| my $Kind = $SymSel{$Symbol}{"Kind"}; |
| my $Loc = $SymSel{$Symbol}{"Loc"}; |
| |
| my $Des = getAffectDesc($Level, $Symbol, $Kind, $Loc); |
| my $PName = getParamName($Loc); |
| my $Pos = adjustParamPos(getParamPos($PName, $Symbol, 1), $Symbol, 1); |
| |
| $Affected .= "<span class='iname_a'>".getSignature($Symbol, 1, "Class|Name|Param|HTML|Italic|Target=".$Pos)."</span><br/>\n"; |
| $Affected .= "<div class='affect'>".specChars($Des)."</div>\n"; |
| |
| if(++$SNum>=$LIMIT) { |
| last; |
| } |
| } |
| |
| my $Total = keys(%SymSel); |
| |
| if($Total>$LIMIT) { |
| $Affected .= " <b>...</b>\n<br/>\n"; # and others ... |
| } |
| |
| $Affected = "<div class='affected'>".$Affected."</div>\n"; |
| if($Affected) |
| { |
| |
| my $Per = showNum($Total*100/keys(%{$CheckedSymbols{$Level}})); |
| $Affected = $ContentDivStart.$Affected.$ContentDivEnd; |
| $Affected = $ContentSpanStart_Affected."[+] affected symbols: $Total ($Per\%)".$ContentSpanEnd.$Affected; |
| } |
| } |
| |
| return $Affected; |
| } |
| |
| sub cmpLocations($$) |
| { |
| my ($L1, $L2) = @_; |
| if((index($L2, "retval")==0 or index($L2, "this")==0) |
| and (index($L1, "retval")!=0 and index($L1, "this")!=0)) |
| { |
| if(index($L1, "->")==-1) { |
| return 1; |
| } |
| elsif(index($L2, "->")!=-1) { |
| return 1; |
| } |
| } |
| return 0; |
| } |
| |
| sub getAffectDesc($$$$) |
| { |
| my ($Level, $Symbol, $Kind, $Loc) = @_; |
| |
| if($ExtendedSymbols{$Symbol}) { |
| return "This is a symbol from an external library that may use subject library and change the ABI after recompiling."; |
| } |
| |
| my $PAttr = $CompatProblems{$Level}{$Symbol}{$Kind}{$Loc}; |
| |
| $Loc=~s/\A(.*)\-\>(.+?)\Z/$1/; # without the latest affected field |
| |
| my @Sentence = (); |
| |
| if($Kind eq "Overridden_Virtual_Method" |
| or $Kind eq "Overridden_Virtual_Method_B") { |
| push(@Sentence, "The method '".$PAttr->{"New_Value"}."' will be called instead of this method."); |
| } |
| elsif($CompatRules{$Level}{$Kind}{"Kind"} eq "Types") |
| { |
| my %SymInfo = %{$CompSign{1}{$Symbol}}; |
| |
| if($Loc eq "this" or $Kind=~/(\A|_)Virtual(_|\Z)/) |
| { |
| my $MType = "method"; |
| if($SymInfo{"Constructor"}) { |
| $MType = "constructor"; |
| } |
| elsif($SymInfo{"Destructor"}) { |
| $MType = "destructor"; |
| } |
| |
| my $ClassName = $TypeInfo{1}{$SymInfo{"Class"}}{"Name"}; |
| |
| if($ClassName eq $PAttr->{"Type_Name"}) { |
| push(@Sentence, "This $MType is from \'".$PAttr->{"Type_Name"}."\' class."); |
| } |
| else { |
| push(@Sentence, "This $MType is from derived class \'".$ClassName."\'."); |
| } |
| } |
| else |
| { |
| my $TypeID = undef; |
| |
| if($Loc=~/retval/) |
| { # return value |
| if(index($Loc, "->")!=-1) { |
| push(@Sentence, "Field \'".$Loc."\' in the return value"); |
| } |
| else { |
| push(@Sentence, "Return value"); |
| } |
| |
| $TypeID = $SymInfo{"Return"}; |
| } |
| elsif($Loc=~/this/) |
| { # "this" pointer |
| if(index($Loc, "->")!=-1) { |
| push(@Sentence, "Field \'".$Loc."\' in the object of this method"); |
| } |
| else { |
| push(@Sentence, "\'this\' pointer"); |
| } |
| |
| $TypeID = $SymInfo{"Class"}; |
| } |
| else |
| { # parameters |
| my $PName = getParamName($Loc); |
| my $PPos = getParamPos($PName, $Symbol, 1); |
| |
| if(index($Loc, "->")!=-1) { |
| push(@Sentence, "Field \'".$Loc."\' in ".showPos(adjustParamPos($PPos, $Symbol, 1))." parameter"); |
| } |
| else { |
| push(@Sentence, showPos(adjustParamPos($PPos, $Symbol, 1))." parameter"); |
| } |
| if($PName) { |
| push(@Sentence, "\'".$PName."\'"); |
| } |
| |
| $TypeID = $SymInfo{"Param"}{$PPos}{"type"}; |
| } |
| |
| if($Loc!~/this/) |
| { |
| if(my %PureType = getPureType($TypeID, 1)) |
| { |
| if($PureType{"Type"} eq "Pointer") { |
| push(@Sentence, "(pointer)"); |
| } |
| elsif($PureType{"Type"} eq "Ref") { |
| push(@Sentence, "(reference)"); |
| } |
| } |
| } |
| |
| if($Loc eq "this") { |
| push(@Sentence, "has base type \'".$PAttr->{"Type_Name"}."\'."); |
| } |
| else |
| { |
| my $Loc_T = $Loc; |
| $Loc_T=~s/\A\w+(\->|\Z)//; # location in type |
| |
| my $TypeID_Problem = $TypeID; |
| if($Loc_T) { |
| $TypeID_Problem = getFieldType($Loc_T, $TypeID, 1); |
| } |
| |
| if($TypeInfo{1}{$TypeID_Problem}{"Name"} eq $PAttr->{"Type_Name"}) { |
| push(@Sentence, "is of type \'".$PAttr->{"Type_Name"}."\'."); |
| } |
| else { |
| push(@Sentence, "has base type \'".$PAttr->{"Type_Name"}."\'."); |
| } |
| } |
| } |
| } |
| |
| my $Sent = join(" ", @Sentence); |
| |
| $Sent=~s/->/./g; |
| |
| if($In::Opt{"ReportFormat"} eq "xml") { |
| $Sent=~s/'//g; |
| } |
| |
| return $Sent; |
| } |
| |
| sub getFieldType($$$) |
| { |
| my ($Loc, $TypeId, $LVer) = @_; |
| |
| my @Fields = split(/\->/, $Loc); |
| |
| foreach my $Name (@Fields) |
| { |
| my %Info = getBaseType($TypeId, $LVer); |
| |
| foreach my $Pos (keys(%{$Info{"Memb"}})) |
| { |
| if($Info{"Memb"}{$Pos}{"name"} eq $Name) |
| { |
| $TypeId = $Info{"Memb"}{$Pos}{"type"}; |
| last; |
| } |
| } |
| } |
| |
| return $TypeId; |
| } |
| |
| sub writeReport($$) |
| { |
| my ($Level, $Report) = @_; |
| if($In::Opt{"ReportFormat"} eq "xml") { |
| $Report = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n".$Report; |
| } |
| if($In::Opt{"StdOut"}) |
| { # --stdout option |
| print STDOUT $Report; |
| } |
| else |
| { |
| my $RPath = getReportPath($Level); |
| mkpath(getDirname($RPath)); |
| |
| open(REPORT, ">", $RPath) || die ("can't open file \'$RPath\': $!\n"); |
| print REPORT $Report; |
| close(REPORT); |
| } |
| } |
| |
| sub getReport($) |
| { |
| my $Level = $_[0]; |
| if($In::Opt{"ReportFormat"} eq "xml") |
| { # XML |
| if($Level eq "Join") |
| { |
| my $Report = "<reports>\n"; |
| $Report .= getReport("Binary"); |
| $Report .= getReport("Source"); |
| $Report .= "</reports>\n"; |
| return $Report; |
| } |
| else |
| { |
| my $Report = "<report kind=\"".lc($Level)."\" version=\"$XML_REPORT_VERSION\">\n\n"; |
| my ($Summary, $MetaData, $AnyChanged) = getSummary($Level); |
| $Report .= $Summary."\n"; |
| $Report .= getReportProblems_All($Level); |
| $Report .= "</report>\n"; |
| return $Report; |
| } |
| } |
| else |
| { # HTML |
| my $CssStyles = readModule("Styles", "Report.css"); |
| my $JScripts = readModule("Scripts", "Sections.js"); |
| if($Level eq "Join") |
| { |
| $CssStyles .= "\n".readModule("Styles", "Tabs.css"); |
| $JScripts .= "\n".readModule("Scripts", "Tabs.js"); |
| my $Title = $In::Opt{"TargetTitle"}.": ".$In::Desc{1}{"Version"}." to ".$In::Desc{2}{"Version"}." compatibility report"; |
| my $Keywords = $In::Opt{"TargetTitle"}.", compatibility, API, ABI, report"; |
| my $Des = "API/ABI compatibility report for the ".$In::Opt{"TargetTitle"}." ".$In::Opt{"TargetComponent"}." between ".$In::Desc{1}{"Version"}." and ".$In::Desc{2}{"Version"}." versions"; |
| my ($BSummary, $BMetaData, $BAnyChanged) = getSummary("Binary"); |
| my ($SSummary, $SMetaData, $SAnyChanged) = getSummary("Source"); |
| my $Report = "<!-\- $BMetaData -\->\n<!-\- $SMetaData -\->\n".composeHTML_Head($Title, $Keywords, $Des, $CssStyles, $JScripts, ($BAnyChanged or $SAnyChanged))."<body><a name='Source'></a><a name='Binary'></a><a name='Top'></a>"; |
| $Report .= getReportTitle("Join")." |
| <br/> |
| <div class='tabset'> |
| <a id='BinaryID' href='#BinaryTab' class='tab active'>Binary<br/>Compatibility</a> |
| <a id='SourceID' href='#SourceTab' style='margin-left:3px' class='tab disabled'>Source<br/>Compatibility</a> |
| </div>"; |
| $Report .= "<div id='BinaryTab' class='tab'>\n$BSummary\n".getReportProblems_All("Binary").getSourceInfo()."<br/><br/><br/></div>"; |
| $Report .= "<div id='SourceTab' class='tab'>\n$SSummary\n".getReportProblems_All("Source").getSourceInfo()."<br/><br/><br/></div>"; |
| $Report .= getReportFooter(); |
| $Report .= "\n</body></html>\n"; |
| return $Report; |
| } |
| else |
| { |
| my ($Summary, $MetaData, $AnyChanged) = getSummary($Level); |
| my $Title = $In::Opt{"TargetTitle"}.": ".$In::Desc{1}{"Version"}." to ".$In::Desc{2}{"Version"}." ".lc($Level)." compatibility report"; |
| my $Keywords = $In::Opt{"TargetTitle"}.", ".lc($Level)." compatibility, API, report"; |
| my $Des = "$Level compatibility report for the ".$In::Opt{"TargetTitle"}." ".$In::Opt{"TargetComponent"}." between ".$In::Desc{1}{"Version"}." and ".$In::Desc{2}{"Version"}." versions"; |
| if($Level eq "Binary") |
| { |
| if($In::ABI{1}{"Arch"} eq $In::ABI{2}{"Arch"}) { |
| $Des .= " on ".showArch($In::ABI{1}{"Arch"}); |
| } |
| } |
| my $Report = "<!-\- $MetaData -\->\n".composeHTML_Head($Title, $Keywords, $Des, $CssStyles, $JScripts, $AnyChanged)."\n<body>\n<div><a name='Top'></a>\n"; |
| $Report .= getReportTitle($Level)."\n".$Summary."\n"; |
| $Report .= getReportProblems_All($Level); |
| $Report .= getSourceInfo(); |
| $Report .= "</div>\n<br/><br/><br/>\n"; |
| $Report .= getReportFooter(); |
| $Report .= "\n</body></html>\n"; |
| return $Report; |
| } |
| } |
| } |
| |
| sub getReportProblems_All($) |
| { |
| my $Level = $_[0]; |
| |
| my $Report = getReportAdded($Level).getReportRemoved($Level); |
| $Report .= getReportProblems("High", $Level); |
| $Report .= getReportProblems("Medium", $Level); |
| $Report .= getReportProblems("Low", $Level); |
| $Report .= getReportProblems("Safe", $Level); |
| |
| # clean memory |
| delete($TypeProblemsIndex{$Level}); |
| delete($TypeChanges{$Level}); |
| delete($CompatProblems{$Level}); |
| |
| return $Report; |
| } |
| |
| sub createReport() |
| { |
| if($In::Opt{"JoinReport"}) { |
| writeReport("Join", getReport("Join")); |
| } |
| elsif($In::Opt{"DoubleReport"}) |
| { # default |
| writeReport("Binary", getReport("Binary")); |
| writeReport("Source", getReport("Source")); |
| } |
| elsif($In::Opt{"BinOnly"}) |
| { # --binary |
| writeReport("Binary", getReport("Binary")); |
| } |
| elsif($In::Opt{"SrcOnly"}) |
| { # --source |
| writeReport("Source", getReport("Source")); |
| } |
| } |
| |
| sub getReportFooter() |
| { |
| my $Footer = ""; |
| |
| $Footer .= "<hr/>\n"; |
| $Footer .= "<div class='footer' align='right'>"; |
| $Footer .= "<i>Generated by <a href='".$HomePage{"Dev"}."'>ABI Compliance Checker</a> $TOOL_VERSION  </i>\n"; |
| $Footer .= "</div>\n"; |
| $Footer .= "<br/>\n"; |
| |
| return $Footer; |
| } |
| |
| sub getReportProblems($$) |
| { |
| my ($Severity, $Level) = @_; |
| |
| my $Report = getReportTypeProblems($Severity, $Level); |
| if(my $SProblems = getReportSymbolProblems($Severity, $Level)) { |
| $Report .= $SProblems; |
| } |
| |
| if($Severity eq "Low" or $Severity eq "Safe") { |
| $Report .= getReportChangedConstants($Severity, $Level); |
| } |
| |
| if($In::Opt{"ReportFormat"} eq "html") |
| { |
| if($Report) |
| { # add anchor |
| if($In::Opt{"JoinReport"}) |
| { |
| if($Severity eq "Safe") { |
| $Report = "<a name=\'Other_".$Level."_Changes\'></a>".$Report; |
| } |
| else { |
| $Report = "<a name=\'".$Severity."_Risk_".$Level."_Problems\'></a>".$Report; |
| } |
| } |
| else |
| { |
| if($Severity eq "Safe") { |
| $Report = "<a name=\'Other_Changes\'></a>".$Report; |
| } |
| else { |
| $Report = "<a name=\'".$Severity."_Risk_Problems\'></a>".$Report; |
| } |
| } |
| } |
| } |
| return $Report; |
| } |
| |
| sub composeHTML_Head($$$$$$) |
| { |
| my ($Title, $Keywords, $Des, $Styles, $Scripts, $AnyChanged) = @_; |
| |
| my $Head = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"; |
| $Head .= "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n"; |
| $Head .= "<head>\n"; |
| $Head .= "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n"; |
| $Head .= "<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />\n"; |
| $Head .= "<meta name=\"keywords\" content=\"$Keywords\" />\n"; |
| $Head .= "<meta name=\"description\" content=\"$Des\" />\n"; |
| |
| if(not $AnyChanged) { |
| $Head .= "<meta name=\"robots\" content=\"noindex\" />\n"; |
| } |
| |
| $Head .= "<title>$Title</title>\n"; |
| $Head .= "<style type=\"text/css\">\n$Styles</style>\n"; |
| $Head .= "<script type=\"text/javascript\" language=\"JavaScript\">\n<!--\n$Scripts\n-->\n</script>\n"; |
| $Head .= "</head>\n"; |
| |
| return $Head; |
| } |
| |
| sub insertIDs($) |
| { |
| my $Text = $_[0]; |
| while($Text=~/CONTENT_ID/) |
| { |
| if(int($Content_Counter)%2) { |
| $ContentID -= 1; |
| } |
| $Text=~s/CONTENT_ID/c_$ContentID/; |
| $ContentID += 1; |
| $Content_Counter += 1; |
| } |
| return $Text; |
| } |
| |
| sub uncoverConstant($$) |
| { |
| my ($LVer, $Constant) = @_; |
| |
| if(isCyclical(\@RecurConstant, $Constant)) { |
| return $Constant; |
| } |
| |
| if(defined $Cache{"uncoverConstant"}{$LVer}{$Constant}) { |
| return $Cache{"uncoverConstant"}{$LVer}{$Constant}; |
| } |
| |
| if(defined $Constants{$LVer}{$Constant}) |
| { |
| my $Value = $Constants{$LVer}{$Constant}{"Value"}; |
| if(defined $Constants{$LVer}{$Value}) |
| { |
| push(@RecurConstant, $Constant); |
| my $Uncovered = uncoverConstant($LVer, $Value); |
| if($Uncovered ne "") { |
| $Value = $Uncovered; |
| } |
| pop(@RecurConstant); |
| } |
| |
| # FIXME: uncover $Value using all the enum constants |
| # USE CASE: change of define NC_LONG from NC_INT (enum value) to NC_INT (define) |
| return ($Cache{"uncoverConstant"}{$LVer}{$Constant} = $Value); |
| } |
| return ($Cache{"uncoverConstant"}{$LVer}{$Constant} = ""); |
| } |
| |
| sub simpleConstant($$) |
| { |
| my ($LVer, $Value) = @_; |
| if($Value=~/\W/) |
| { |
| my $Value_Copy = $Value; |
| while($Value_Copy=~s/([a-z_]\w+)/\@/i) |
| { |
| my $Word = $1; |
| if($Value!~/$Word\s*\(/) |
| { |
| my $Val = uncoverConstant($LVer, $Word); |
| if($Val ne "") |
| { |
| $Value=~s/\b$Word\b/$Val/g; |
| } |
| } |
| } |
| } |
| return $Value; |
| } |
| |
| sub computeValue($) |
| { |
| my $Value = $_[0]; |
| |
| if($Value=~/\A\((-?[\d]+)\)\Z/) { |
| return $1; |
| } |
| |
| if($Value=~/\A[\d\-\+()]+\Z/) { |
| return eval($Value); |
| } |
| |
| return $Value; |
| } |
| |
| my $IgnoreConstant = join("|", |
| "VERSION", |
| "VERSIONCODE", |
| "VERNUM", |
| "VERS_INFO", |
| "PATCHLEVEL", |
| "INSTALLPREFIX", |
| "VBUILD", |
| "VPATCH", |
| "VMINOR", |
| "BUILD_STRING", |
| "BUILD_TIME", |
| "PACKAGE_STRING", |
| "PRODUCTION", |
| "CONFIGURE_COMMAND", |
| "INSTALLDIR", |
| "BINDIR", |
| "CONFIG_FILE_PATH", |
| "DATADIR", |
| "EXTENSION_DIR", |
| "INCLUDE_PATH", |
| "LIBDIR", |
| "LOCALSTATEDIR", |
| "SBINDIR", |
| "SYSCONFDIR", |
| "RELEASE", |
| "SOURCE_ID", |
| "SUBMINOR", |
| "MINOR", |
| "MINNOR", |
| "MINORVERSION", |
| "MAJOR", |
| "MAJORVERSION", |
| "MICRO", |
| "MICROVERSION", |
| "BINARY_AGE", |
| "INTERFACE_AGE", |
| "CORE_ABI", |
| "PATCH", |
| "COPYRIGHT", |
| "TIMESTAMP", |
| "REVISION", |
| "PACKAGE_TAG", |
| "PACKAGEDATE", |
| "NUMVERSION", |
| "Release", |
| "Version" |
| ); |
| |
| sub constantFilter($$$) |
| { |
| my ($Name, $Value, $Level) = @_; |
| |
| if($Level eq "Binary") |
| { |
| if($Name=~/_t\Z/) |
| { # __malloc_ptr_t |
| return 1; |
| } |
| if($Name=~/(\A|_)($IgnoreConstant)(_|\Z)/) |
| { # version |
| return 1; |
| } |
| if($Name=~/(\A|[a-z])(Release|Version)([A-Z]|\Z)/) |
| { # version |
| return 1; |
| } |
| my $LShort = $In::Opt{"TargetLibShort"}; |
| if($Name=~/(\A|_)(lib|open|)$LShort(_|)(VERSION|VER|DATE|API|PREFIX)(_|\Z)/i) |
| { # version |
| return 1; |
| } |
| if($Value=~/\A('|"|)[\/\\]\w+([\/\\]|:|('|"|)\Z)/ or $Value=~/[\/\\]\w+[\/\\]\w+/) |
| { # /lib64:/usr/lib64:/lib:/usr/lib:/usr/X11R6/lib/Xaw3d ... |
| return 1; |
| } |
| |
| if($Value=~/\A["'].*['"]/i) |
| { # string |
| return 0; |
| } |
| |
| if($Value=~/\A[({]*\s*[a-z_]+\w*(\s+|[\|,])/i) |
| { # static int gcry_pth_init |
| # extern ABC |
| # (RE_BACKSLASH_ESCAPE_IN_LISTS | RE... |
| # { H5FD_MEM_SUPER, H5FD_MEM_SUPER, ... |
| return 1; |
| } |
| if($Value=~/\w+\s*\(/i) |
| { # foo(p) |
| return 1; |
| } |
| if($Value=~/\A[a-z_]+\w*\Z/i) |
| { # asn1_node_st |
| # __SMTH_P |
| return 1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| sub mergeConstants($) |
| { |
| my $Level = $_[0]; |
| foreach my $Constant (sort keys(%{$Constants{1}})) |
| { |
| if($In::Desc{1}{"SkipConstants"}{$Constant}) |
| { # skipped by the user |
| next; |
| } |
| |
| if(my $Header = $Constants{1}{$Constant}{"Header"}) |
| { |
| if(not isTargetHeader($Header, 1) |
| and not isTargetHeader($Header, 2)) { |
| next; |
| } |
| } |
| elsif(my $Source = $Constants{1}{$Constant}{"Source"}) |
| { |
| if(not isTargetSource($Source, 1) |
| and not isTargetSource($Source, 2)) { |
| next; |
| } |
| } |
| else { |
| next; |
| } |
| |
| my $Old_Value = uncoverConstant(1, $Constant); |
| |
| if(constantFilter($Constant, $Old_Value, $Level)) |
| { # separate binary and source problems |
| next; |
| } |
| |
| if(not defined $Constants{2}{$Constant}{"Value"}) |
| { # removed |
| if(not defined $In::Opt{"SkipRemovedConstants"}) |
| { |
| %{$CompatProblems_Constants{$Level}{$Constant}{"Removed_Constant"}} = ( |
| "Target"=>$Constant, |
| "Old_Value"=>$Old_Value ); |
| } |
| next; |
| } |
| |
| if($Constants{2}{$Constant}{"Value"} eq "") |
| { # empty value |
| # TODO: implement a rule |
| next; |
| } |
| |
| my $New_Value = uncoverConstant(2, $Constant); |
| |
| my $Old_Value_Pure = $Old_Value; |
| my $New_Value_Pure = $New_Value; |
| |
| $Old_Value_Pure=~s/(\W)\s+/$1/g; |
| $Old_Value_Pure=~s/\s+(\W)/$1/g; |
| $New_Value_Pure=~s/(\W)\s+/$1/g; |
| $New_Value_Pure=~s/\s+(\W)/$1/g; |
| |
| next if($New_Value_Pure eq "" or $Old_Value_Pure eq ""); |
| |
| if($New_Value_Pure ne $Old_Value_Pure) |
| { # different values |
| if(simpleConstant(1, $Old_Value) eq simpleConstant(2, $New_Value)) |
| { # complex values |
| next; |
| } |
| if(computeValue($Old_Value) eq computeValue($New_Value)) |
| { # expressions |
| next; |
| } |
| if(convertInteger($Old_Value) eq convertInteger($New_Value)) |
| { # 0x0001 and 0x1, 0x1 and 1 equal constants |
| next; |
| } |
| if($Old_Value eq "0" and $New_Value eq "NULL") |
| { # 0 => NULL |
| next; |
| } |
| if($Old_Value eq "NULL" and $New_Value eq "0") |
| { # NULL => 0 |
| next; |
| } |
| %{$CompatProblems_Constants{$Level}{$Constant}{"Changed_Constant"}} = ( |
| "Target"=>$Constant, |
| "Old_Value"=>$Old_Value, |
| "New_Value"=>$New_Value ); |
| } |
| } |
| |
| if(defined $In::Opt{"SkipAddedConstants"}) { |
| return; |
| } |
| |
| foreach my $Constant (keys(%{$Constants{2}})) |
| { |
| if(not defined $Constants{1}{$Constant}{"Value"}) |
| { |
| if($In::Desc{2}{"SkipConstants"}{$Constant}) |
| { # skipped by the user |
| next; |
| } |
| |
| if(my $Header = $Constants{2}{$Constant}{"Header"}) |
| { |
| if(not isTargetHeader($Header, 1) |
| and not isTargetHeader($Header, 2)) |
| { # user-defined header |
| next; |
| } |
| } |
| elsif(my $Source = $Constants{2}{$Constant}{"Source"}) |
| { |
| if(not isTargetSource($Source, 1) |
| and not isTargetSource($Source, 2)) |
| { # user-defined header |
| next; |
| } |
| } |
| else { |
| next; |
| } |
| |
| my $New_Value = uncoverConstant(2, $Constant); |
| if(not defined $New_Value or $New_Value eq "") { |
| next; |
| } |
| |
| if(constantFilter($Constant, $New_Value, $Level)) |
| { # separate binary and source problems |
| next; |
| } |
| |
| %{$CompatProblems_Constants{$Level}{$Constant}{"Added_Constant"}} = ( |
| "Target"=>$Constant, |
| "New_Value"=>$New_Value ); |
| } |
| } |
| } |
| |
| sub convertInteger($) |
| { |
| my $Value = $_[0]; |
| if($Value=~/\A0x[a-f0-9]+\Z/) |
| { # hexadecimal |
| return hex($Value); |
| } |
| elsif($Value=~/\A0[0-7]+\Z/) |
| { # octal |
| return oct($Value); |
| } |
| elsif($Value=~/\A0b[0-1]+\Z/) |
| { # binary |
| return oct($Value); |
| } |
| else { |
| return $Value; |
| } |
| } |
| |
| sub getSymbolSize($$) |
| { # size from the shared library |
| my ($Symbol, $LVer) = @_; |
| |
| if(defined $In::ABI{$LVer}{"SymLib"}{$Symbol} |
| and my $LibName = $In::ABI{$LVer}{"SymLib"}{$Symbol}) |
| { |
| if(defined $In::ABI{$LVer}{"Symbols"}{$LibName}{$Symbol} |
| and my $Size = $In::ABI{$LVer}{"Symbols"}{$LibName}{$Symbol}) |
| { |
| if($Size<0) { |
| return -$Size; |
| } |
| } |
| } |
| return 0; |
| } |
| |
| sub createSymbolsList($$$$$) |
| { |
| my ($DPath, $SaveTo, $LName, $LVersion, $ArchName) = @_; |
| |
| $In::ABI{1} = readABIDump(1, $DPath); |
| initAliases(1); |
| |
| prepareSymbols(1); |
| |
| my %SymbolHeaderLib = (); |
| my $Total = 0; |
| |
| # Get List |
| foreach my $Symbol (sort keys(%{$CompSign{1}})) |
| { |
| if(not linkSymbol($Symbol, 1, "-Deps")) |
| { # skip src only and all external functions |
| next; |
| } |
| if(not symbolFilter($Symbol, $CompSign{1}{$Symbol}, "Public", "Binary", 1)) |
| { # skip other symbols |
| next; |
| } |
| my $HeaderName = $CompSign{1}{$Symbol}{"Header"}; |
| if(not $HeaderName) |
| { # skip src only and all external functions |
| next; |
| } |
| my $DyLib = $In::ABI{1}{"SymLib"}{$Symbol}; |
| if(not $DyLib) |
| { # skip src only and all external functions |
| next; |
| } |
| $SymbolHeaderLib{$HeaderName}{$DyLib}{$Symbol} = 1; |
| $Total += 1; |
| } |
| # Draw List |
| my $SYMBOLS_LIST = "<h1>Public symbols in <span style='color:Blue;'>$LName</span> (<span style='color:Red;'>$LVersion</span>)"; |
| $SYMBOLS_LIST .= " on <span style='color:Blue;'>".showArch($ArchName)."</span><br/>Total: $Total</h1><br/>"; |
| foreach my $HeaderName (sort {lc($a) cmp lc($b)} keys(%SymbolHeaderLib)) |
| { |
| foreach my $DyLib (sort {lc($a) cmp lc($b)} keys(%{$SymbolHeaderLib{$HeaderName}})) |
| { |
| my %NS_Symbol = (); |
| foreach my $Symbol (keys(%{$SymbolHeaderLib{$HeaderName}{$DyLib}})) { |
| $NS_Symbol{selectSymbolNs($Symbol, 1)}{$Symbol} = 1; |
| } |
| foreach my $NameSpace (sort keys(%NS_Symbol)) |
| { |
| $SYMBOLS_LIST .= getTitle($HeaderName, $DyLib, $NameSpace); |
| my @SortedInterfaces = sort {lc($CompSign{1}{$a}{"Unmangled"}) cmp lc($CompSign{1}{$b}{"Unmangled"})} sort {lc($a) cmp lc($b)} keys(%{$NS_Symbol{$NameSpace}}); |
| foreach my $Symbol (@SortedInterfaces) |
| { |
| my $SubReport = ""; |
| my $Signature = highLight_ItalicColor($Symbol, 1); |
| if($NameSpace) { |
| $Signature = cutNs($Signature, $NameSpace); |
| } |
| if($Symbol=~/\A(_Z|\?)/) { |
| $SubReport = insertIDs($ContentSpanStart.$Signature.$ContentSpanEnd."<br/>\n".$ContentDivStart."<span class='mngl'>$Symbol</span><br/><br/>".$ContentDivEnd."\n"); |
| } |
| else { |
| $SubReport = "<span class='iname'>".$Signature."</span><br/>\n"; |
| } |
| $SYMBOLS_LIST .= $SubReport; |
| } |
| } |
| $SYMBOLS_LIST .= "<br/>\n"; |
| } |
| } |
| # clear info |
| (%CompSign, %ClassMethods, %AllocableClass, %ClassNames, %In::ABI) = (); |
| |
| ($Content_Counter, $ContentID) = (0, 0); |
| |
| my $CssStyles = readModule("Styles", "SymbolsList.css"); |
| my $JScripts = readModule("Scripts", "Sections.js"); |
| $SYMBOLS_LIST = "<a name='Top'></a>".$SYMBOLS_LIST.$TOP_REF."<br/>\n"; |
| my $Title = "$LName: public symbols"; |
| my $Keywords = "$LName, API, symbols"; |
| my $Des = "List of symbols in $LName ($LVersion) on ".showArch($ArchName); |
| $SYMBOLS_LIST = composeHTML_Head($Title, $Keywords, $Des, $CssStyles, $JScripts, 1)." |
| <body><div>\n$SYMBOLS_LIST</div> |
| <br/><br/>\n".getReportFooter()." |
| </body></html>"; |
| writeFile($SaveTo, $SYMBOLS_LIST); |
| } |
| |
| sub dumpSorting($) |
| { |
| my $Hash = $_[0]; |
| if(not $Hash) { |
| return []; |
| } |
| |
| my @Keys = keys(%{$Hash}); |
| if($#Keys<0) { |
| return []; |
| } |
| |
| if($Keys[0]=~/\A\d+\Z/) |
| { # numbers |
| return [sort {$a<=>$b} @Keys]; |
| } |
| else |
| { # strings |
| return [sort {$a cmp $b} @Keys]; |
| } |
| } |
| |
| sub exitReport() |
| { # the tool has run without any errors |
| printReport(); |
| if($In::Opt{"CompileError"}) |
| { # errors in headers may add false positives/negatives |
| exit(getErrorCode("Compile_Error")); |
| } |
| if($In::Opt{"BinOnly"} and $RESULT{"Binary"}{"Problems"}) |
| { # --binary |
| exit(getErrorCode("Incompatible")); |
| } |
| elsif($In::Opt{"SrcOnly"} |
| and $RESULT{"Source"}{"Problems"}) |
| { # --source |
| exit(getErrorCode("Incompatible")); |
| } |
| elsif($RESULT{"Source"}{"Problems"} |
| or $RESULT{"Binary"}{"Problems"}) |
| { # default |
| exit(getErrorCode("Incompatible")); |
| } |
| else { |
| exit(getErrorCode("Compatible")); |
| } |
| } |
| |
| sub readRules($) |
| { |
| my $Kind = $_[0]; |
| if(not -f $RULES_PATH{$Kind}) { |
| exitStatus("Module_Error", "can't access \'".$RULES_PATH{$Kind}."\'"); |
| } |
| my $Content = readFile($RULES_PATH{$Kind}); |
| while(my $Rule = parseTag(\$Content, "rule")) |
| { |
| my $RId = parseTag(\$Rule, "id"); |
| my @Properties = ("Severity", "Change", "Effect", "Overcome", "Kind"); |
| foreach my $Prop (@Properties) { |
| if(my $Value = parseTag(\$Rule, lc($Prop))) |
| { |
| $Value=~s/\n[ ]*//; |
| $CompatRules{$Kind}{$RId}{$Prop} = $Value; |
| } |
| } |
| if($CompatRules{$Kind}{$RId}{"Kind"}=~/\A(Symbols|Parameters)\Z/) { |
| $CompatRules{$Kind}{$RId}{"Kind"} = "Symbols"; |
| } |
| else { |
| $CompatRules{$Kind}{$RId}{"Kind"} = "Types"; |
| } |
| } |
| } |
| |
| sub getReportPath($) |
| { |
| my $Level = $_[0]; |
| my $Dir = "compat_reports/".$In::Opt{"TargetLib"}."/".$In::Desc{1}{"Version"}."_to_".$In::Desc{2}{"Version"}; |
| if($Level eq "Binary") |
| { |
| if($In::Opt{"BinReportPath"}) |
| { # --bin-report-path |
| return $In::Opt{"BinReportPath"}; |
| } |
| elsif($In::Opt{"OutputReportPath"}) |
| { # --report-path |
| return $In::Opt{"OutputReportPath"}; |
| } |
| else |
| { # default |
| return $Dir."/abi_compat_report.".$In::Opt{"ReportFormat"}; |
| } |
| } |
| elsif($Level eq "Source") |
| { |
| if($In::Opt{"SrcReportPath"}) |
| { # --src-report-path |
| return $In::Opt{"SrcReportPath"}; |
| } |
| elsif($In::Opt{"OutputReportPath"}) |
| { # --report-path |
| return $In::Opt{"OutputReportPath"}; |
| } |
| else |
| { # default |
| return $Dir."/src_compat_report.".$In::Opt{"ReportFormat"}; |
| } |
| } |
| else |
| { |
| if($In::Opt{"OutputReportPath"}) |
| { # --report-path |
| return $In::Opt{"OutputReportPath"}; |
| } |
| else |
| { # default |
| return $Dir."/compat_report.".$In::Opt{"ReportFormat"}; |
| } |
| } |
| } |
| |
| sub printStatMsg($) |
| { |
| my $Level = $_[0]; |
| printMsg("INFO", "Total ".lc($Level)." compatibility problems: ".$RESULT{$Level}{"Problems"}.", warnings: ".$RESULT{$Level}{"Warnings"}); |
| } |
| |
| sub listAffected($) |
| { |
| my $Level = $_[0]; |
| my $List = ""; |
| foreach (keys(%{$TotalAffected{$Level}})) |
| { |
| if($In::Opt{"StrictCompat"} and $TotalAffected{$Level}{$_} eq "Low") |
| { # skip "Low"-severity problems |
| next; |
| } |
| $List .= "$_\n"; |
| } |
| my $Dir = getDirname(getReportPath($Level)); |
| if($Level eq "Binary") { |
| writeFile($Dir."/abi_affected.txt", $List); |
| } |
| elsif($Level eq "Source") { |
| writeFile($Dir."/src_affected.txt", $List); |
| } |
| } |
| |
| sub printReport() |
| { |
| printMsg("INFO", "Creating compatibility report ..."); |
| |
| createReport(); |
| |
| if($In::Opt{"JoinReport"} or $In::Opt{"DoubleReport"}) |
| { |
| if($RESULT{"Binary"}{"Problems"} |
| or $RESULT{"Source"}{"Problems"}) |
| { |
| printMsg("INFO", "Binary compatibility: ".(100-$RESULT{"Binary"}{"Affected"})."\%"); |
| printMsg("INFO", "Source compatibility: ".(100-$RESULT{"Source"}{"Affected"})."\%"); |
| } |
| else |
| { |
| printMsg("INFO", "Binary compatibility: 100\%"); |
| printMsg("INFO", "Source compatibility: 100\%"); |
| } |
| printStatMsg("Binary"); |
| printStatMsg("Source"); |
| |
| if($In::Opt{"ListAffected"}) |
| { # --list-affected |
| listAffected("Binary"); |
| listAffected("Source"); |
| } |
| } |
| elsif($In::Opt{"BinOnly"}) |
| { |
| if($RESULT{"Binary"}{"Problems"}) { |
| printMsg("INFO", "Binary compatibility: ".(100-$RESULT{"Binary"}{"Affected"})."\%"); |
| } |
| else { |
| printMsg("INFO", "Binary compatibility: 100\%"); |
| } |
| printStatMsg("Binary"); |
| |
| if($In::Opt{"ListAffected"}) |
| { # --list-affected |
| listAffected("Binary"); |
| } |
| } |
| elsif($In::Opt{"SrcOnly"}) |
| { |
| if($RESULT{"Source"}{"Problems"}) { |
| printMsg("INFO", "Source compatibility: ".(100-$RESULT{"Source"}{"Affected"})."\%"); |
| } |
| else { |
| printMsg("INFO", "Source compatibility: 100\%"); |
| } |
| printStatMsg("Source"); |
| |
| if($In::Opt{"ListAffected"}) |
| { # --list-affected |
| listAffected("Source"); |
| } |
| } |
| |
| if($In::Opt{"StdOut"}) |
| { |
| if($In::Opt{"JoinReport"} or not $In::Opt{"DoubleReport"}) |
| { # --binary or --source |
| printMsg("INFO", "Compatibility report has been generated to stdout"); |
| } |
| else |
| { # default |
| printMsg("INFO", "Compatibility reports have been generated to stdout"); |
| } |
| } |
| else |
| { |
| if($In::Opt{"JoinReport"}) |
| { # default |
| printMsg("INFO", "Report: ".pathFmt(getReportPath("Join"))); |
| } |
| elsif($In::Opt{"DoubleReport"}) |
| { |
| printMsg("INFO", "Report (BC): ".pathFmt(getReportPath("Binary"))); |
| printMsg("INFO", "Report (SC): ".pathFmt(getReportPath("Source"))); |
| } |
| elsif($In::Opt{"BinOnly"}) |
| { # --binary |
| printMsg("INFO", "Report: ".pathFmt(getReportPath("Binary"))); |
| } |
| elsif($In::Opt{"SrcOnly"}) |
| { # --source |
| printMsg("INFO", "Report: ".pathFmt(getReportPath("Source"))); |
| } |
| } |
| } |
| |
| sub unpackDump($) |
| { |
| my $Path = $_[0]; |
| |
| $Path = getAbsPath($Path); |
| my ($Dir, $FileName) = sepPath($Path); |
| |
| my $TmpDir = $In::Opt{"Tmp"}; |
| my $UnpackDir = $TmpDir."/unpack"; |
| rmtree($UnpackDir); |
| mkpath($UnpackDir); |
| |
| if($FileName=~s/\Q.zip\E\Z//g) |
| { # *.zip |
| my $UnzipCmd = getCmdPath("unzip"); |
| if(not $UnzipCmd) { |
| exitStatus("Not_Found", "can't find \"unzip\" command"); |
| } |
| chdir($UnpackDir); |
| system("$UnzipCmd \"$Path\" >\"$TmpDir/null\""); |
| if($?) { |
| exitStatus("Error", "can't extract \'$Path\' ($?): $!"); |
| } |
| chdir($In::Opt{"OrigDir"}); |
| my @Contents = cmdFind($UnpackDir, "f"); |
| if(not @Contents) { |
| exitStatus("Error", "can't extract \'$Path\'"); |
| } |
| return $Contents[0]; |
| } |
| elsif($FileName=~s/\Q.tar.gz\E(\.\w+|)\Z//g) |
| { # *.tar.gz |
| # *.tar.gz.amd64 (dh & cdbs) |
| if($In::Opt{"OS"} eq "windows") |
| { # -xvzf option is not implemented in tar.exe (2003) |
| # use "gzip.exe -k -d -f" + "tar.exe -xvf" instead |
| my $TarCmd = getCmdPath("tar"); |
| if(not $TarCmd) { |
| exitStatus("Not_Found", "can't find \"tar\" command"); |
| } |
| my $GzipCmd = getCmdPath("gzip"); |
| if(not $GzipCmd) { |
| exitStatus("Not_Found", "can't find \"gzip\" command"); |
| } |
| chdir($UnpackDir); |
| system("$GzipCmd -k -d -f \"$Path\""); # keep input files (-k) |
| if($?) { |
| exitStatus("Error", "can't extract \'$Path\'"); |
| } |
| system("$TarCmd -xvf \"$Dir\\$FileName.tar\" >\"$TmpDir/null\""); |
| if($?) { |
| exitStatus("Error", "can't extract \'$Path\' ($?): $!"); |
| } |
| chdir($In::Opt{"OrigDir"}); |
| unlink($Dir."/".$FileName.".tar"); |
| my @Contents = cmdFind($UnpackDir, "f"); |
| if(not @Contents) { |
| exitStatus("Error", "can't extract \'$Path\'"); |
| } |
| return $Contents[0]; |
| } |
| else |
| { # Unix, Mac |
| my $TarCmd = getCmdPath("tar"); |
| if(not $TarCmd) { |
| exitStatus("Not_Found", "can't find \"tar\" command"); |
| } |
| chdir($UnpackDir); |
| system("$TarCmd -xvzf \"$Path\" >\"$TmpDir/null\""); |
| if($?) { |
| exitStatus("Error", "can't extract \'$Path\' ($?): $!"); |
| } |
| chdir($In::Opt{"OrigDir"}); |
| my @Contents = cmdFind($UnpackDir, "f"); |
| if(not @Contents) { |
| exitStatus("Error", "can't extract \'$Path\'"); |
| } |
| return $Contents[0]; |
| } |
| } |
| } |
| |
| sub createArchive($$) |
| { |
| my ($Path, $To) = @_; |
| if(not $To) { |
| $To = "."; |
| } |
| |
| my ($From, $Name) = sepPath($Path); |
| if($In::Opt{"OS"} eq "windows") |
| { # *.zip |
| my $ZipCmd = getCmdPath("zip"); |
| if(not $ZipCmd) { |
| exitStatus("Not_Found", "can't find \"zip\""); |
| } |
| my $Pkg = $To."/".$Name.".zip"; |
| unlink($Pkg); |
| chdir($To); |
| system("$ZipCmd -j \"$Name.zip\" \"$Path\" >\"".$In::Opt{"Tmp"}."/null\""); |
| if($?) |
| { # cannot allocate memory (or other problems with "zip") |
| chdir($In::Opt{"OrigDir"}); |
| exitStatus("Error", "can't pack the ABI dump: ".$!); |
| } |
| chdir($In::Opt{"OrigDir"}); |
| unlink($Path); |
| return $Pkg; |
| } |
| else |
| { # *.tar.gz |
| my $TarCmd = getCmdPath("tar"); |
| if(not $TarCmd) { |
| exitStatus("Not_Found", "can't find \"tar\""); |
| } |
| my $GzipCmd = getCmdPath("gzip"); |
| if(not $GzipCmd) { |
| exitStatus("Not_Found", "can't find \"gzip\""); |
| } |
| my $Pkg = abs_path($To)."/".$Name.".tar.gz"; |
| if(-e $Pkg) { |
| unlink($Pkg); |
| } |
| system($TarCmd, "-C", $From, "-czf", $Pkg, $Name); |
| if($?) |
| { # cannot allocate memory (or other problems with "tar") |
| exitStatus("Error", "can't pack the ABI dump: ".$!); |
| } |
| unlink($Path); |
| return $To."/".$Name.".tar.gz"; |
| } |
| } |
| |
| sub defaultDumpPath($$) |
| { |
| my ($N, $V) = @_; |
| return "abi_dumps/".$N."/".$V."/ABI.dump"; |
| } |
| |
| sub createABIFile($$) |
| { |
| my ($LVer, $DescPath) = @_; |
| |
| if(not -e $DescPath) { |
| exitStatus("Access_Error", "can't access \'$DescPath\'"); |
| } |
| |
| detectDefaultPaths(undef, undef, "bin", undef); |
| |
| if(isDump($DescPath)) |
| { |
| $In::ABI{$LVer} = readABIDump($LVer, $DescPath); |
| initAliases($LVer); |
| |
| if(my $V = $In::Desc{$LVer}{"TargetVersion"}) { |
| $In::Desc{$LVer}{"Version"} = $V; |
| } |
| else { |
| $In::Desc{$LVer}{"Version"} = $In::ABI{$LVer}{"LibraryVersion"}; |
| } |
| } |
| else |
| { |
| loadModule("ABIDump"); |
| readDesc(createDesc($DescPath, $LVer), $LVer); |
| |
| initLogging($LVer); |
| |
| if($In::Opt{"Debug"}) |
| { |
| if(not $In::Opt{"ExtraInfo"}) { |
| $In::Opt{"ExtraInfo"} = getExtraDir($LVer); |
| } |
| } |
| |
| detectDefaultPaths("inc", "lib", undef, "gcc"); |
| createABIDump($LVer); |
| } |
| |
| clearSysFilesCache($LVer); |
| |
| printMsg("INFO", "Creating library ABI dump ..."); |
| |
| $In::ABI{$LVer}{"ABI_DUMP_VERSION"} = $ABI_DUMP_VERSION; |
| $In::ABI{$LVer}{"ABI_COMPLIANCE_CHECKER_VERSION"} = $TOOL_VERSION; |
| |
| if($In::Opt{"UseXML"}) { |
| $In::ABI{$LVer}{"XML_ABI_DUMP_VERSION"} = $XML_ABI_DUMP_VERSION; |
| } |
| |
| $In::ABI{$LVer}{"TargetHeaders"} = $In::Desc{$LVer}{"TargetHeader"}; |
| |
| foreach ("SymLib", "DepSymLib", "TName_Tid", "TypeTypedef", |
| "TypedefBase", "Class_SubClasses", "ClassVTable") { |
| delete($In::ABI{$LVer}{$_}); |
| } |
| |
| my $DumpPath = defaultDumpPath($In::Opt{"TargetLib"}, $In::Desc{1}{"Version"}); |
| if($In::Opt{"OutputDumpPath"}) |
| { # user defined path |
| $DumpPath = $In::Opt{"OutputDumpPath"}; |
| } |
| |
| my $ArExt = $In::Opt{"Ar"}; |
| my $Archive = ($DumpPath=~s/\Q.$ArExt\E\Z//g); |
| |
| if($Archive and not $In::Opt{"StdOut"}) |
| { # check archive utilities |
| if($In::Opt{"OS"} eq "windows") |
| { # using zip |
| my $ZipCmd = getCmdPath("zip"); |
| if(not $ZipCmd) { |
| exitStatus("Not_Found", "can't find \"zip\""); |
| } |
| } |
| else |
| { # using tar and gzip |
| my $TarCmd = getCmdPath("tar"); |
| if(not $TarCmd) { |
| exitStatus("Not_Found", "can't find \"tar\""); |
| } |
| my $GzipCmd = getCmdPath("gzip"); |
| if(not $GzipCmd) { |
| exitStatus("Not_Found", "can't find \"gzip\""); |
| } |
| } |
| } |
| |
| my $ABI_DUMP = ""; |
| if($In::Opt{"UseXML"}) |
| { |
| loadModule("XmlDump"); |
| $ABI_DUMP = createXmlDump($LVer); |
| } |
| else |
| { # default |
| $ABI_DUMP = Dumper($In::ABI{$LVer}); |
| } |
| if($In::Opt{"StdOut"}) |
| { |
| print STDOUT $ABI_DUMP; |
| printMsg("INFO", "ABI dump has been generated to stdout"); |
| return; |
| } |
| else |
| { # to file |
| my ($DDir, $DName) = sepPath($DumpPath); |
| my $DPath = $In::Opt{"Tmp"}."/".$DName; |
| if(not $Archive) { |
| $DPath = $DumpPath; |
| } |
| |
| mkpath($DDir); |
| |
| open(DUMP, ">", $DPath) || die ("can't open file \'$DPath\': $!\n"); |
| print DUMP $ABI_DUMP; |
| close(DUMP); |
| |
| # Ensure contents of archive have deterministic contents |
| chmod(0644, $DPath); |
| utime(0, 0, $DPath); |
| |
| if(not -s $DPath) { |
| exitStatus("Error", "can't create ABI dump because something is going wrong with the Data::Dumper module"); |
| } |
| if($Archive) { |
| $DumpPath = createArchive($DPath, $DDir); |
| } |
| |
| printMsg("INFO", "Dump path: ".pathFmt($DumpPath)); |
| } |
| } |
| |
| sub readABIDump($$) |
| { |
| my ($LVer, $Path) = @_; |
| |
| my $FilePath = ""; |
| if(isDump_U($Path)) |
| { # input *.abi |
| $FilePath = $Path; |
| } |
| else |
| { # input *.abi.tar.gz |
| $FilePath = unpackDump($Path); |
| if(not isDump_U($FilePath)) { |
| exitStatus("Invalid_Dump", "specified ABI dump \'$Path\' is not valid, try to recreate it"); |
| } |
| } |
| |
| my $ABIRef = {}; |
| |
| my $Line = readLineNum($FilePath, 0); |
| if($Line=~/xml/) |
| { # XML format |
| loadModule("XmlDump"); |
| $ABIRef = readXmlDump($FilePath); |
| } |
| else |
| { # Perl Data::Dumper format (default) |
| open(DUMP, $FilePath); |
| local $/ = undef; |
| my $Content = <DUMP>; |
| close(DUMP); |
| |
| if(getDirname($FilePath) eq $In::Opt{"Tmp"}."/unpack") |
| { # remove temp file |
| unlink($FilePath); |
| } |
| if($Content!~/};\s*\Z/) { |
| exitStatus("Invalid_Dump", "specified ABI dump \'$Path\' is not valid, try to recreate it"); |
| } |
| $ABIRef = eval($Content); |
| if(not $ABIRef) { |
| exitStatus("Error", "internal error - eval() procedure seem to not working correctly, try to remove 'use strict' and try again"); |
| } |
| } |
| |
| my $ABIVer = $ABIRef->{"ABI_DUMP_VERSION"}; |
| |
| if($ABIVer) |
| { |
| if(cmpVersions($ABIVer, $ABI_DUMP_VERSION)>0) |
| { # future formats |
| exitStatus("Dump_Version", "the versions of the ABI dump is newer than version of the tool"); |
| } |
| } |
| |
| if(cmpVersions($ABIVer, $ABI_DUMP_VERSION_MIN)<0 and not $In::Opt{"CountPubSymbols"}) { |
| exitStatus("Dump_Version", "the version of the ABI dump is too old and unsupported anymore, please regenerate it"); |
| } |
| |
| if(defined $ABIRef->{"ABI_DUMPER_VERSION"}) |
| { # DWARF ABI Dump |
| $UseConv_Real{$LVer}{"P"} = 1; |
| $UseConv_Real{$LVer}{"R"} = 0; # not implemented yet |
| |
| $UsedDump{$LVer}{"DWARF"} = 1; |
| |
| if($ABIRef->{"LibraryName"}=~/\.ko(\.|\Z)/) { |
| $In::Opt{"TargetComponent"} = "module"; |
| } |
| else { |
| $In::Opt{"TargetComponent"} = "object"; |
| } |
| } |
| |
| if(index($ABIRef->{"LibraryName"}, "libstdc++")==0 |
| or index($ABIRef->{"LibraryName"}, "libc++")==0) { |
| $In::Opt{"StdcxxTesting"} = 1; |
| } |
| |
| if($ABIRef->{"BinOnly"}) |
| { # ABI dump created with --binary option |
| $UsedDump{$LVer}{"BinOnly"} = 1; |
| } |
| else |
| { # default |
| $UsedDump{$LVer}{"SrcBin"} = 1; |
| } |
| |
| if(defined $ABIRef->{"Mode"} |
| and $ABIRef->{"Mode"} eq "Extended") |
| { # --ext option |
| $In::Opt{"ExtendedCheck"} = 1; |
| } |
| |
| if($ABIRef->{"Extra"}) { |
| $In::Opt{"ExtraDump"} = 1; |
| } |
| |
| if(not keys(%{$ABIRef->{"SymbolInfo"}})) |
| { # validation of old-version dumps |
| if(not $In::Opt{"ExtendedCheck"}) { |
| exitStatus("Invalid_Dump", "no symbols info in the ABI dump"); |
| } |
| } |
| |
| if(defined $ABIRef->{"GccConstants"}) |
| { # support for 3.0 |
| foreach my $Name (keys(%{$ABIRef->{"GccConstants"}})) { |
| $ABIRef->{"Constants"}{$Name}{"Value"} = $ABIRef->{"GccConstants"}{$Name}; |
| } |
| } |
| elsif(defined $ABIRef->{"CompilerConstants"}) |
| { |
| foreach my $Name (keys(%{$ABIRef->{"CompilerConstants"}})) { |
| $ABIRef->{"Constants"}{$Name}{"Value"} = $ABIRef->{"CompilerConstants"}{$Name}; |
| } |
| } |
| |
| if(not $ABIRef->{"SymbolVersion"}) { |
| $ABIRef->{"SymbolVersion"} = $ABIRef->{"SymVer"}; |
| } |
| |
| if(defined $ABIRef->{"TargetHeaders"}) { |
| $In::Desc{$LVer}{"TargetHeader"} = $ABIRef->{"TargetHeaders"}; |
| } |
| |
| foreach my $LName (keys(%{$ABIRef->{"Symbols"}})) |
| { |
| foreach my $Symbol (keys(%{$ABIRef->{"Symbols"}{$LName}})) { |
| $ABIRef->{"SymLib"}{$Symbol} = $LName; |
| } |
| } |
| |
| foreach my $LName (keys(%{$ABIRef->{"DepSymbols"}})) |
| { |
| foreach my $Symbol (keys(%{$ABIRef->{"DepSymbols"}{$LName}})) { |
| $ABIRef->{"DepSymLib"}{$Symbol} = $LName; |
| } |
| } |
| |
| $In::Opt{"Target"} = $ABIRef->{"Target"}; |
| $In::Desc{$LVer}{"Dump"} = 1; |
| |
| return $ABIRef; |
| } |
| |
| sub prepareCompare($) |
| { |
| my $LVer = $_[0]; |
| |
| foreach my $Lib_Name (keys(%{$In::ABI{$LVer}{"Symbols"}})) |
| { |
| foreach my $Symbol (keys(%{$In::ABI{$LVer}{"Symbols"}{$Lib_Name}})) |
| { |
| if($In::ABI{$LVer}{"Symbols"}{$Lib_Name}{$Symbol}<0) |
| { # data marked as -size in the dump |
| $GlobalDataObject{$LVer}{$Symbol} = -$In::ABI{$LVer}{"Symbols"}{$Lib_Name}{$Symbol}; |
| |
| if($Symbol=~/\A(.+?)\@.+/) { |
| $GlobalDataObject{$LVer}{$1} = $GlobalDataObject{$LVer}{$Symbol}; |
| } |
| } |
| } |
| } |
| |
| foreach my $TypeId (sort {$a<=>$b} keys(%{$TypeInfo{$LVer}})) |
| { # NOTE: order is important |
| if(not defined $TypeInfo{$LVer}{$TypeId}{"Tid"}) { |
| $TypeInfo{$LVer}{$TypeId}{"Tid"} = $TypeId; |
| } |
| |
| my $TInfo = $TypeInfo{$LVer}{$TypeId}; |
| if(defined $TInfo->{"Base"}) |
| { |
| foreach my $SubId (keys(%{$TInfo->{"Base"}})) |
| { |
| if($SubId eq $TypeId) |
| { # Fix erroneous ABI dump |
| delete($TypeInfo{$LVer}{$TypeId}{"Base"}{$SubId}); |
| next; |
| } |
| |
| $In::ABI{$LVer}{"Class_SubClasses"}{$SubId}{$TypeId} = 1; |
| } |
| } |
| |
| if($TInfo->{"BaseType"} eq $TypeId) |
| { # fix ABI dump |
| delete($TypeInfo{$LVer}{$TypeId}{"BaseType"}); |
| } |
| |
| if($TInfo->{"Type"} eq "Typedef" and not $TInfo->{"Artificial"}) |
| { |
| if(my $BTid = $TInfo->{"BaseType"}) |
| { |
| my $BName = $TypeInfo{$LVer}{$BTid}{"Name"}; |
| if(not $BName) |
| { # broken type |
| next; |
| } |
| if($TInfo->{"Name"} eq $BName) |
| { # typedef to "class Class" |
| # should not be registered in TName_Tid |
| next; |
| } |
| if(not $In::ABI{$LVer}{"TypedefBase"}{$TInfo->{"Name"}}) { |
| $In::ABI{$LVer}{"TypedefBase"}{$TInfo->{"Name"}} = $BName; |
| } |
| } |
| } |
| if(not $TName_Tid{$LVer}{$TInfo->{"Name"}}) |
| { # classes: class (id1), typedef (artificial, id2 > id1) |
| $TName_Tid{$LVer}{$TInfo->{"Name"}} = $TypeId; |
| } |
| } |
| } |
| |
| sub compareABIDumps($$) |
| { |
| my ($V1, $V2) = @_; |
| my $DumpPath1 = defaultDumpPath($In::Opt{"TargetLib"}, $V1); |
| my $DumpPath2 = defaultDumpPath($In::Opt{"TargetLib"}, $V2); |
| |
| unlink($DumpPath1); |
| unlink($DumpPath2); |
| |
| my $Pid = fork(); |
| if($Pid) |
| { # dump on two CPU cores |
| my @PARAMS = ("-dump", $In::Desc{1}{"Path"}, "-l", $In::Opt{"TargetLib"}); |
| @PARAMS = (@PARAMS, "-vnum", $V1); |
| |
| if($In::Desc{1}{"RelativeDirectory"}) { |
| @PARAMS = (@PARAMS, "-relpath", $In::Desc{1}{"RelativeDirectory"}); |
| } |
| if($In::Desc{1}{"OutputLogPath"}) { |
| @PARAMS = (@PARAMS, "-log-path", $In::Desc{1}{"OutputLogPath"}); |
| } |
| if($In::Opt{"CrossGcc"}) { |
| @PARAMS = (@PARAMS, "-cross-gcc", $In::Opt{"CrossGcc"}); |
| } |
| if($In::Opt{"Quiet"}) |
| { |
| @PARAMS = (@PARAMS, "-quiet"); |
| @PARAMS = (@PARAMS, "-logging-mode", "a"); |
| } |
| elsif($In::Opt{"LogMode"} and $In::Opt{"LogMode"} ne "w") |
| { # "w" is default |
| @PARAMS = (@PARAMS, "-logging-mode", $In::Opt{"LogMode"}); |
| } |
| if($In::Opt{"ExtendedCheck"}) { |
| @PARAMS = (@PARAMS, "-extended"); |
| } |
| if($In::Opt{"UserLang"}) { |
| @PARAMS = (@PARAMS, "-lang", $In::Opt{"UserLang"}); |
| } |
| if($In::Opt{"BinOnly"}) { |
| @PARAMS = (@PARAMS, "-binary"); |
| } |
| if($In::Opt{"SrcOnly"}) { |
| @PARAMS = (@PARAMS, "-source"); |
| } |
| if($In::Opt{"SortDump"}) { |
| @PARAMS = (@PARAMS, "-sort"); |
| } |
| if($In::Opt{"DumpFormat"} and $In::Opt{"DumpFormat"} ne "perl") { |
| @PARAMS = (@PARAMS, "-dump-format", $In::Opt{"DumpFormat"}); |
| } |
| if($In::Opt{"CheckHeadersOnly"}) { |
| @PARAMS = (@PARAMS, "-headers-only"); |
| } |
| if($In::Opt{"CxxIncompat"}) { |
| @PARAMS = (@PARAMS, "-cxx-incompatible"); |
| } |
| if($In::Opt{"Debug"}) |
| { |
| @PARAMS = (@PARAMS, "-debug"); |
| printMsg("INFO", "Executing perl $0 @PARAMS"); |
| } |
| system("perl", $0, @PARAMS); |
| if(not -f $DumpPath1) { |
| exit(1); |
| } |
| } |
| else |
| { # child |
| my @PARAMS = ("-dump", $In::Desc{2}{"Path"}, "-l", $In::Opt{"TargetLib"}); |
| @PARAMS = (@PARAMS, "-vnum", $V2); |
| |
| if($In::Desc{2}{"RelativeDirectory"}) { |
| @PARAMS = (@PARAMS, "-relpath", $In::Desc{2}{"RelativeDirectory"}); |
| } |
| if($In::Desc{2}{"OutputLogPath"}) { |
| @PARAMS = (@PARAMS, "-log-path", $In::Desc{2}{"OutputLogPath"}); |
| } |
| if($In::Opt{"CrossGcc"}) { |
| @PARAMS = (@PARAMS, "-cross-gcc", $In::Opt{"CrossGcc"}); |
| } |
| if($In::Opt{"Quiet"}) |
| { |
| @PARAMS = (@PARAMS, "-quiet"); |
| @PARAMS = (@PARAMS, "-logging-mode", "a"); |
| } |
| elsif($In::Opt{"LogMode"} and $In::Opt{"LogMode"} ne "w") |
| { # "w" is default |
| @PARAMS = (@PARAMS, "-logging-mode", $In::Opt{"LogMode"}); |
| } |
| if($In::Opt{"ExtendedCheck"}) { |
| @PARAMS = (@PARAMS, "-extended"); |
| } |
| if($In::Opt{"UserLang"}) { |
| @PARAMS = (@PARAMS, "-lang", $In::Opt{"UserLang"}); |
| } |
| if($In::Opt{"BinOnly"}) { |
| @PARAMS = (@PARAMS, "-binary"); |
| } |
| if($In::Opt{"SrcOnly"}) { |
| @PARAMS = (@PARAMS, "-source"); |
| } |
| if($In::Opt{"SortDump"}) { |
| @PARAMS = (@PARAMS, "-sort"); |
| } |
| if($In::Opt{"DumpFormat"} and $In::Opt{"DumpFormat"} ne "perl") { |
| @PARAMS = (@PARAMS, "-dump-format", $In::Opt{"DumpFormat"}); |
| } |
| if($In::Opt{"CheckHeadersOnly"}) { |
| @PARAMS = (@PARAMS, "-headers-only"); |
| } |
| if($In::Opt{"CxxIncompat"}) { |
| @PARAMS = (@PARAMS, "-cxx-incompatible"); |
| } |
| if($In::Opt{"Debug"}) |
| { |
| @PARAMS = (@PARAMS, "-debug"); |
| printMsg("INFO", "Executing perl $0 @PARAMS"); |
| } |
| system("perl", $0, @PARAMS); |
| if(not -f $DumpPath2) { |
| exit(1); |
| } |
| else { |
| exit(0); |
| } |
| } |
| waitpid($Pid, 0); |
| |
| my @CMP_PARAMS = ("-l", $In::Opt{"TargetLib"}); |
| @CMP_PARAMS = (@CMP_PARAMS, "-d1", $DumpPath1); |
| @CMP_PARAMS = (@CMP_PARAMS, "-d2", $DumpPath2); |
| if($In::Opt{"TargetTitle"} ne $In::Opt{"TargetLib"}) { |
| @CMP_PARAMS = (@CMP_PARAMS, "-title", $In::Opt{"TargetTitle"}); |
| } |
| if($In::Opt{"ShowRetVal"}) { |
| @CMP_PARAMS = (@CMP_PARAMS, "-show-retval"); |
| } |
| if($In::Opt{"CrossGcc"}) { |
| @CMP_PARAMS = (@CMP_PARAMS, "-cross-gcc", $In::Opt{"CrossGcc"}); |
| } |
| @CMP_PARAMS = (@CMP_PARAMS, "-logging-mode", "a"); |
| if($In::Opt{"Quiet"}) { |
| @CMP_PARAMS = (@CMP_PARAMS, "-quiet"); |
| } |
| if($In::Opt{"ReportFormat"} |
| and $In::Opt{"ReportFormat"} ne "html") |
| { # HTML is default format |
| @CMP_PARAMS = (@CMP_PARAMS, "-report-format", $In::Opt{"ReportFormat"}); |
| } |
| if($In::Opt{"OutputReportPath"}) { |
| @CMP_PARAMS = (@CMP_PARAMS, "-report-path", $In::Opt{"OutputReportPath"}); |
| } |
| if($In::Opt{"BinReportPath"}) { |
| @CMP_PARAMS = (@CMP_PARAMS, "-bin-report-path", $In::Opt{"BinReportPath"}); |
| } |
| if($In::Opt{"SrcReportPath"}) { |
| @CMP_PARAMS = (@CMP_PARAMS, "-src-report-path", $In::Opt{"SrcReportPath"}); |
| } |
| if($In::Opt{"LoggingPath"}) { |
| @CMP_PARAMS = (@CMP_PARAMS, "-log-path", $In::Opt{"LoggingPath"}); |
| } |
| if($In::Opt{"CheckHeadersOnly"}) { |
| @CMP_PARAMS = (@CMP_PARAMS, "-headers-only"); |
| } |
| if($In::Opt{"BinOnly"}) { |
| @CMP_PARAMS = (@CMP_PARAMS, "-binary"); |
| } |
| if($In::Opt{"SrcOnly"}) { |
| @CMP_PARAMS = (@CMP_PARAMS, "-source"); |
| } |
| if($In::Opt{"FilterPath"}) { |
| @CMP_PARAMS = (@CMP_PARAMS, "-filter", $In::Opt{"FilterPath"}); |
| } |
| if($In::Opt{"SkipInternalSymbols"}) { |
| @CMP_PARAMS = (@CMP_PARAMS, "-skip-internal-symbols", $In::Opt{"SkipInternalSymbols"}); |
| } |
| if($In::Opt{"SkipInternalTypes"}) { |
| @CMP_PARAMS = (@CMP_PARAMS, "-skip-internal-types", $In::Opt{"SkipInternalTypes"}); |
| } |
| if($In::Opt{"Debug"}) |
| { |
| @CMP_PARAMS = (@CMP_PARAMS, "-debug"); |
| printMsg("INFO", "Executing perl $0 @CMP_PARAMS"); |
| } |
| system("perl", $0, @CMP_PARAMS); |
| exit($?>>8); |
| } |
| |
| sub compareInit() |
| { |
| # read input XML descriptors or ABI dumps |
| if(not $In::Desc{1}{"Path"}) { |
| exitStatus("Error", "-old option is not specified"); |
| } |
| if(not -e $In::Desc{1}{"Path"}) { |
| exitStatus("Access_Error", "can't access \'".$In::Desc{1}{"Path"}."\'"); |
| } |
| |
| if(not $In::Desc{2}{"Path"}) { |
| exitStatus("Error", "-new option is not specified"); |
| } |
| if(not -e $In::Desc{2}{"Path"}) { |
| exitStatus("Access_Error", "can't access \'".$In::Desc{2}{"Path"}."\'"); |
| } |
| |
| detectDefaultPaths(undef, undef, "bin", undef); # to extract dumps |
| |
| printMsg("INFO", "Preparing, please wait ..."); |
| |
| if($In::Opt{"UseDumps"}) |
| { # --use-dumps |
| # parallel processing |
| if(isDump($In::Desc{1}{"Path"}) |
| or isDump($In::Desc{2}{"Path"})) { |
| exitStatus("Error", "please specify input XML descriptors instead of ABI dumps to use with -use-dumps option."); |
| } |
| |
| readDesc(createDesc($In::Desc{1}{"Path"}, 1), 1); |
| readDesc(createDesc($In::Desc{2}{"Path"}, 2), 2); |
| |
| compareABIDumps($In::Desc{1}{"Version"}, $In::Desc{2}{"Version"}); |
| } |
| |
| if(isDump($In::Desc{1}{"Path"})) |
| { |
| $In::ABI{1} = readABIDump(1, $In::Desc{1}{"Path"}); |
| initAliases(1); |
| |
| if(my $V = $In::Desc{1}{"TargetVersion"}) { |
| $In::Desc{1}{"Version"} = $V; |
| } |
| else { |
| $In::Desc{1}{"Version"} = $In::ABI{1}{"LibraryVersion"}; |
| } |
| |
| if(not defined $In::Desc{1}{"Version"}) { |
| $In::Desc{1}{"Version"} = "X"; |
| } |
| } |
| else |
| { |
| loadModule("ABIDump"); |
| readDesc(createDesc($In::Desc{1}{"Path"}, 1), 1); |
| |
| initLogging(1); |
| detectDefaultPaths("inc", "lib", undef, "gcc"); |
| createABIDump(1); |
| } |
| |
| if(isDump($In::Desc{2}{"Path"})) |
| { |
| $In::ABI{2} = readABIDump(2, $In::Desc{2}{"Path"}); |
| initAliases(2); |
| |
| if(my $V = $In::Desc{2}{"TargetVersion"}) { |
| $In::Desc{2}{"Version"} = $V; |
| } |
| else { |
| $In::Desc{2}{"Version"} = $In::ABI{2}{"LibraryVersion"}; |
| } |
| |
| if(not defined $In::Desc{2}{"Version"}) { |
| $In::Desc{2}{"Version"} = "Y"; |
| } |
| } |
| else |
| { |
| loadModule("ABIDump"); |
| readDesc(createDesc($In::Desc{2}{"Path"}, 2), 2); |
| |
| initLogging(2); |
| detectDefaultPaths("inc", "lib", undef, "gcc"); |
| createABIDump(2); |
| } |
| |
| clearSysFilesCache(1); |
| clearSysFilesCache(2); |
| |
| if(my $FPath = $In::Opt{"FilterPath"}) |
| { |
| if(not -f $FPath) { |
| exitStatus("Access_Error", "can't access \'".$FPath."\'"); |
| } |
| |
| if(my $Filt = readFile($FPath)) |
| { |
| readFilter($Filt, 1); |
| readFilter($Filt, 2); |
| } |
| } |
| |
| prepareCompare(1); |
| prepareCompare(2); |
| |
| if($In::Opt{"AppPath"} and not keys(%{$In::ABI{1}{"SymLib"}})) { |
| printMsg("WARNING", "the application ".getFilename($In::Opt{"AppPath"})." has no symbols imported from libraries"); |
| } |
| |
| prepareSymbols(1); |
| prepareSymbols(2); |
| |
| # Virtual Tables |
| registerVTable(1); |
| registerVTable(2); |
| |
| registerOverriding(1); |
| registerOverriding(2); |
| |
| setVirtFuncPositions(1); |
| setVirtFuncPositions(2); |
| |
| # Other |
| addParamNames(1); |
| addParamNames(2); |
| |
| detectChangedTypedefs(); |
| } |
| |
| sub compareAPIs($) |
| { |
| my $Level = $_[0]; |
| |
| readRules($Level); |
| loadModule("CallConv"); |
| |
| if($Level eq "Binary") { |
| printMsg("INFO", "Comparing ABIs ..."); |
| } |
| else { |
| printMsg("INFO", "Comparing APIs ..."); |
| } |
| |
| if($In::Opt{"CheckHeadersOnly"} |
| or $Level eq "Source") |
| { # added/removed in headers |
| detectAdded_H($Level); |
| detectRemoved_H($Level); |
| } |
| else |
| { # added/removed in libs |
| detectAdded($Level); |
| detectRemoved($Level); |
| } |
| |
| mergeSymbols($Level); |
| |
| if(not defined $In::Opt{"DisableConstantsCheck"}) |
| { |
| if(keys(%{$CheckedSymbols{$Level}})) { |
| mergeConstants($Level); |
| } |
| } |
| |
| $Cache{"mergeTypes"} = (); # free memory |
| |
| if($In::Opt{"CheckHeadersOnly"} |
| or $Level eq "Source") |
| { # added/removed in headers |
| mergeHeaders($Level); |
| } |
| else |
| { # added/removed in libs |
| mergeLibs($Level); |
| } |
| } |
| |
| sub initAliases($) |
| { |
| my $LVer = $_[0]; |
| |
| initABI($LVer); |
| |
| $SymbolInfo{$LVer} = $In::ABI{$LVer}{"SymbolInfo"}; |
| $TypeInfo{$LVer} = $In::ABI{$LVer}{"TypeInfo"}; |
| $TName_Tid{$LVer} = $In::ABI{$LVer}{"TName_Tid"}; |
| $Constants{$LVer} = $In::ABI{$LVer}{"Constants"}; |
| |
| initAliases_TypeAttr($LVer); |
| } |
| |
| sub scenario() |
| { |
| setTarget("default"); |
| |
| initAliases(1); |
| initAliases(2); |
| |
| $In::Opt{"Locale"} = "C.UTF-8"; |
| $In::Opt{"OrigDir"} = cwd(); |
| $In::Opt{"Tmp"} = tempdir(CLEANUP=>1); |
| $In::Opt{"TargetLibShort"} = libPart($In::Opt{"TargetLib"}, "shortest"); |
| |
| $In::Opt{"DoubleReport"} = 0; |
| $In::Opt{"JoinReport"} = 1; |
| |
| $In::Opt{"SysPaths"}{"include"} = []; |
| $In::Opt{"SysPaths"}{"lib"} = []; |
| $In::Opt{"SysPaths"}{"bin"} = []; |
| |
| $In::Opt{"CompileError"} = 0; |
| |
| if($In::Opt{"TargetComponent"}) { |
| $In::Opt{"TargetComponent"} = lc($In::Opt{"TargetComponent"}); |
| } |
| else |
| { # default: library |
| $In::Opt{"TargetComponent"} = "library"; |
| } |
| |
| if($In::Opt{"CountAllSymbols"}) { |
| $In::Opt{"CountPubSymbols"} = $In::Opt{"CountAllSymbols"}; |
| } |
| |
| foreach (keys(%{$In::Desc{0}})) |
| { # common options |
| $In::Desc{1}{$_} = $In::Desc{0}{$_}; |
| $In::Desc{2}{$_} = $In::Desc{0}{$_}; |
| } |
| |
| $In::Opt{"AddTemplateInstances"} = 1; |
| $In::Opt{"GccMissedMangling"} = 0; |
| |
| if($In::Opt{"StdOut"}) |
| { # enable quiet mode |
| $In::Opt{"Quiet"} = 1; |
| $In::Opt{"JoinReport"} = 1; |
| } |
| if(not $In::Opt{"LogMode"}) |
| { # default |
| $In::Opt{"LogMode"} = "w"; |
| } |
| |
| if($In::Opt{"UserLang"}) { |
| $In::Opt{"UserLang"} = uc($In::Opt{"UserLang"}); |
| } |
| |
| if(my $LoggingPath = $In::Opt{"LoggingPath"}) |
| { |
| $In::Desc{1}{"OutputLogPath"} = $LoggingPath; |
| $In::Desc{2}{"OutputLogPath"} = $LoggingPath; |
| if($In::Opt{"Quiet"}) { |
| $In::Opt{"DefaultLog"} = $LoggingPath; |
| } |
| } |
| |
| if($In::Opt{"Force"}) { |
| $In::Opt{"GccMissedMangling"} = 1; |
| } |
| |
| if($In::Opt{"Quick"}) { |
| $In::Opt{"AddTemplateInstances"} = 0; |
| } |
| if(my $DP = $In::Opt{"OutputDumpPath"}) |
| { # validate |
| if(not isDump($DP)) { |
| exitStatus("Error", "the dump path should be a path to *.dump or *.dump.".$In::Opt{"Ar"}." file"); |
| } |
| } |
| if($In::Opt{"BinOnly"} |
| and $In::Opt{"SrcOnly"}) |
| { # both --binary and --source |
| # is the default mode |
| if(not $In::Opt{"CmpSystems"}) |
| { |
| $In::Opt{"BinOnly"} = 0; |
| $In::Opt{"SrcOnly"} = 0; |
| } |
| |
| $In::Opt{"DoubleReport"} = 1; |
| $In::Opt{"JoinReport"} = 0; |
| |
| if($In::Opt{"OutputReportPath"}) |
| { # --report-path |
| $In::Opt{"DoubleReport"} = 0; |
| $In::Opt{"JoinReport"} = 1; |
| } |
| } |
| elsif($In::Opt{"BinOnly"} |
| or $In::Opt{"SrcOnly"}) |
| { # --binary or --source |
| $In::Opt{"DoubleReport"} = 0; |
| $In::Opt{"JoinReport"} = 0; |
| } |
| if($In::Opt{"UseXML"}) |
| { # --xml option |
| $In::Opt{"ReportFormat"} = "xml"; |
| $In::Opt{"DumpFormat"} = "xml"; |
| } |
| if($In::Opt{"ReportFormat"}) |
| { # validate |
| $In::Opt{"ReportFormat"} = lc($In::Opt{"ReportFormat"}); |
| if($In::Opt{"ReportFormat"}!~/\A(xml|html|htm)\Z/) { |
| exitStatus("Error", "unknown report format \'".$In::Opt{"ReportFormat"}."\'"); |
| } |
| if($In::Opt{"ReportFormat"} eq "htm") |
| { # HTM == HTML |
| $In::Opt{"ReportFormat"} = "html"; |
| } |
| elsif($In::Opt{"ReportFormat"} eq "xml") |
| { # --report-format=XML equal to --xml |
| $In::Opt{"UseXML"} = 1; |
| } |
| } |
| else |
| { # default: HTML |
| $In::Opt{"ReportFormat"} = "html"; |
| } |
| if($In::Opt{"DumpFormat"}) |
| { # validate |
| $In::Opt{"DumpFormat"} = lc($In::Opt{"DumpFormat"}); |
| if($In::Opt{"DumpFormat"}!~/\A(xml|perl)\Z/) { |
| exitStatus("Error", "unknown ABI dump format \'".$In::Opt{"DumpFormat"}."\'"); |
| } |
| if($In::Opt{"DumpFormat"} eq "xml") |
| { # --dump-format=XML equal to --xml |
| $In::Opt{"UseXML"} = 1; |
| } |
| } |
| else |
| { # default: Perl Data::Dumper |
| $In::Opt{"DumpFormat"} = "perl"; |
| } |
| if($In::Opt{"Quiet"} and $In::Opt{"LogMode"}!~/a|n/) |
| { # --quiet log |
| if(-f $In::Opt{"DefaultLog"}) { |
| unlink($In::Opt{"DefaultLog"}); |
| } |
| } |
| if($In::Opt{"ExtraInfo"}) { |
| $In::Opt{"CheckUndefined"} = 1; |
| } |
| |
| if($In::Opt{"TestTool"} and $In::Opt{"UseDumps"}) |
| { # --test && --use-dumps == --test-dump |
| $In::Opt{"TestDump"} = 1; |
| } |
| if($In::Opt{"Tolerant"}) |
| { # enable all |
| $In::Opt{"Tolerance"} = 1234; |
| } |
| if($In::Opt{"Help"}) |
| { |
| helpMsg(); |
| exit(0); |
| } |
| if($In::Opt{"InfoMsg"}) |
| { |
| infoMsg(); |
| exit(0); |
| } |
| if($In::Opt{"ShowVersion"}) |
| { |
| printMsg("INFO", "ABI Compliance Checker (ABICC) $TOOL_VERSION\nCopyright (C) 2019 Andrey Ponomarenko's ABI Laboratory\nLicense: GNU LGPL 2.1 <http://www.gnu.org/licenses/>\nThis program is free software: you can redistribute it and/or modify it.\n\nWritten by Andrey Ponomarenko."); |
| exit(0); |
| } |
| if($In::Opt{"DumpVersion"}) |
| { |
| printMsg("INFO", $TOOL_VERSION); |
| exit(0); |
| } |
| if($In::Opt{"ExtendedCheck"}) { |
| $In::Opt{"CheckHeadersOnly"} = 1; |
| } |
| if($In::Opt{"SystemRoot"}) |
| { # user defined root |
| if(not -e $In::Opt{"SystemRoot"}) { |
| exitStatus("Access_Error", "can't access \'".$In::Opt{"SystemRoot"}."\'"); |
| } |
| $In::Opt{"SystemRoot"}=~s/[\/]+\Z//g; |
| if($In::Opt{"SystemRoot"}) { |
| $In::Opt{"SystemRoot"} = getAbsPath($In::Opt{"SystemRoot"}); |
| } |
| } |
| $Data::Dumper::Sortkeys = 1; |
| |
| if($In::Opt{"SortDump"}) |
| { |
| $Data::Dumper::Useperl = 1; |
| $Data::Dumper::Sortkeys = \&dump_sorting; |
| } |
| |
| if(my $TargetLibsPath = $In::Opt{"TargetLibsPath"}) |
| { |
| if(not -f $TargetLibsPath) { |
| exitStatus("Access_Error", "can't access file \'$TargetLibsPath\'"); |
| } |
| foreach my $Lib (split(/\s*\n\s*/, readFile($TargetLibsPath))) |
| { |
| if($In::Opt{"OS"} eq "windows") { |
| $In::Opt{"TargetLibs"}{lc($Lib)} = 1; |
| } |
| else { |
| $In::Opt{"TargetLibs"}{$Lib} = 1; |
| } |
| } |
| } |
| if(my $TPath = $In::Opt{"TargetHeadersPath"}) |
| { # --headers-list |
| if(not -f $TPath) { |
| exitStatus("Access_Error", "can't access file \'$TPath\'"); |
| } |
| |
| $In::Desc{1}{"TargetHeader"} = {}; |
| $In::Desc{2}{"TargetHeader"} = {}; |
| |
| foreach my $Header (split(/\s*\n\s*/, readFile($TPath))) |
| { |
| my $Name = getFilename($Header); |
| $In::Desc{1}{"TargetHeader"}{$Name} = 1; |
| $In::Desc{2}{"TargetHeader"}{$Name} = 1; |
| } |
| } |
| if($In::Opt{"TargetHeader"}) |
| { # --header |
| $In::Desc{1}{"TargetHeader"} = {}; |
| $In::Desc{2}{"TargetHeader"} = {}; |
| |
| my $Name = getFilename($In::Opt{"TargetHeader"}); |
| $In::Desc{1}{"TargetHeader"}{$Name} = 1; |
| $In::Desc{2}{"TargetHeader"}{$Name} = 1; |
| } |
| if($In::Opt{"TestABIDumper"}) |
| { |
| if($In::Opt{"OS"} ne "linux") { |
| exitStatus("Error", "-test-abi-dumper option is available on Linux only"); |
| } |
| } |
| if($In::Opt{"TestTool"} |
| or $In::Opt{"TestDump"} |
| or $In::Opt{"TestABIDumper"}) |
| { # --test, --test-dump |
| detectDefaultPaths(undef, undef, "bin", "gcc"); # to compile libs |
| loadModule("RegTests"); |
| testTool(); |
| exit(0); |
| } |
| if($In::Opt{"DumpSystem"}) |
| { # --dump-system |
| if(not $In::Opt{"TargetSysInfo"}) |
| { |
| if(-d $MODULES_DIR."/Targets/" |
| and -d $MODULES_DIR."/Targets/".$In::Opt{"Target"}) { |
| $In::Opt{"TargetSysInfo"} = $MODULES_DIR."/Targets/".$In::Opt{"Target"}; |
| } |
| } |
| |
| if(not $In::Opt{"TargetSysInfo"}) { |
| exitStatus("Error", "-sysinfo option should be specified to dump system ABI"); |
| } |
| |
| if(not -d $In::Opt{"TargetSysInfo"}) { |
| exitStatus("Access_Error", "can't access \'".$In::Opt{"TargetSysInfo"}."\'"); |
| } |
| |
| loadModule("SysCheck"); |
| if($In::Opt{"DumpSystem"}=~/\.(xml|desc)\Z/) |
| { # system XML descriptor |
| if(not -f $In::Opt{"DumpSystem"}) { |
| exitStatus("Access_Error", "can't access file \'".$In::Opt{"DumpSystem"}."\'"); |
| } |
| |
| my $SDesc = readFile($In::Opt{"DumpSystem"}); |
| if(my $RelDir = $In::Desc{1}{"RelativeDirectory"}) { |
| $SDesc=~s/\{RELPATH\}/$RelDir/g; |
| } |
| |
| readSysDesc($SDesc); |
| } |
| elsif(defined $In::Opt{"SystemRoot"}) |
| { # -sysroot "/" option |
| # default target: /usr/lib, /usr/include |
| # search libs: /usr/lib and /lib |
| my $SystemRoot = $In::Opt{"SystemRoot"}; |
| |
| if(not -e $SystemRoot."/usr/lib") { |
| exitStatus("Access_Error", "can't access '".$SystemRoot."/usr/lib'"); |
| } |
| if(not -e $SystemRoot."/lib") { |
| exitStatus("Access_Error", "can't access '".$SystemRoot."/lib'"); |
| } |
| if(not -e $SystemRoot."/usr/include") { |
| exitStatus("Access_Error", "can't access '".$SystemRoot."/usr/include'"); |
| } |
| readSysDesc(" |
| <name> |
| ".$In::Opt{"DumpSystem"}." |
| </name> |
| <headers> |
| $SystemRoot/usr/include |
| </headers> |
| <libs> |
| $SystemRoot/usr/lib |
| </libs> |
| <search_libs> |
| $SystemRoot/lib |
| </search_libs>"); |
| } |
| else { |
| exitStatus("Error", "-sysroot <dirpath> option should be specified, usually it's \"/\""); |
| } |
| detectDefaultPaths(undef, undef, "bin", "gcc"); # to check symbols |
| if($In::Opt{"Target"} eq "windows") |
| { # to run dumpbin.exe |
| # and undname.exe |
| checkWin32Env(); |
| } |
| dumpSystem(); |
| exit(0); |
| } |
| |
| if($In::Opt{"CmpSystems"}) |
| { # --cmp-systems |
| detectDefaultPaths(undef, undef, "bin", undef); # to extract dumps |
| loadModule("SysCheck"); |
| |
| if(not $In::Opt{"BinOnly"} |
| and not $In::Opt{"SrcOnly"}) |
| { # default |
| $In::Opt{"BinOnly"} = 1; |
| } |
| |
| cmpSystems($In::Desc{1}{"Path"}, $In::Desc{2}{"Path"}); |
| exit(0); |
| } |
| |
| if(not $In::Opt{"CountPubSymbols"}) |
| { |
| if(not $In::Opt{"TargetLib"}) { |
| exitStatus("Error", "library name is not selected (-l option)"); |
| } |
| else |
| { # validate library name |
| if($In::Opt{"TargetLib"}=~/[\*\/\\]/) { |
| exitStatus("Error", "\"\\\", \"\/\" and \"*\" symbols are not allowed in the library name"); |
| } |
| } |
| } |
| |
| if(not $In::Opt{"TargetTitle"}) { |
| $In::Opt{"TargetTitle"} = $In::Opt{"TargetLib"}; |
| } |
| |
| if(my $SymbolsListPath = $In::Opt{"SymbolsListPath"}) |
| { |
| if(not -f $SymbolsListPath) { |
| exitStatus("Access_Error", "can't access file \'$SymbolsListPath\'"); |
| } |
| foreach my $S (split(/\s*\n\s*/, readFile($SymbolsListPath))) |
| { |
| $In::Desc{1}{"SymbolsList"}{$S} = 1; |
| $In::Desc{2}{"SymbolsList"}{$S} = 1; |
| } |
| } |
| if(my $TypesListPath = $In::Opt{"TypesListPath"}) |
| { |
| if(not -f $TypesListPath) { |
| exitStatus("Access_Error", "can't access file \'$TypesListPath\'"); |
| } |
| foreach my $Type (split(/\s*\n\s*/, readFile($TypesListPath))) |
| { |
| $In::Desc{1}{"TypesList"}{$Type} = 1; |
| $In::Desc{2}{"TypesList"}{$Type} = 1; |
| } |
| } |
| if(my $SymbolsListPath = $In::Opt{"SkipSymbolsListPath"}) |
| { |
| if(not -f $SymbolsListPath) { |
| exitStatus("Access_Error", "can't access file \'$SymbolsListPath\'"); |
| } |
| foreach my $Interface (split(/\s*\n\s*/, readFile($SymbolsListPath))) |
| { |
| $In::Desc{1}{"SkipSymbols"}{$Interface} = 1; |
| $In::Desc{2}{"SkipSymbols"}{$Interface} = 1; |
| } |
| } |
| if(my $TypesListPath = $In::Opt{"SkipTypesListPath"}) |
| { |
| if(not -f $TypesListPath) { |
| exitStatus("Access_Error", "can't access file \'$TypesListPath\'"); |
| } |
| foreach my $Type (split(/\s*\n\s*/, readFile($TypesListPath))) |
| { |
| $In::Desc{1}{"SkipTypes"}{$Type} = 1; |
| $In::Desc{2}{"SkipTypes"}{$Type} = 1; |
| } |
| } |
| if(my $HeadersList = $In::Opt{"SkipHeadersPath"}) |
| { |
| if(not -f $HeadersList) { |
| exitStatus("Access_Error", "can't access file \'$HeadersList\'"); |
| } |
| foreach my $Path (split(/\s*\n\s*/, readFile($HeadersList))) |
| { |
| my ($CPath, $Type) = classifyPath($Path); |
| $In::Desc{1}{"SkipHeaders"}{$Type}{$CPath} = 1; |
| $In::Desc{2}{"SkipHeaders"}{$Type}{$CPath} = 1; |
| } |
| } |
| if(my $ParamNamesPath = $In::Opt{"ParamNamesPath"}) |
| { |
| if(not -f $ParamNamesPath) { |
| exitStatus("Access_Error", "can't access file \'$ParamNamesPath\'"); |
| } |
| foreach my $Line (split(/\n/, readFile($ParamNamesPath))) |
| { |
| if($Line=~s/\A(\w+)\;//) |
| { |
| my $Interface = $1; |
| if($Line=~/;(\d+);/) |
| { |
| while($Line=~s/(\d+);(\w+)//) { |
| $AddSymbolParams{$Interface}{$1}=$2; |
| } |
| } |
| else |
| { |
| my $Num = 0; |
| foreach my $Name (split(/;/, $Line)) { |
| $AddSymbolParams{$Interface}{$Num++}=$Name; |
| } |
| } |
| } |
| } |
| } |
| |
| if(my $AppPath = $In::Opt{"AppPath"}) |
| { |
| if(not -f $AppPath) { |
| exitStatus("Access_Error", "can't access file \'$AppPath\'"); |
| } |
| |
| detectDefaultPaths(undef, undef, "bin", "gcc"); |
| foreach my $Symbol (readSymbols_App($AppPath)) { |
| $In::Opt{"SymbolsList_App"}{$Symbol} = 1; |
| } |
| } |
| |
| if(my $Path = $In::Opt{"CountPubSymbols"}) |
| { |
| if(not -e $Path) { |
| exitStatus("Access_Error", "can't access \'$Path\'"); |
| } |
| |
| $In::ABI{1} = readABIDump(1, $Path); |
| initAliases(1); |
| |
| foreach my $Id (keys(%{$SymbolInfo{1}})) |
| { |
| my $MnglName = $SymbolInfo{1}{$Id}{"MnglName"}; |
| if(not $MnglName) { |
| $MnglName = $SymbolInfo{1}{$Id}{"ShortName"} |
| } |
| |
| if(my $SV = $In::ABI{1}{"SymbolVersion"}{$MnglName}) { |
| $CompSign{1}{$SV} = $SymbolInfo{1}{$Id}; |
| } |
| else { |
| $CompSign{1}{$MnglName} = $SymbolInfo{1}{$Id}; |
| } |
| |
| if(my $Alias = $CompSign{1}{$MnglName}{"Alias"}) { |
| $CompSign{1}{$Alias} = $SymbolInfo{1}{$Id}; |
| } |
| } |
| |
| my $Count = 0; |
| foreach my $Symbol (sort keys(%{$CompSign{1}})) |
| { |
| if($CompSign{1}{$Symbol}{"PureVirt"}) { |
| next; |
| } |
| |
| if(not $CompSign{1}{$Symbol}{"Header"} and not $In::Opt{"CountAllSymbols"}) |
| { |
| if(index($CompSign{1}{$Symbol}{"Source"}, ".f")==-1) |
| { # Fortran |
| next; |
| } |
| } |
| |
| $Count += symbolFilter($Symbol, $CompSign{1}{$Symbol}, "Affected + InlineVirt", "Binary", 1); |
| } |
| |
| printMsg("INFO", $Count); |
| exit(0); |
| } |
| |
| if($In::Opt{"DumpABI"}) |
| { |
| createABIFile(1, $In::Opt{"DumpABI"}); |
| |
| if($In::Opt{"CompileError"}) { |
| exit(getErrorCode("Compile_Error")); |
| } |
| |
| exit(0); |
| } |
| |
| # default: compare APIs |
| compareInit(); |
| if($In::Opt{"JoinReport"} or $In::Opt{"DoubleReport"}) |
| { |
| compareAPIs("Binary"); |
| compareAPIs("Source"); |
| } |
| elsif($In::Opt{"BinOnly"}) { |
| compareAPIs("Binary"); |
| } |
| elsif($In::Opt{"SrcOnly"}) { |
| compareAPIs("Source"); |
| } |
| exitReport(); |
| } |
| |
| scenario(); |