#!/usr/bin/perl
###########################################################################
# ABI Compliance Checker (ACC) 1.96.8
# A tool for checking backward binary compatibility of a C/C++ library API
#
# Copyright (C) 2009-2010 The Linux Foundation.
# Copyright (C) 2009-2011 Institute for System Programming, RAS.
# Copyright (C) 2011-2012 Nokia Corporation and/or its subsidiary(-ies).
# Copyright (C) 2011-2012 ROSA Laboratory.
#
# Written by Andrey Ponomarenko
#
# PLATFORMS
# =========
#  Linux, FreeBSD, Mac OS X, Haiku, MS Windows, Symbian
#
# REQUIREMENTS
# ============
#  Linux
#    - G++ (3.0-4.6.2, recommended >= 4.5)
#    - GNU Binutils (readelf, c++filt, objdump)
#    - Perl 5 (5.8-5.14)
#
#  Mac OS X
#    - Xcode (gcc, otool, c++filt)
#
#  MS Windows
#    - MinGW (3.0-4.6.2, recommended >= 4.5)
#    - MS Visual C++ (dumpbin, undname, cl)
#    - Active Perl 5 (5.8-5.14)
#    - Sigcheck v1.71 or newer
#    - Info-ZIP 3.0 (zip, unzip)
#    - Add gcc.exe path (C:\MinGW\bin\) to your system PATH variable
#    - Run vsvars32.bat (C:\Microsoft Visual Studio 9.0\Common7\Tools\)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License or the GNU Lesser
# General Public License as published by the Free Software Foundation.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# and the GNU Lesser General Public License along with this program.
# If not, see <http://www.gnu.org/licenses/>.
###########################################################################
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 move);
use Cwd qw(abs_path cwd);
use Data::Dumper;
use Config;

my $TOOL_VERSION = "1.96.8";
my $ABI_DUMP_VERSION = "2.10.1";
my $OLDEST_SUPPORTED_VERSION = "1.18";
my $XML_REPORT_VERSION = "1.0";
my $OSgroup = get_OSgroup();
my $ORIG_DIR = cwd();
my $TMP_DIR = tempdir(CLEANUP=>1);

# Internal modules
my $MODULES_DIR = get_Modules();
push(@INC, get_dirname($MODULES_DIR));
# Rules DB
my %RULES_PATH = (
    "Binary" => $MODULES_DIR."/RulesBin.xml",
    "Source" => $MODULES_DIR."/RulesSrc.xml");

my ($Help, $ShowVersion, %Descriptor, $TargetLibraryName, $GenerateTemplate,
$TestTool, $DumpAPI, $SymbolsListPath, $CheckHeadersOnly_Opt, $UseDumps,
$CheckObjectsOnly_Opt, $AppPath, $StrictCompat, $DumpVersion, $ParamNamesPath,
%RelativeDirectory, $TargetLibraryFName, $TestDump, $CheckImpl, $LoggingPath,
%TargetVersion, $InfoMsg, $UseOldDumps, %UsedDump, $CrossGcc, %OutputLogPath,
$OutputReportPath, $OutputDumpPath, $ShowRetVal, $SystemRoot_Opt, $DumpSystem,
$CmpSystems, $TargetLibsPath, $Debug, $CrossPrefix, $UseStaticLibs, $NoStdInc,
$TargetComponent_Opt, $TargetSysInfo, $TargetHeader, $ExtendedCheck, $Quiet,
$SkipHeadersPath, $Cpp2003, $LogMode, $StdOut, $ListAffected, $ReportFormat,
$UserLang, $TargetHeadersPath);

my $CmdName = get_filename($0);
my %OS_LibExt = (
    "dynamic" => {
        "default"=>"so",
        "macos"=>"dylib",
        "windows"=>"dll",
        "symbian"=>"dso"
    },
    "static" => {
        "default"=>"a",
        "windows"=>"lib",
        "symbian"=>"lib"
    }
);

my %OS_Archive = (
    "windows"=>"zip",
    "default"=>"tar.gz"
);

my %ERROR_CODE = (
    # Compatible verdict
    "Compatible"=>0,
    "Success"=>0,
    # Incompatible verdict
    "Incompatible"=>1,
    # Undifferentiated error code
    "Error"=>2,
    # System command is not found
    "Not_Found"=>3,
    # Cannot access input files
    "Access_Error"=>4,
    # Cannot compile header files
    "Cannot_Compile"=>5,
    # Header compiled with errors
    "Compile_Error"=>6,
    # Invalid input ABI dump
    "Invalid_Dump"=>7,
    # Incompatible version of ABI dump
    "Dump_Version"=>8,
    # Cannot find a module
    "Module_Error"=>9,
    # Empty intersection between
    # headers and shared objects
    "Empty_Intersection"=>10,
    # Empty set of symbols in headers
    "Empty_Set"=>11
);

my %HomePage = (
    "Wiki"=>"http://ispras.linux-foundation.org/index.php/ABI_compliance_checker",
    "Dev"=>"http://forge.ispras.ru/projects/abi-compliance-checker"
);

my $ShortUsage = "ABI Compliance Checker (ACC) $TOOL_VERSION
A tool for checking backward binary compatibility of a C/C++ library API
Copyright (C) 2012 ROSA Laboratory
License: GNU LGPL or GNU GPL

Usage: $CmdName [options]
Example: $CmdName -lib NAME -d1 OLD.xml -d2 NEW.xml

OLD.xml and NEW.xml are XML-descriptors:

    <version>
        1.0
    </version>

    <headers>
        /path/to/headers/
    </headers>

    <libs>
        /path/to/libraries/
    </libs>

More info: $CmdName --help\n";

if($#ARGV==-1) {
    printMsg("INFO", $ShortUsage);
    exit(0);
}

foreach (2 .. $#ARGV)
{# correct comma separated options
    if($ARGV[$_-1] eq ",") {
        $ARGV[$_-2].=",".$ARGV[$_];
        splice(@ARGV, $_-1, 2);
    }
    elsif($ARGV[$_-1]=~/,\Z/) {
        $ARGV[$_-1].=$ARGV[$_];
        splice(@ARGV, $_, 1);
    }
    elsif($ARGV[$_]=~/\A,/
    and $ARGV[$_] ne ",") {
        $ARGV[$_-1].=$ARGV[$_];
        splice(@ARGV, $_, 1);
    }
}

GetOptions("h|help!" => \$Help,
  "i|info!" => \$InfoMsg,
  "v|version!" => \$ShowVersion,
  "dumpversion!" => \$DumpVersion,
# general options
  "l|lib|library=s" => \$TargetLibraryName,
  "d1|old|o=s" => \$Descriptor{1}{"Path"},
  "d2|new|n=s" => \$Descriptor{2}{"Path"},
  "dump|dump-abi|dump_abi=s" => \$DumpAPI,
  "old-dumps!" => \$UseOldDumps,
# extra options
  "d|descriptor-template!" => \$GenerateTemplate,
  "app|application=s" => \$AppPath,
  "static-libs!" => \$UseStaticLibs,
  "cross-gcc=s" => \$CrossGcc,
  "cross-prefix=s" => \$CrossPrefix,
  "sysroot=s" => \$SystemRoot_Opt,
  "v1|version1|vnum=s" => \$TargetVersion{1},
  "v2|version2=s" => \$TargetVersion{2},
  "s|strict!" => \$StrictCompat,
  "symbols-list=s" => \$SymbolsListPath,
  "skip-headers=s" => \$SkipHeadersPath,
  "headers-only|headers_only!" => \$CheckHeadersOnly_Opt,
  "objects-only!" => \$CheckObjectsOnly_Opt,
  "check-impl|check-implementation!" => \$CheckImpl,
  "show-retval!" => \$ShowRetVal,
  "use-dumps!" => \$UseDumps,
  "nostdinc!" => \$NoStdInc,
  "dump-system=s" => \$DumpSystem,
  "sysinfo=s" => \$TargetSysInfo,
  "cmp-systems!" => \$CmpSystems,
  "libs-list=s" => \$TargetLibsPath,
  "headers-list=s" => \$TargetHeadersPath,
  "header=s" => \$TargetHeader,
  "ext|extended!" => \$ExtendedCheck,
  "q|quiet!" => \$Quiet,
  "stdout!" => \$StdOut,
  "report-format=s" => \$ReportFormat,
  "lang=s" => \$UserLang,
# other options
  "test!" => \$TestTool,
  "test-dump!" => \$TestDump,
  "debug!" => \$Debug,
  "cpp-compatible!" => \$Cpp2003,
  "p|params=s" => \$ParamNamesPath,
  "relpath1|relpath=s" => \$RelativeDirectory{1},
  "relpath2=s" => \$RelativeDirectory{2},
  "dump-path=s" => \$OutputDumpPath,
  "report-path=s" => \$OutputReportPath,
  "log-path=s" => \$LoggingPath,
  "log1-path=s" => \$OutputLogPath{1},
  "log2-path=s" => \$OutputLogPath{2},
  "logging-mode=s" => \$LogMode,
  "list-affected!" => \$ListAffected,
  "l-full|lib-full=s" => \$TargetLibraryFName,
  "component=s" => \$TargetComponent_Opt
) or ERR_MESSAGE();

sub ERR_MESSAGE()
{
    printMsg("INFO", "\n".$ShortUsage);
    exit($ERROR_CODE{"Error"});
}

my $LIB_TYPE = $UseStaticLibs?"static":"dynamic";
my $SLIB_TYPE = $LIB_TYPE;
if($OSgroup!~/macos|windows/ and $SLIB_TYPE eq "dynamic")
{ # show as "shared" library
    $SLIB_TYPE = "shared";
}
my $LIB_EXT = getLIB_EXT($OSgroup);
my $AR_EXT = getAR_EXT($OSgroup);
my $BYTE_SIZE = 8;
my $COMMON_LOG_PATH = "logs/run.log";

my $HelpMessage="
NAME:
  ABI Compliance Checker ($CmdName)
  Check backward binary compatibility of a $SLIB_TYPE C/C++ library API

DESCRIPTION:
  ABI Compliance Checker (ACC) is a tool for checking backward binary
  compatibility of a $SLIB_TYPE C/C++ library API. The tool checks header
  files and $SLIB_TYPE libraries (*.$LIB_EXT) of old and new versions and
  analyzes changes in Application Binary Interface (ABI=API+compiler ABI)
  that may break binary compatibility: changes in calling stack, v-table
  changes, removed symbols, 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.

  The tool is intended for library developers and OS maintainers who are
  interested in ensuring binary compatibility, i.e. allow old applications
  to run with new library versions without the need to recompile. Also it
  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 or GNU GPL.

USAGE:
  $CmdName [options]

EXAMPLE:
  $CmdName -lib NAME -d1 OLD.xml -d2 NEW.xml

  OLD.xml and NEW.xml are XML-descriptors:

    <version>
        1.0
    </version>

    <headers>
        /path1/to/header(s)/
        /path2/to/header(s)/
         ...
    </headers>

    <libs>
        /path1/to/library(ies)/
        /path2/to/library(ies)/
         ...
    </libs>

INFORMATION OPTIONS:
  -h|-help
      Print this help.

  -i|-info
      Print complete info.

  -v|-version
      Print version information.

  -dumpversion
      Print the tool version ($TOOL_VERSION) and don't do anything else.

GENERAL OPTIONS:
  -l|-lib|-library <name>
      Library name (without version).
      It affects only on the path and the title of the report.

  -d1|-old|-o <path>
      Descriptor of 1st (old) library version.
      It may be one of the following:
      
         1. XML-descriptor (VERSION.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>

                 ... (XML-descriptor template
                         can be generated by -d option)
             
         2. ABI dump generated by -dump option
         3. Directory with headers and/or $SLIB_TYPE libraries
         4. Single header file
         5. Single $SLIB_TYPE library
         6. Comma separated list of headers and/or libraries

      If you are using an 2-6 descriptor types then you should
      specify version numbers with -v1 <num> and -v2 <num> options too.

      For more information, please see:
        http://ispras.linux-foundation.org/index.php/Library_Descriptor

  -d2|-new|-n <path>
      Descriptor of 2nd (new) library version.

  -dump|-dump-abi <descriptor path(s)>
      Dump library ABI to gzipped TXT format file. You can transfer it
      anywhere and pass instead of the descriptor. Also it can be used
      for debugging the tool. Compatible dump versions: ".majorVersion($ABI_DUMP_VERSION).".0<=V<=$ABI_DUMP_VERSION

  -old-dumps
      Enable support for old-version ABI dumps ($OLDEST_SUPPORTED_VERSION<=V<".majorVersion($ABI_DUMP_VERSION).".0).\n";

sub HELP_MESSAGE() {
    printMsg("INFO", $HelpMessage."
MORE INFO:
     $CmdName --info\n");
}

sub INFO_MESSAGE()
{
    printMsg("INFO", "$HelpMessage
EXTRA OPTIONS:
  -d|-descriptor-template
      Create XML-descriptor template ./VERSION.xml

  -app|-application <path>
      This option allows to specify the application that should be checked
      for portability to the new library version.

  -static-libs
      Check static libraries instead of the shared ones. The <libs> section
      of the XML-descriptor should point to static libraries location.

  -cross-gcc <path>
      Path to the cross GCC compiler to use instead of the usual (host) GCC.

  -cross-prefix <prefix>
      GCC toolchain prefix.

  -sysroot <dirpath>
      Specify the alternative root directory. The tool will search for include
      paths in the <dirpath>/usr/include and <dirpath>/usr/lib directories.

  -v1|-version1 <num>
      Specify 1st library version outside the descriptor. This option is needed
      if you have prefered an alternative descriptor type (see -d1 option).

      In general case you should specify it in the XML-descriptor:
          <version>
              VERSION
          </version>

  -v2|-version2 <num>
      Specify 2nd library version outside the descriptor.

  -s|-strict
      Treat all compatibility warnings as problems. Add a number of \"Low\"
      severity problems to the return value of the tool.

  -headers-only
      Check header files without $SLIB_TYPE libraries. It is easy to run, but may
      provide a low quality compatibility report with false positives and
      without detecting of added/removed symbols.
      
      Alternatively you can write \"none\" word to the <libs> section
      in the XML-descriptor:
          <libs>
              none
          </libs>

  -objects-only
      Check $SLIB_TYPE libraries without header files. It is easy to run, but may
      provide a low quality compatibility report with false positives and
      without analysis of changes in parameters and data types.

      Alternatively you can write \"none\" word to the <headers> section
      in the XML-descriptor:
          <headers>
              none
          </headers>

  -check-impl|-check-implementation
      Compare canonified disassembled binary code of $SLIB_TYPE libraries to
      detect changes in the implementation. Add \'Problems with Implementation\'
      section to the report.

  -show-retval
      Show the symbol's return type in the report.

  -symbols-list <path>
      This option allows to specify a file with a list of symbols (mangled
      names in C++) that should be checked, other symbols will not be checked.

  -skip-headers <path>
      The file with the list of header files, that should not be checked.

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

  -nostdinc
      Do not search the GCC standard system directories for header files.

  -dump-system <name> -sysroot <dirpath>
      Find all the shared libraries and header files in <dirpath> 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>

  -sysinfo <dir>
      This option may be used with -dump-system to dump ABI of operating
      systems and configure the dumping process.
      Default:
          modules/Targets/{unix, symbian, windows}

  -cmp-systems -d1 sys_dumps/<name1>/<arch> -d2 sys_dumps/<name2>/<arch>
      Compare two system ABI dumps. Create compatibility reports for each
      library and the common HTML report including the summary of test
      results for all checked libraries. Report will be generated to:
          sys_compat_reports/<name1>_to_<name2>/<arch>

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

  -header <name>
      Check/Dump ABI of this header only.

  -headers-list <path>
      The file with a list of headers, that should be checked/dumped.

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

  -q|-quiet
      Print all messages to the file instead of stdout and stderr.
      Default path (can be changed by -log-path option):
          $COMMON_LOG_PATH

  -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

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

OTHER 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 to check if the tool works correctly in the current environment.

  -test-dump
      Test ability to create, read and compare ABI dumps.
      
  -debug
      Debugging mode. Print debug info on the screen. Save intermediate
      analysis stages in the debug directory:
          debug/<library>/<version>/

  -cpp-compatible
      If your header file is written in C language and can be compiled by
      the C++ compiler (i.e. doesn't contain C++-keywords and other bad
      things), then you can tell ACC about this and speedup the analysis.

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

  -relpath <path>
      Replace {RELPATH} macros to <path> in the XML-descriptor used
      for dumping the library ABI (see -dump option).
  
  -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).

  -dump-path <path>
      Specify a file path (*.abi.$AR_EXT) where to generate an ABI dump.
      Default: 
          abi_dumps/<library>/<library>_<version>.abi.$AR_EXT

  -report-path <path>
      Compatibility report path.
      Default: 
          compat_reports/<library name>/<v1>_to_<v2>/abi_compat_report.html

  -log-path <path>
      Log path for all messages.
      Default:
          logs/<library>/<version>/log.txt

  -log1-path <path>
      Log path for 1st version of a library.
      Default:
          logs/<library name>/<v1>/log.txt

  -log2-path <path>
      Log path for 2nd version of a library.
      Default:
          logs/<library 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

  -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 name:
          abi_affected.txt

  -component <name>
      The component name in the title and summary of the HTML report.
      Default:
          library
      
  -l-full|-lib-full <name>
      Change library name in the report title to <name>. By default
      will be displayed a name specified by -l option.

REPORT:
    Compatibility report will be generated to:
        compat_reports/<library name>/<v1>_to_<v2>/abi_compat_report.html

    Log will be generated to:
        logs/<library name>/<v1>/log.txt
        logs/<library 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.

REPORT BUGS TO:
    abi-compliance-checker\@linuxtesting.org
    Andrey Ponomarenko <aponomarenko\@mandriva.org>

MORE INFORMATION:
    ".$HomePage{"Wiki"}."
    ".$HomePage{"Dev"}."\n");
}

my $DescriptorTemplate = "
<?xml version=\"1.0\" encoding=\"utf-8\"?>
<descriptor>

/* Primary sections */

<version>
    /* Version of the library */
</version>

<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 (*.$LIB_EXT) and/or
       directories with shared libraries, one per line */
</libs>

/* Optional sections */

<include_paths>
    /* The list of include paths that will be provided
       to GCC to compile library headers, one per line.
       NOTE: If you define this section then the tool
       will not automatically generate include paths */
</include_paths>

<add_include_paths>
    /* The list of include paths that will be added
       to the automatically generated include paths, one per line */
</add_include_paths>

<skip_include_paths>
    /* The list of include paths that will be removed from the
       list of automatically generated include paths, one per line */
</skip_include_paths>

<gcc_options>
    /* Additional GCC options, one per line */
</gcc_options>

<include_preamble>
    /* The list of header files that will be
       included before other headers, one per line.
       Examples:
           1) tree.h for libxml2
           2) ft2build.h for freetype2 */
</include_preamble>

<defines>
    /* The list of defines that will be added at the
       headers compiling stage, one per line:
          #define A B
          #define C D */
</defines>

<skip_types>
    /* The list of data types, that
       should not be checked, one per line */
</skip_types>

<skip_symbols>
    /* The list of functions (mangled/symbol names in C++),
       that should not be checked, one per line */
</skip_symbols>

<skip_namespaces>
    /* The list of C++ namespaces, that
       should not be checked, one per line */
</skip_namespaces>

<skip_constants>
    /* The list of constants that should
       not be checked, one name per line */
</skip_constants>

<skip_headers>
    /* The list of header files and/or directories
       with header files that should not be checked, one per line */
</skip_headers>

<skip_libs>
    /* The list of shared libraries and/or directories
       with shared libraries that should not be checked, one per line */
</skip_libs>

<skip_including>
    /* The list of header files, that cannot be included
       directly (or non-self compiled ones), one per line */
</skip_including>

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

</descriptor>";

my %Operator_Indication = (
    "not" => "~",
    "assign" => "=",
    "andassign" => "&=",
    "orassign" => "|=",
    "xorassign" => "^=",
    "or" => "|",
    "xor" => "^",
    "addr" => "&",
    "and" => "&",
    "lnot" => "!",
    "eq" => "==",
    "ne" => "!=",
    "lt" => "<",
    "lshift" => "<<",
    "lshiftassign" => "<<=",
    "rshiftassign" => ">>=",
    "call" => "()",
    "mod" => "%",
    "modassign" => "%=",
    "subs" => "[]",
    "land" => "&&",
    "lor" => "||",
    "rshift" => ">>",
    "ref" => "->",
    "le" => "<=",
    "deref" => "*",
    "mult" => "*",
    "preinc" => "++",
    "delete" => " delete",
    "vecnew" => " new[]",
    "vecdelete" => " delete[]",
    "predec" => "--",
    "postinc" => "++",
    "postdec" => "--",
    "plusassign" => "+=",
    "plus" => "+",
    "minus" => "-",
    "minusassign" => "-=",
    "gt" => ">",
    "ge" => ">=",
    "new" => " new",
    "multassign" => "*=",
    "divassign" => "/=",
    "div" => "/",
    "neg" => "-",
    "pos" => "+",
    "memref" => "->*",
    "compound" => "," );

my %CppKeywords_C = map {$_=>1} (
    # C++ 2003 keywords
    "public",
    "protected",
    "private",
    "default",
    "template",
    "new",
    #"asm",
    "dynamic_cast",
    "auto",
    "try",
    "namespace",
    "typename",
    "using",
    "reinterpret_cast",
    "friend",
    "class",
    "virtual",
    "const_cast",
    "mutable",
    "static_cast",
    "export",
    # C++0x keywords
    "noexcept",
    "nullptr",
    "constexpr",
    "static_assert",
    "explicit",
    # cannot be used as a macro name
    # as it is an operator in C++
    "and",
    #"and_eq",
    "not",
    #"not_eq",
    "or"
    #"or_eq",
    #"bitand",
    #"bitor",
    #"xor",
    #"xor_eq",
    #"compl"
);

my %CppKeywords_F = map {$_=>1} (
    "delete",
    "catch",
    "alignof",
    "thread_local",
    "decltype",
    "typeid"
);

my %CppKeywords_O = map {$_=>1} (
    "bool",
    "register",
    "inline",
    "operator"
);

my %CppKeywords_A = map {$_=>1} (
    "this",
    "throw"
);

foreach (keys(%CppKeywords_C),
keys(%CppKeywords_F),
keys(%CppKeywords_O)) {
    $CppKeywords_A{$_}=1;
}

# Header file extensions as described by gcc
my $HEADER_EXT = "h|hh|hp|hxx|hpp|h\\+\\+";

my %IntrinsicMangling = (
    "void" => "v",
    "bool" => "b",
    "wchar_t" => "w",
    "char" => "c",
    "signed char" => "a",
    "unsigned char" => "h",
    "short" => "s",
    "unsigned short" => "t",
    "int" => "i",
    "unsigned int" => "j",
    "long" => "l",
    "unsigned long" => "m",
    "long long" => "x",
    "__int64" => "x",
    "unsigned long long" => "y",
    "__int128" => "n",
    "unsigned __int128" => "o",
    "float" => "f",
    "double" => "d",
    "long double" => "e",
    "__float80" => "e",
    "__float128" => "g",
    "..." => "z"
);

my %StdcxxMangling = (
    "3std"=>"St",
    "3std9allocator"=>"Sa",
    "3std12basic_string"=>"Sb",
    "3std12basic_stringIcE"=>"Ss",
    "3std13basic_istreamIcE"=>"Si",
    "3std13basic_ostreamIcE"=>"So",
    "3std14basic_iostreamIcE"=>"Sd"
);

my %ConstantSuffix = (
    "unsigned int"=>"u",
    "long"=>"l",
    "unsigned long"=>"ul",
    "long long"=>"ll",
    "unsigned long long"=>"ull"
);

my %ConstantSuffixR =
reverse(%ConstantSuffix);

my %OperatorMangling = (
    "~" => "co",
    "=" => "aS",
    "|" => "or",
    "^" => "eo",
    "&" => "an",#ad (addr)
    "==" => "eq",
    "!" => "nt",
    "!=" => "ne",
    "<" => "lt",
    "<=" => "le",
    "<<" => "ls",
    "<<=" => "lS",
    ">" => "gt",
    ">=" => "ge",
    ">>" => "rs",
    ">>=" => "rS",
    "()" => "cl",
    "%" => "rm",
    "[]" => "ix",
    "&&" => "aa",
    "||" => "oo",
    "*" => "ml",#de (deref)
    "++" => "pp",#
    "--" => "mm",#
    "new" => "nw",
    "delete" => "dl",
    "new[]" => "na",
    "delete[]" => "da",
    "+=" => "pL",
    "+" => "pl",#ps (pos)
    "-" => "mi",#ng (neg)
    "-=" => "mI",
    "*=" => "mL",
    "/=" => "dV",
    "&=" => "aN",
    "|=" => "oR",
    "%=" => "rM",
    "^=" => "eO",
    "/" => "dv",
    "->*" => "pm",
    "->" => "pt",#rf (ref)
    "," => "cm",
    "?" => "qu",
    "." => "dt",
    "sizeof"=> "sz"#st
);

my %GlibcHeader = map {$_=>1} (
    "aliases.h",
    "argp.h",
    "argz.h",
    "assert.h",
    "cpio.h",
    "ctype.h",
    "dirent.h",
    "envz.h",
    "errno.h",
    "error.h",
    "execinfo.h",
    "fcntl.h",
    "fstab.h",
    "ftw.h",
    "glob.h",
    "grp.h",
    "iconv.h",
    "ifaddrs.h",
    "inttypes.h",
    "langinfo.h",
    "limits.h",
    "link.h",
    "locale.h",
    "malloc.h",
    "math.h",
    "mntent.h",
    "monetary.h",
    "nl_types.h",
    "obstack.h",
    "printf.h",
    "pwd.h",
    "regex.h",
    "sched.h",
    "search.h",
    "setjmp.h",
    "shadow.h",
    "signal.h",
    "spawn.h",
    "stdarg.h",
    "stdint.h",
    "stdio.h",
    "stdlib.h",
    "string.h",
    "tar.h",
    "termios.h",
    "time.h",
    "ulimit.h",
    "unistd.h",
    "utime.h",
    "wchar.h",
    "wctype.h",
    "wordexp.h" );

my %GlibcDir = map {$_=>1} (
    "arpa",
    "bits",
    "gnu",
    "netinet",
    "net",
    "nfs",
    "rpc",
    "sys",
    "linux" );

my %LocalIncludes = map {$_=>1} (
    "/usr/local/include",
    "/usr/local" );

my %OS_AddPath=(
# These paths are needed if the tool cannot detect them automatically
    "macos"=>{
        "include"=>{
            "/Library"=>1,
            "/Developer/usr/include"=>1
        },
        "lib"=>{
            "/Library"=>1,
            "/Developer/usr/lib"=>1
        },
        "bin"=>{
            "/Developer/usr/bin"=>1
        }
    },
    "beos"=>{
    # Haiku has GCC 2.95.3 by default
    # try to find GCC>=3.0 in /boot/develop/abi
        "include"=>{
            "/boot/common"=>1,
            "/boot/develop"=>1},
        "lib"=>{
            "/boot/common/lib"=>1,
            "/boot/system/lib"=>1,
            "/boot/apps"=>1},
        "bin"=>{
            "/boot/common/bin"=>1,
            "/boot/system/bin"=>1,
            "/boot/develop/abi"=>1
    }
}
);

my %Slash_Type=(
    "default"=>"/",
    "windows"=>"\\"
);

my $SLASH = $Slash_Type{$OSgroup}?$Slash_Type{$OSgroup}:$Slash_Type{"default"};

# Global Variables
my %COMMON_LANGUAGE=(
  1 => "C",
  2 => "C" );

my $MAX_COMMAND_LINE_ARGUMENTS = 4096;
my (%WORD_SIZE, %CPU_ARCH, %GCC_VERSION);

my %LIB_ARCH;

my $STDCXX_TESTING = 0;
my $GLIBC_TESTING = 0;

my $CheckHeadersOnly = $CheckHeadersOnly_Opt;
my $CheckObjectsOnly = $CheckObjectsOnly_Opt;
my $TargetComponent = lc($TargetComponent_Opt);
if(not $TargetComponent)
{ # default: library
  # other components: header, system, ...
    $TargetComponent = "library";
}

my $SystemRoot;

my $MAIN_CPP_DIR;
my %RESULT=(
    "Problems"=>0,
    "Warnings"=>0,
    "Affected"=>0
);
my %LOG_PATH;
my %DEBUG_PATH;
my %Cache;
my %LibInfo;
my $COMPILE_ERRORS = 0;
my %CompilerOptions;
my %CheckedDyLib;
my $TargetLibraryShortName = parse_libname($TargetLibraryName, "shortest", $OSgroup);

# Constants (#defines)
my %Constants;
my %SkipConstants;

# Types
my %TypeInfo;
my %TemplateInstance_Func;
my %TemplateInstance;
my %SkipTypes = (
  "1"=>{},
  "2"=>{} );
my %Tid_TDid = (
  "1"=>{},
  "2"=>{} );
my %CheckedTypes;
my %TName_Tid;
my %EnumMembName_Id;
my %NestedNameSpaces = (
  "1"=>{},
  "2"=>{} );
my %UsedType;
my %VirtualTable;
my %VirtualTable_Full;
my %ClassVTable;
my %ClassVTable_Content;
my %VTableClass;
my %AllocableClass;
my %ClassMethods;
my %ClassToId;
my %Class_SubClasses;
my %OverriddenMethods;
my $MAX_TID;

# Typedefs
my %Typedef_BaseName;
my %Typedef_Tr;
my %Typedef_Eq;
my %StdCxxTypedef;
my %MissedTypedef;

# Symbols
my %SymbolInfo;
my %tr_name;
my %mangled_name_gcc;
my %mangled_name;
my %SkipSymbols = (
  "1"=>{},
  "2"=>{} );
my %SkipNameSpaces = (
  "1"=>{},
  "2"=>{} );
my %SymbolsList;
my %SymbolsList_App;
my %CheckedSymbols;
my %GeneratedSymbols;
my %DepSymbols = (
  "1"=>{},
  "2"=>{} );
my %MangledNames;
my %AddIntParams;
my %Interface_Impl;

# Headers
my %Include_Preamble;
my %Registered_Headers;
my %HeaderName_Paths;
my %Header_Dependency;
my %Include_Neighbors;
my %Include_Paths;
my %INC_PATH_AUTODETECT = (
  "1"=>1,
  "2"=>1 );
my %Add_Include_Paths;
my %Skip_Include_Paths;
my %RegisteredDirs;
my %RegisteredDeps;
my %Header_ErrorRedirect;
my %Header_Includes;
my %Header_ShouldNotBeUsed;
my %RecursiveIncludes;
my %Header_Include_Prefix;
my %SkipHeaders;
my %SkipHeadersList=(
  "1"=>{},
  "2"=>{} );
my %SkipLibs;
my %Include_Order;
my %TUnit_NameSpaces;

my %C99Mode = (
  "1"=>0,
  "2"=>0 );
my %AutoPreambleMode = (
  "1"=>0,
  "2"=>0 );
my %MinGWMode = (
  "1"=>0,
  "2"=>0 );

# Shared Objects
my %DyLib_DefaultPath;
my %InputObject_Paths;
my %RegisteredObjDirs;

# System Objects
my %SystemObjects;
my %DefaultLibPaths;

# System Headers
my %SystemHeaders;
my %DefaultCppPaths;
my %DefaultGccPaths;
my %DefaultIncPaths;
my %DefaultCppHeader;
my %DefaultGccHeader;
my %UserIncPath;

# Merging
my %CompleteSignature;
my %Symbol_Library;
my %Library_Symbol = (
  "1"=>{},
  "2"=>{} );
my $Version;
my %AddedInt;
my %RemovedInt;
my %AddedInt_Virt;
my %RemovedInt_Virt;
my %VirtualReplacement;
my %ChangedTypedef;
my %CompatRules;
my %IncompleteRules;
my %UnknownRules;
my %VTableChanged;
my %ExtendedFuncs;
my %ReturnedClass;
my %ParamClass;

# OS Compliance
my %TargetLibs;
my %TargetHeaders;

# OS Specifics
my $OStarget = $OSgroup;
my %TargetTools;

# Compliance Report
my %Type_MaxPriority;

# Recursion locks
my @RecurLib;
my @RecurSymlink;
my @RecurTypes;
my @RecurInclude;
my @RecurConstant;

# System
my %SystemPaths;
my %DefaultBinPaths;
my $GCC_PATH;

# Symbols versioning
my %SymVer = (
  "1"=>{},
  "2"=>{} );

# Problem descriptions
my %CompatProblems;
my %ProblemsWithConstants;
my %ImplProblems;
my %TotalAffected;

# Rerorts
my $ContentID = 1;
my $ContentSpanStart = "<span class=\"section\" onclick=\"javascript:showContent(this, 'CONTENT_ID')\">\n";
my $ContentSpanStart_Affected = "<span class=\"section_affected\" onclick=\"javascript:showContent(this, 'CONTENT_ID')\">\n";
my $ContentSpanStart_Info = "<span class=\"section_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 $JScripts = "
<script type=\"text/javascript\" language=\"JavaScript\">
<!--
function showContent(header, id)   {
    e = document.getElementById(id);
    if(e.style.display == 'none')
    {
        e.style.display = 'block';
        e.style.visibility = 'visible';
        header.innerHTML = header.innerHTML.replace(/\\\[[^0-9 ]\\\]/gi,\"[&minus;]\");
    }
    else
    {
        e.style.display = 'none';
        e.style.visibility = 'hidden';
        header.innerHTML = header.innerHTML.replace(/\\\[[^0-9 ]\\\]/gi,\"[+]\");
    }
}
-->
</script>";

sub get_Modules()
{
    my $TOOL_DIR = get_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",
        # system directory
        "../share/abi-compliance-checker"
    );
    foreach my $DIR (@SEARCH_DIRS)
    {
        if(not is_abs($DIR))
        { # relative path
            $DIR = abs_path($TOOL_DIR)."/".$DIR;
        }
        if(-d $DIR."/modules") {
            return $DIR."/modules";
        }
    }
    exitStatus("Module_Error", "can't find modules");
}

sub loadModule($)
{
    my $Name = $_[0];
    my $Path = $MODULES_DIR."/Internals/$Name.pm";
    if(not -f $Path) {
        exitStatus("Module_Error", "can't access \'$Path\'");
    }
    require $Path;
}

sub numToStr($)
{
    my $Number = int($_[0]);
    if($Number>3) {
        return $Number."th";
    }
    elsif($Number==1) {
        return "1st";
    }
    elsif($Number==2) {
        return "2nd";
    }
    elsif($Number==3) {
        return "3rd";
    }
    else {
        return $Number;
    }
}

sub search_Tools($)
{
    my $Name = $_[0];
    return "" if(not $Name);
    if(my @Paths = keys(%TargetTools))
    {
        foreach my $Path (@Paths)
        {
            if(-f joinPath($Path, $Name)) {
                return joinPath($Path, $Name);
            }
            if($CrossPrefix)
            { # user-defined prefix (arm-none-symbianelf, ...)
                my $Candidate = joinPath($Path, $CrossPrefix."-".$Name);
                if(-f $Candidate) {
                    return $Candidate;
                }
            }
        }
    }
    else {
        return "";
    }
}

sub synch_Cmd($)
{
    my $Name = $_[0];
    if(not $GCC_PATH)
    { # GCC was not found yet
        return "";
    }
    my $Candidate = $GCC_PATH;
    if($Candidate=~s/(\W|\A)gcc(|\.\w+)\Z/$1$Name$2/) {
        return $Candidate;
    }
    return "";
}

sub get_CmdPath($)
{
    my $Name = $_[0];
    return "" if(not $Name);
    if(defined $Cache{"get_CmdPath"}{$Name}) {
        return $Cache{"get_CmdPath"}{$Name};
    }
    my %BinUtils = map {$_=>1} (
        "c++filt",
        "objdump",
        "readelf"
    );
    if($BinUtils{$Name}) {
        if(my $Dir = get_dirname($GCC_PATH)) {
            $TargetTools{$Dir}=1;
        }
    }
    my $Path = search_Tools($Name);
    if(not $Path and $OSgroup eq "windows") {
        $Path = search_Tools($Name.".exe");
    }
    if(not $Path and $BinUtils{$Name})
    {
        if($CrossPrefix)
        { # user-defined prefix
            $Path = search_Cmd($CrossPrefix."-".$Name);
        }
    }
    if(not $Path and $BinUtils{$Name})
    {
        if(my $Candidate = synch_Cmd($Name))
        { # synch with GCC
            if($Candidate=~/[\/\\]/)
            {# command path
                if(-f $Candidate) {
                    $Path = $Candidate;
                }
            }
            elsif($Candidate = search_Cmd($Candidate))
            {# command name
                $Path = $Candidate;
            }
        }
    }
    if(not $Path) {
        $Path = search_Cmd($Name);
    }
    if(not $Path and $OSgroup eq "windows")
    {# search for *.exe file
        $Path=search_Cmd($Name.".exe");
    }
    if($Path=~/\s/) {
        $Path = "\"".$Path."\"";
    }
    return ($Cache{"get_CmdPath"}{$Name}=$Path);
}

sub search_Cmd($)
{
    my $Name = $_[0];
    return "" if(not $Name);
    if(defined $Cache{"search_Cmd"}{$Name}) {
        return $Cache{"search_Cmd"}{$Name};
    }
    if(my $DefaultPath = get_CmdPath_Default($Name)) {
        return ($Cache{"search_Cmd"}{$Name} = $DefaultPath);
    }
    foreach my $Path (sort {length($a)<=>length($b)} keys(%{$SystemPaths{"bin"}}))
    {
        my $CmdPath = joinPath($Path,$Name);
        if(-f $CmdPath)
        {
            if($Name=~/gcc/) {
                next if(not check_gcc_version($CmdPath, "3"));
            }
            return ($Cache{"search_Cmd"}{$Name} = $CmdPath);
        }
    }
    return ($Cache{"search_Cmd"}{$Name} = "");
}

sub get_CmdPath_Default($)
{ # search in PATH
    my $Name = $_[0];
    return "" if(not $Name);
    if(defined $Cache{"get_CmdPath_Default"}{$Name}) {
        return $Cache{"get_CmdPath_Default"}{$Name};
    }
    if($Name=~/find/)
    { # special case: search for "find" utility
        if(`find . -maxdepth 0 2>$TMP_DIR/null`) {
            return ($Cache{"get_CmdPath_Default"}{$Name} = "find");
        }
    }
    elsif($Name=~/gcc/) {
        return check_gcc_version($Name, "3");
    }
    if(check_command($Name)) {
        return ($Cache{"get_CmdPath_Default"}{$Name} = $Name);
    }
    if($OSgroup eq "windows"
    and `$Name /? 2>$TMP_DIR/null`) {
        return ($Cache{"get_CmdPath_Default"}{$Name} = $Name);
    }
    if($Name!~/which/)
    {
        my $WhichCmd = get_CmdPath("which");
        if($WhichCmd and `$WhichCmd $Name 2>$TMP_DIR/null`) {
            return ($Cache{"get_CmdPath_Default"}{$Name} = $Name);
        }
    }
    foreach my $Path (sort {length($a)<=>length($b)} keys(%DefaultBinPaths))
    {
        if(-f $Path."/".$Name) {
            return ($Cache{"get_CmdPath_Default"}{$Name} = joinPath($Path,$Name));
        }
    }
    return ($Cache{"get_CmdPath_Default"}{$Name} = "");
}

sub clean_path($)
{
    my $Path = $_[0];
    $Path=~s/[\/\\]+\Z//g;
    return $Path;
}

sub classifyPath($)
{
    my $Path = $_[0];
    if($Path=~/[\*\[]/)
    { # wildcard
        $Path=~s/\*/.*/g;
        $Path=~s/\\/\\\\/g;
        return ($Path, "Pattern");
    }
    elsif($Path=~/[\/\\]/)
    { # directory or relative path
        return (path_format($Path, $OSgroup), "Path");
    }
    else {
        return ($Path, "Name");
    }
}

sub readDescriptor($$)
{
    my ($LibVersion, $Content) = @_;
    return if(not $LibVersion);
    my $DName = $DumpAPI?"descriptor":"descriptor \"d$LibVersion\"";
    if(not $Content) {
        exitStatus("Error", "$DName is empty");
    }
    if($Content!~/\</) {
        exitStatus("Error", "$DName is not a descriptor (see -d1 option)");
    }
    $Content=~s/\/\*(.|\n)+?\*\///g;
    $Content=~s/<\!--(.|\n)+?-->//g;
    $Descriptor{$LibVersion}{"Version"} = parseTag(\$Content, "version");
    if($TargetVersion{$LibVersion}) {
        $Descriptor{$LibVersion}{"Version"} = $TargetVersion{$LibVersion};
    }
    if(not $Descriptor{$LibVersion}{"Version"}) {
        exitStatus("Error", "version in the $DName is not specified (<version> section)");
    }
    if($Content=~/{RELPATH}/)
    {
        if(my $RelDir = $RelativeDirectory{$LibVersion}) {
            $Content =~ s/{RELPATH}/$RelDir/g;
        }
        else
        {
            my $NeedRelpath = $DumpAPI?"-relpath":"-relpath$LibVersion";
            exitStatus("Error", "you have not specified $NeedRelpath option, but the $DName contains {RELPATH} macro");
        }
    }
    
    if(not $CheckObjectsOnly_Opt)
    {
        my $DHeaders = parseTag(\$Content, "headers");
        if(not $DHeaders) {
            exitStatus("Error", "header files in the $DName are not specified (<headers> section)");
        }
        elsif(lc($DHeaders) ne "none")
        { # append the descriptor headers list
            if($Descriptor{$LibVersion}{"Headers"})
            { # multiple descriptors
                $Descriptor{$LibVersion}{"Headers"} .= "\n".$DHeaders;
            }
            else {
                $Descriptor{$LibVersion}{"Headers"} = $DHeaders;
            }
            foreach my $Path (split(/\s*\n\s*/, $DHeaders))
            {
                if(not -e $Path) {
                    exitStatus("Access_Error", "can't access \'$Path\'");
                }
            }
        }
    }
    if(not $CheckHeadersOnly_Opt)
    {
        my $DObjects = parseTag(\$Content, "libs");
        if(not $DObjects) {
            exitStatus("Error", "$SLIB_TYPE libraries in the $DName are not specified (<libs> section)");
        }
        elsif(lc($DObjects) ne "none")
        { # append the descriptor libraries list
            if($Descriptor{$LibVersion}{"Libs"})
            { # multiple descriptors
                $Descriptor{$LibVersion}{"Libs"} .= "\n".$DObjects;
            }
            else {
                $Descriptor{$LibVersion}{"Libs"} .= $DObjects;
            }
            foreach my $Path (split(/\s*\n\s*/, $DObjects))
            {
                if(not -e $Path) {
                    exitStatus("Access_Error", "can't access \'$Path\'");
                }
            }
        }
    }
    foreach my $Path (split(/\s*\n\s*/, parseTag(\$Content, "search_headers")))
    {
        $Path = clean_path($Path);
        if(not -d $Path) {
            exitStatus("Access_Error", "can't access directory \'$Path\'");
        }
        $Path = path_format($Path, $OSgroup);
        $SystemPaths{"include"}{$Path}=1;
    }
    foreach my $Path (split(/\s*\n\s*/, parseTag(\$Content, "search_libs")))
    {
        $Path = clean_path($Path);
        if(not -d $Path) {
            exitStatus("Access_Error", "can't access directory \'$Path\'");
        }
        $Path = path_format($Path, $OSgroup);
        $SystemPaths{"lib"}{$Path}=1;
    }
    foreach my $Path (split(/\s*\n\s*/, parseTag(\$Content, "tools")))
    {
        $Path=clean_path($Path);
        if(not -d $Path) {
            exitStatus("Access_Error", "can't access directory \'$Path\'");
        }
        $Path = path_format($Path, $OSgroup);
        $SystemPaths{"bin"}{$Path}=1;
        $TargetTools{$Path}=1;
    }
    if(my $Prefix = parseTag(\$Content, "cross_prefix")) {
        $CrossPrefix = $Prefix;
    }
    foreach my $Path (split(/\s*\n\s*/, parseTag(\$Content, "include_paths")))
    {
        $Path=clean_path($Path);
        if(not -d $Path) {
            exitStatus("Access_Error", "can't access directory \'$Path\'");
        }
        $Path = path_format($Path, $OSgroup);
        $Descriptor{$LibVersion}{"IncludePaths"}{$Path} = 1;
    }
    foreach my $Path (split(/\s*\n\s*/, parseTag(\$Content, "add_include_paths")))
    {
        $Path=clean_path($Path);
        if(not -d $Path) {
            exitStatus("Access_Error", "can't access directory \'$Path\'");
        }
        $Path = path_format($Path, $OSgroup);
        $Descriptor{$LibVersion}{"AddIncludePaths"}{$Path} = 1;
    }
    foreach my $Path (split(/\s*\n\s*/, parseTag(\$Content, "skip_include_paths")))
    {
        # skip some auto-generated include paths
        $Skip_Include_Paths{$LibVersion}{path_format($Path)}=1;
    }
    foreach my $Path (split(/\s*\n\s*/, parseTag(\$Content, "skip_including")))
    {
        # skip direct including of some headers
        my ($CPath, $Type) = classifyPath($Path);
        $SkipHeaders{$LibVersion}{$Type}{$CPath} = 2;
        $SkipHeadersList{$LibVersion}{$Path} = 2;
    }
    $Descriptor{$LibVersion}{"GccOptions"} = parseTag(\$Content, "gcc_options");
    foreach my $Option (split(/\s*\n\s*/, $Descriptor{$LibVersion}{"GccOptions"})) {
        $CompilerOptions{$LibVersion} .= " ".$Option;
    }
    $Descriptor{$LibVersion}{"SkipHeaders"} = parseTag(\$Content, "skip_headers");
    foreach my $Path (split(/\s*\n\s*/, $Descriptor{$LibVersion}{"SkipHeaders"}))
    {
        my ($CPath, $Type) = classifyPath($Path);
        $SkipHeaders{$LibVersion}{$Type}{$CPath} = 1;
        $SkipHeadersList{$LibVersion}{$Path} = 1;
    }
    $Descriptor{$LibVersion}{"SkipLibs"} = parseTag(\$Content, "skip_libs");
    foreach my $Path (split(/\s*\n\s*/, $Descriptor{$LibVersion}{"SkipLibs"}))
    {
        my ($CPath, $Type) = classifyPath($Path);
        $SkipLibs{$LibVersion}{$Type}{$CPath} = 1;
    }
    if(my $DDefines = parseTag(\$Content, "defines"))
    {
        if($Descriptor{$LibVersion}{"Defines"})
        { # multiple descriptors
            $Descriptor{$LibVersion}{"Defines"} .= "\n".$DDefines;
        }
        else {
            $Descriptor{$LibVersion}{"Defines"} = $DDefines;
        }
    }
    foreach my $Order (split(/\s*\n\s*/, parseTag(\$Content, "include_order")))
    {
        if($Order=~/\A(.+):(.+)\Z/) {
            $Include_Order{$LibVersion}{$1} = $2;
        }
    }
    foreach my $Type_Name (split(/\s*\n\s*/, parseTag(\$Content, "opaque_types")),
    split(/\s*\n\s*/, parseTag(\$Content, "skip_types")))
    {# opaque_types renamed to skip_types (1.23.4)
        $SkipTypes{$LibVersion}{$Type_Name} = 1;
    }
    foreach my $Symbol (split(/\s*\n\s*/, parseTag(\$Content, "skip_interfaces")),
    split(/\s*\n\s*/, parseTag(\$Content, "skip_symbols")))
    {# skip_interfaces renamed to skip_symbols (1.22.1)
        $SkipSymbols{$LibVersion}{$Symbol} = 1;
    }
    foreach my $NameSpace (split(/\s*\n\s*/, parseTag(\$Content, "skip_namespaces"))) {
        $SkipNameSpaces{$LibVersion}{$NameSpace} = 1;
    }
    foreach my $Constant (split(/\s*\n\s*/, parseTag(\$Content, "skip_constants"))) {
        $SkipConstants{$LibVersion}{$Constant} = 1;
    }
    if(my $DIncPreamble = parseTag(\$Content, "include_preamble"))
    {
        if($Descriptor{$LibVersion}{"IncludePreamble"})
        {# multiple descriptors
            $Descriptor{$LibVersion}{"IncludePreamble"} .= "\n".$DIncPreamble;
        }
        else {
            $Descriptor{$LibVersion}{"IncludePreamble"} = $DIncPreamble;
        }
    }
}

sub parseTag($$)
{
    my ($CodeRef, $Tag) = @_;
    return "" if(not $CodeRef or not ${$CodeRef} or not $Tag);
    if(${$CodeRef}=~s/\<\Q$Tag\E\>((.|\n)+?)\<\/\Q$Tag\E\>//)
    {
        my $Content = $1;
        $Content=~s/(\A\s+|\s+\Z)//g;
        return $Content;
    }
    else {
        return "";
    }
}

my %check_node= map {$_=>1} (
  "array_type",
  "binfo",
  "boolean_type",
  "complex_type",
  "const_decl",
  "enumeral_type",
  "field_decl",
  "function_decl",
  "function_type",
  "identifier_node",
  "integer_cst",
  "integer_type",
  "method_type",
  "namespace_decl",
  "parm_decl",
  "pointer_type",
  "real_cst",
  "real_type",
  "record_type",
  "reference_type",
  "string_cst",
  "template_decl",
  "template_type_parm",
  "tree_list",
  "tree_vec",
  "type_decl",
  "union_type",
  "var_decl",
  "void_type",
  "offset_type" );

sub getInfo($)
{
    my $InfoPath = $_[0];
    return if(not $InfoPath or not -f $InfoPath);
    my $Content = readFile($InfoPath);
    unlink($InfoPath);
    $Content=~s/\n[ ]+/ /g;
    my @Lines = split("\n", $Content);
    $Content="";# clear
    foreach (@Lines)
    {
        if(/\A\@(\d+)\s+([a-z_]+)\s+(.+)\Z/oi)
        { # get a number and attributes of a node
            next if(not $check_node{$2});
            $LibInfo{$Version}{"info_type"}{$1}=$2;
            $LibInfo{$Version}{"info"}{$1}=$3;
        }
    }
    $MAX_TID = $#Lines+1;
    @Lines=();# clear
    # processing info
    setTemplateParams_All();
    getTypeInfo_All();
    simplifyNames();
    getSymbolInfo_All();
    getVarInfo_All();
    
    # cleaning memory
    %LibInfo = ();
    %TemplateInstance = ();
    %TemplateInstance_Func = ();
    %MangledNames = ();

    if($Debug) {
        # debugMangling($Version);
    }
}

sub simplifyNames()
{
    foreach my $Base (keys(%{$Typedef_Tr{$Version}}))
    {
        my @Translations = keys(%{$Typedef_Tr{$Version}{$Base}});
        if($#Translations==0 and length($Translations[0])<=length($Base)) {
            $Typedef_Eq{$Version}{$Base} = $Translations[0];
        }
    }
    foreach my $TDid (keys(%{$TypeInfo{$Version}}))
    {
        foreach my $Tid (keys(%{$TypeInfo{$Version}{$TDid}}))
        {
            my $TypeName = $TypeInfo{$Version}{$TDid}{$Tid}{"Name"};
            if(not $TypeName) {
                next;
            }
            next if(index($TypeName,"<")==-1);# template instances only
            if($TypeName=~/>(::\w+)+\Z/)
            { # skip unused types
                next;
            };
            foreach my $Base (sort {length($b)<=>length($a)}
            sort {$b cmp $a} keys(%{$Typedef_Eq{$Version}}))
            {
                next if(not $Base);
                next if(index($TypeName,$Base)==-1);
                next if(length($TypeName) - length($Base) <= 3);
                my $Typedef = $Typedef_Eq{$Version}{$Base};
                $TypeName=~s/(\<|\,)\Q$Base\E(\W|\Z)/$1$Typedef$2/g;
                $TypeName=~s/(\<|\,)\Q$Base\E(\w|\Z)/$1$Typedef $2/g;
            }
            if($TypeName ne $TypeInfo{$Version}{$TDid}{$Tid}{"Name"})
            {
                $TypeInfo{$Version}{$TDid}{$Tid}{"Name"} = formatName($TypeName);
                $TName_Tid{$Version}{$TypeName} = $Tid;
            }
        }
    }
}

sub setTemplateParams_All()
{
    foreach (keys(%{$LibInfo{$Version}{"info"}}))
    {
        if($LibInfo{$Version}{"info_type"}{$_} eq "template_decl") {
            setTemplateParams($_);
        }
    }
}

sub setTemplateParams($)
{
    my $TypeInfoId = $_[0];
    if($LibInfo{$Version}{"info"}{$TypeInfoId}=~/(inst|spcs)[ ]*:[ ]*@(\d+) /)
    {
        my $TmplInst_InfoId = $2;
        setTemplateInstParams($TmplInst_InfoId);
        my $TmplInst_Info = $LibInfo{$Version}{"info"}{$TmplInst_InfoId};
        while($TmplInst_Info=~/(chan|chain)[ ]*:[ ]*@(\d+) /)
        {
            $TmplInst_InfoId = $2;
            $TmplInst_Info = $LibInfo{$Version}{"info"}{$TmplInst_InfoId};
            setTemplateInstParams($TmplInst_InfoId);
        }
    }
}

sub setTemplateInstParams($)
{
    my $TmplInst_Id = $_[0];
    my $Info = $LibInfo{$Version}{"info"}{$TmplInst_Id};
    my ($Params_InfoId, $ElemId) = ();
    if($Info=~/purp[ ]*:[ ]*@(\d+) /) {
        $Params_InfoId = $1;
    }
    if($Info=~/valu[ ]*:[ ]*@(\d+) /) {
        $ElemId = $1;
    }
    if($Params_InfoId and $ElemId)
    {
        my $Params_Info = $LibInfo{$Version}{"info"}{$Params_InfoId};
        while($Params_Info=~s/ (\d+)[ ]*:[ ]*\@(\d+) / /)
        {
            my ($Param_Pos, $Param_TypeId) = ($1, $2);
            return if($LibInfo{$Version}{"info_type"}{$Param_TypeId} eq "template_type_parm");
            if($LibInfo{$Version}{"info_type"}{$ElemId} eq "function_decl") {
                $TemplateInstance_Func{$Version}{$ElemId}{$Param_Pos} = $Param_TypeId;
            }
            else {
                $TemplateInstance{$Version}{getTypeDeclId($ElemId)}{$ElemId}{$Param_Pos} = $Param_TypeId;
            }
        }
    }
}

sub getTypeDeclId($)
{
    if($LibInfo{$Version}{"info"}{$_[0]}=~/name[ ]*:[ ]*@(\d+)/) {
        return $1;
    }
    else {
        return "";
    }
}

sub isFuncPtr($)
{
    my $Ptd = pointTo($_[0]);
    return 0 if(not $Ptd);
    if($LibInfo{$Version}{"info"}{$_[0]}=~/unql[ ]*:/
    and $LibInfo{$Version}{"info"}{$_[0]}!~/qual[ ]*:/) {
        return 0;
    }
    elsif($LibInfo{$Version}{"info_type"}{$_[0]} eq "pointer_type"
    and $LibInfo{$Version}{"info_type"}{$Ptd} eq "function_type") {
        return 1;
    }
    return 0;
}

sub isMethodPtr($)
{
    my $Ptd = pointTo($_[0]);
    return 0 if(not $Ptd);
    if($LibInfo{$Version}{"info_type"}{$_[0]} eq "record_type"
    and $LibInfo{$Version}{"info_type"}{$Ptd} eq "method_type"
    and $LibInfo{$Version}{"info"}{$_[0]}=~/ ptrmem /) {
        return 1;
    }
    return 0;
}

sub isFieldPtr($)
{
    if($LibInfo{$Version}{"info_type"}{$_[0]} eq "offset_type"
    and $LibInfo{$Version}{"info"}{$_[0]}=~/ ptrmem /) {
        return 1;
    }
    return 0;
}

sub pointTo($)
{
    if($LibInfo{$Version}{"info"}{$_[0]}=~/ptd[ ]*:[ ]*@(\d+)/) {
        return $1;
    }
    else {
        return "";
    }
}

sub getTypeInfo_All()
{
    if(not check_gcc_version($GCC_PATH, "4.5"))
    { # support for GCC < 4.5
      # missed typedefs: QStyle::State is typedef to QFlags<QStyle::StateFlag>
      # but QStyleOption.state is of type QFlags<QStyle::StateFlag> in the TU dump
      # FIXME: check GCC versions
        addMissedTypes_Pre();
    }
    foreach (sort {int($a)<=>int($b)} keys(%{$LibInfo{$Version}{"info"}}))
    {
        my $IType = $LibInfo{$Version}{"info_type"}{$_};
        if($IType=~/_type\Z/ and $IType ne "function_type"
        and $IType ne "method_type") {
            getTypeInfo(getTypeDeclId("$_"), "$_");
        }
    }
    $TypeInfo{$Version}{""}{-1}{"Name"} = "...";
    $TypeInfo{$Version}{""}{-1}{"Type"} = "Intrinsic";
    $TypeInfo{$Version}{""}{-1}{"Tid"} = -1;
    if(not check_gcc_version($GCC_PATH, "4.5"))
    { # support for GCC < 4.5
        addMissedTypes_Post();
    }
}

sub addMissedTypes_Pre()
{
    foreach my $MissedTDid (sort {int($a)<=>int($b)} keys(%{$LibInfo{$Version}{"info"}}))
    { # detecting missed typedefs
        if($LibInfo{$Version}{"info_type"}{$MissedTDid} eq "type_decl")
        {
            my $TypeId = getTreeAttr($MissedTDid, "type");
            next if(not $TypeId);
            my $TypeType = getTypeType($MissedTDid, $TypeId);
            if($TypeType eq "Unknown")
            { # template_type_parm
                next;
            }
            my $TypeDeclId = getTypeDeclId($TypeId);
            next if($TypeDeclId eq $MissedTDid);#or not $TypeDeclId
            my $TypedefName = getNameByInfo($MissedTDid);
            next if(not $TypedefName);
            next if($TypedefName eq "__float80");
            next if(isAnon($TypedefName));
            if(not $TypeDeclId
            or getNameByInfo($TypeDeclId) ne $TypedefName) {
                $MissedTypedef{$Version}{$TypeId}{"$MissedTDid"} = 1;
            }
        }
    }
    foreach my $Tid (keys(%{$MissedTypedef{$Version}}))
    { # add missed typedefs
        my @Missed = keys(%{$MissedTypedef{$Version}{$Tid}});
        if(not @Missed or $#Missed>=1) {
            delete($MissedTypedef{$Version}{$Tid});
            next;
        }
        my $MissedTDid = $Missed[0];
        my $TDid = getTypeDeclId($Tid);
        my ($TypedefName, $TypedefNS) = getTrivialName($MissedTDid, $Tid);
        my %MissedInfo = ( # typedef info
            "Name" => $TypedefName,
            "NameSpace" => $TypedefNS,
            "BaseType" => {
                            "TDid" => $TDid,
                            "Tid" => $Tid
                          },
            "Type" => "Typedef",
            "Tid" => ++$MAX_TID,
            "TDid" => $MissedTDid );
        my ($H, $L) = getLocation($MissedTDid);
        $MissedInfo{"Header"} = $H;
        $MissedInfo{"Line"} = $L;
        # $MissedInfo{"Size"} = getSize($Tid)/$BYTE_SIZE;
        my $MName = $MissedInfo{"Name"};
        next if(not $MName);
        if($MName=~/\*|\&|\s/)
        { # other types
            next;
        }
        if($MName=~/>(::\w+)+\Z/)
        { # QFlags<Qt::DropAction>::enum_type
            delete($MissedTypedef{$Version}{$Tid});
            next;
        }
        if(getTypeType($TDid, $Tid)=~/\A(Intrinsic|Union|Struct|Enum|Class)\Z/)
        { # double-check for the name of typedef
            my ($TName, $TNS) = getTrivialName($TDid, $Tid); # base type info
            next if(not $TName);
            if(length($MName)>=length($TName))
            { # too long typedef
                delete($MissedTypedef{$Version}{$Tid});
                next;
            }
            if($TName=~/\A\Q$MName\E</) {
                next;
            }
            if($MName=~/\A\Q$TName\E/)
            { # QDateTimeEdit::Section and QDateTimeEdit::Sections::enum_type
                delete($MissedTypedef{$Version}{$Tid});
                next;
            }
            if(get_depth($MName)==0 and get_depth($TName)!=0)
            { # std::_Vector_base and std::vector::_Base
                delete($MissedTypedef{$Version}{$Tid});
                next;
            }
        }
        %{$TypeInfo{$Version}{$MissedTDid}{$MissedInfo{"Tid"}}} = %MissedInfo;
        $Tid_TDid{$Version}{$MissedInfo{"Tid"}} = $MissedTDid;
        delete($TypeInfo{$Version}{$MissedTDid}{$Tid});
        # register typedef
        $MissedTypedef{$Version}{$Tid}{"TDid"} = $MissedTDid;
        $MissedTypedef{$Version}{$Tid}{"Tid"} = $MissedInfo{"Tid"};
    }
}

sub addMissedTypes_Post()
{
    foreach my $BaseId (keys(%{$MissedTypedef{$Version}}))
    {
        my $Tid = $MissedTypedef{$Version}{$BaseId}{"Tid"};
        my $TDid = $MissedTypedef{$Version}{$BaseId}{"TDid"};
        $TypeInfo{$Version}{$TDid}{$Tid}{"Size"} = get_TypeAttr($BaseId, $Version, "Size");
    }
}

sub getTypeInfo($$)
{
    my ($TDId, $TId) = @_;
    %{$TypeInfo{$Version}{$TDId}{$TId}} = getTypeAttr($TDId, $TId);
    my $TName = $TypeInfo{$Version}{$TDId}{$TId}{"Name"};
    if(not $TName) {
        delete($TypeInfo{$Version}{$TDId}{$TId});
        return;
    }
    if($TDId) {
        $Tid_TDid{$Version}{$TId} = $TDId;
    }
    if(not $TName_Tid{$Version}{$TName}) {
        $TName_Tid{$Version}{$TName} = $TId;
    }
}

sub getArraySize($$)
{
    my ($TypeId, $BaseName) = @_;
    my $SizeBytes = getSize($TypeId)/$BYTE_SIZE;
    while($BaseName=~s/\s*\[(\d+)\]//) {
        $SizeBytes/=$1;
    }
    my $BasicId = $TName_Tid{$Version}{$BaseName};
    if(my $BasicSize = $TypeInfo{$Version}{getTypeDeclId($BasicId)}{$BasicId}{"Size"}) {
        $SizeBytes/=$BasicSize;
    }
    return $SizeBytes;
}

sub getTParams_Func($)
{
    my $FuncInfoId = $_[0];
    my @TmplParams = ();
    foreach my $Pos (sort {int($a) <=> int($b)} keys(%{$TemplateInstance_Func{$Version}{$FuncInfoId}}))
    {
        my $Param = get_TemplateParam($Pos, $TemplateInstance_Func{$Version}{$FuncInfoId}{$Pos});
        if($Param eq "") {
            return ();
        }
        elsif($Param ne "\@skip\@") {
            push(@TmplParams, $Param);
        }
    }
    return @TmplParams;
}

sub getTParams($$)
{
    my ($TypeDeclId, $TypeId) = @_;
    my @Template_Params = ();
    foreach my $Pos (sort {int($a)<=>int($b)} keys(%{$TemplateInstance{$Version}{$TypeDeclId}{$TypeId}}))
    {
        my $Param_TypeId = $TemplateInstance{$Version}{$TypeDeclId}{$TypeId}{$Pos};
        my $Param = get_TemplateParam($Pos, $Param_TypeId);
        if($Param eq "") {
            return ();
        }
        elsif($Param ne "\@skip\@") {
            @Template_Params = (@Template_Params, $Param);
        }
    }
    return @Template_Params;
}

sub getTypeAttr($$)
{
    my ($TypeDeclId, $TypeId) = @_;
    my ($BaseTypeSpec, %TypeAttr) = ();
    if(defined $TypeInfo{$Version}{$TypeDeclId}{$TypeId}
    and $TypeInfo{$Version}{$TypeDeclId}{$TypeId}{"Name"}) {
        return %{$TypeInfo{$Version}{$TypeDeclId}{$TypeId}};
    }
    $TypeAttr{"Tid"} = $TypeId;
    $TypeAttr{"TDid"} = $TypeDeclId;
    $TypeAttr{"Type"} = getTypeType($TypeDeclId, $TypeId);
    if($TypeAttr{"Type"} eq "Unknown") {
        return ();
    }
    elsif($TypeAttr{"Type"}=~/(Func|Method|Field)Ptr/)
    {
        %{$TypeInfo{$Version}{$TypeDeclId}{$TypeId}} = getMemPtrAttr(pointTo($TypeId), $TypeDeclId, $TypeId, $TypeAttr{"Type"});
        $TName_Tid{$Version}{$TypeInfo{$Version}{$TypeDeclId}{$TypeId}{"Name"}} = $TypeId;
        return %{$TypeInfo{$Version}{$TypeDeclId}{$TypeId}};
    }
    elsif($TypeAttr{"Type"} eq "Array")
    {
        ($TypeAttr{"BaseType"}{"Tid"}, $TypeAttr{"BaseType"}{"TDid"}, $BaseTypeSpec) = selectBaseType($TypeDeclId, $TypeId);
        my %BaseTypeAttr = getTypeAttr($TypeAttr{"BaseType"}{"TDid"}, $TypeAttr{"BaseType"}{"Tid"});
        if(my $NElems = getArraySize($TypeId, $BaseTypeAttr{"Name"}))
        {
            $TypeAttr{"Size"} = getSize($TypeId)/$BYTE_SIZE;
            if($BaseTypeAttr{"Name"}=~/\A([^\[\]]+)(\[(\d+|)\].*)\Z/) {
                $TypeAttr{"Name"} = $1."[$NElems]".$2;
            }
            else {
                $TypeAttr{"Name"} = $BaseTypeAttr{"Name"}."[$NElems]";
            }
        }
        else
        {
            $TypeAttr{"Size"} = $WORD_SIZE{$Version}; # pointer
            if($BaseTypeAttr{"Name"}=~/\A([^\[\]]+)(\[(\d+|)\].*)\Z/) {
                $TypeAttr{"Name"} = $1."[]".$2;
            }
            else {
                $TypeAttr{"Name"} = $BaseTypeAttr{"Name"}."[]";
            }
        }
        $TypeAttr{"Name"} = formatName($TypeAttr{"Name"});
        if($BaseTypeAttr{"Header"})  {
            $TypeAttr{"Header"} = $BaseTypeAttr{"Header"};
        }
        %{$TypeInfo{$Version}{$TypeDeclId}{$TypeId}} = %TypeAttr;
        $TName_Tid{$Version}{$TypeInfo{$Version}{$TypeDeclId}{$TypeId}{"Name"}} = $TypeId;
        return %TypeAttr;
    }
    elsif($TypeAttr{"Type"}=~/\A(Intrinsic|Union|Struct|Enum|Class)\Z/)
    {
        %{$TypeInfo{$Version}{$TypeDeclId}{$TypeId}} = getTrivialTypeAttr($TypeDeclId, $TypeId);
        return %{$TypeInfo{$Version}{$TypeDeclId}{$TypeId}};
    }
    else
    {
        ($TypeAttr{"BaseType"}{"Tid"}, $TypeAttr{"BaseType"}{"TDid"}, $BaseTypeSpec) = selectBaseType($TypeDeclId, $TypeId);
        if(my $MissedTDid = $MissedTypedef{$Version}{$TypeAttr{"BaseType"}{"Tid"}}{"TDid"})
        {
            if($MissedTDid ne $TypeDeclId)
            {
                $TypeAttr{"BaseType"}{"TDid"} = $MissedTDid;
                $TypeAttr{"BaseType"}{"Tid"} = $MissedTypedef{$Version}{$TypeAttr{"BaseType"}{"Tid"}}{"Tid"};
            }
        }
        my %BaseTypeAttr = getTypeAttr($TypeAttr{"BaseType"}{"TDid"}, $TypeAttr{"BaseType"}{"Tid"});
        if(not $BaseTypeAttr{"Name"})
        { # const "template_type_parm"
            return ();
        }
        if($BaseTypeAttr{"Type"} eq "Typedef")
        { # relinking typedefs
            my %BaseBase = get_Type($BaseTypeAttr{"BaseType"}{"TDid"},$BaseTypeAttr{"BaseType"}{"Tid"}, $Version);
            if($BaseTypeAttr{"Name"} eq $BaseBase{"Name"}) {
                ($TypeAttr{"BaseType"}{"Tid"}, $TypeAttr{"BaseType"}{"TDid"}) = ($BaseBase{"Tid"}, $BaseBase{"TDid"});
            }
        }
        if($BaseTypeSpec)
        {
            if($TypeAttr{"Type"} eq "Pointer"
            and $BaseTypeAttr{"Name"}=~/\([\*]+\)/) {
                $TypeAttr{"Name"} = $BaseTypeAttr{"Name"};
                $TypeAttr{"Name"}=~s/\(([*]+)\)/($1*)/g;
            }
            else {
                $TypeAttr{"Name"} = $BaseTypeAttr{"Name"}." ".$BaseTypeSpec;
            }
        }
        else {
            $TypeAttr{"Name"} = $BaseTypeAttr{"Name"};
        }
        if($TypeAttr{"Type"} eq "Typedef")
        {
            $TypeAttr{"Name"} = getNameByInfo($TypeDeclId);
            if(my $NS = getNameSpace($TypeDeclId))
            {
                my $TypeName = $TypeAttr{"Name"};
                if($NS=~/\A(struct |union |class |)((.+)::|)\Q$TypeName\E\Z/)
                { # "some_type" is the typedef to "struct some_type" in C++
                    if($3) {
                        $TypeAttr{"Name"} = $3."::".$TypeName;
                    }
                }
                else
                {
                    $TypeAttr{"NameSpace"} = $NS;
                    $TypeAttr{"Name"} = $TypeAttr{"NameSpace"}."::".$TypeAttr{"Name"};
                    if($TypeAttr{"NameSpace"}=~/\Astd(::|\Z)/ and $BaseTypeAttr{"NameSpace"}=~/\Astd(::|\Z)/
                    and $BaseTypeAttr{"Name"}=~/</ and $TypeAttr{"Name"}!~/>(::\w+)+\Z/)
                    { # types like "std::fpos<__mbstate_t>" are
                      # not covered by typedefs in the ABI dump
                      # so trying to add such typedefs manually
                        $StdCxxTypedef{$Version}{$BaseTypeAttr{"Name"}}{$TypeAttr{"Name"}} = 1;
                        if(length($TypeAttr{"Name"})<=length($BaseTypeAttr{"Name"}))
                        {
                            if(($BaseTypeAttr{"Name"}!~/\A(std|boost)::/ or $TypeAttr{"Name"}!~/\A[a-z]+\Z/))
                            { # skip "other" in "std" and "type" in "boost"
                                $Typedef_Eq{$Version}{$BaseTypeAttr{"Name"}} = $TypeAttr{"Name"};
                            }
                        }
                    }
                }
            }
            if($TypeAttr{"Name"} ne $BaseTypeAttr{"Name"}
            and $TypeAttr{"Name"}!~/>(::\w+)+\Z/ and $BaseTypeAttr{"Name"}!~/>(::\w+)+\Z/)
            {
                $Typedef_BaseName{$Version}{$TypeAttr{"Name"}} = $BaseTypeAttr{"Name"};
                if($BaseTypeAttr{"Name"}=~/</)
                {
                    if(($BaseTypeAttr{"Name"}!~/\A(std|boost)::/ or $TypeAttr{"Name"}!~/\A[a-z]+\Z/)) {
                        $Typedef_Tr{$Version}{$BaseTypeAttr{"Name"}}{$TypeAttr{"Name"}} = 1;
                    }
                }
            }
            ($TypeAttr{"Header"}, $TypeAttr{"Line"}) = getLocation($TypeDeclId);
        }
        if(not $TypeAttr{"Size"})
        {
            if($TypeAttr{"Type"} eq "Pointer") {
                $TypeAttr{"Size"} = $WORD_SIZE{$Version};
            }
            elsif($BaseTypeAttr{"Size"}) {
                $TypeAttr{"Size"} = $BaseTypeAttr{"Size"};
            }
        }
        $TypeAttr{"Name"} = formatName($TypeAttr{"Name"});
        if(not $TypeAttr{"Header"} and $BaseTypeAttr{"Header"})  {
            $TypeAttr{"Header"} = $BaseTypeAttr{"Header"};
        }
        %{$TypeInfo{$Version}{$TypeDeclId}{$TypeId}} = %TypeAttr;
        if(not $TName_Tid{$Version}{$TypeAttr{"Name"}}) {
            $TName_Tid{$Version}{$TypeAttr{"Name"}} = $TypeId;
        }
        return %TypeAttr;
    }
}

sub get_TemplateParam($$)
{
    my ($Pos, $Type_Id) = @_;
    return "" if(not $Type_Id);
    if($Cache{"get_TemplateParam"}{$Type_Id}) {
        return $Cache{"get_TemplateParam"}{$Type_Id};
    }
    if(getNodeType($Type_Id) eq "integer_cst")
    { # int (1), unsigned (2u), char ('c' as 99), ...
        my $CstTid = getTreeAttr($Type_Id, "type");
        my %CstType = getTypeAttr(getTypeDeclId($CstTid), $CstTid);
        my $Num = getNodeIntCst($Type_Id);
        if(my $CstSuffix = $ConstantSuffix{$CstType{"Name"}}) {
            return $Num.$CstSuffix;
        }
        else {
            return "(".$CstType{"Name"}.")".$Num;
        }
    }
    elsif(getNodeType($Type_Id) eq "string_cst") {
        return getNodeStrCst($Type_Id);
    }
    elsif(getNodeType($Type_Id) eq "tree_vec") {
        return "\@skip\@";
    }
    else
    {
        my $Type_DId = getTypeDeclId($Type_Id);
        my %ParamAttr = getTypeAttr($Type_DId, $Type_Id);
        if(not $ParamAttr{"Name"}) {
            return "";
        }
        my $PName = $ParamAttr{"Name"};
        if($ParamAttr{"Name"}=~/\>/) {
            if(my $Cover = cover_stdcxx_typedef($ParamAttr{"Name"})) {
                $PName = $Cover;
            }
        }
        if($Pos>=1 and
        $PName=~/\Astd::(allocator|less|((char|regex)_traits)|((i|o)streambuf_iterator))\</)
        { # template<typename _Tp, typename _Alloc = std::allocator<_Tp> >
          # template<typename _Key, typename _Compare = std::less<_Key>
          # template<typename _CharT, typename _Traits = std::char_traits<_CharT> >
          # template<typename _Ch_type, typename _Rx_traits = regex_traits<_Ch_type> >
          # template<typename _CharT, typename _InIter = istreambuf_iterator<_CharT> >
          # template<typename _CharT, typename _OutIter = ostreambuf_iterator<_CharT> >
            return "\@skip\@";
        }
        return $PName;
    }
}

sub cover_stdcxx_typedef($)
{
    my $TypeName = $_[0];
    if(my @Covers = sort {length($a)<=>length($b)}
    sort keys(%{$StdCxxTypedef{$Version}{$TypeName}}))
    { # take the shortest typedef
      # FIXME: there may be more than
      # one typedefs to the same type
        return $Covers[0];
    }
    my $TypeName_Covered = $TypeName;
    while($TypeName=~s/(>)[ ]*(const|volatile|restrict| |\*|\&)\Z/$1/g){};
    if(my @Covers = sort {length($a)<=>length($b)} sort keys(%{$StdCxxTypedef{$Version}{$TypeName}}))
    {
        my $Cover = $Covers[0];
        $TypeName_Covered=~s/(\W|\A)\Q$TypeName\E(\W|\Z)/$1$Cover$2/g;
        $TypeName_Covered=~s/(\W|\A)\Q$TypeName\E(\w|\Z)/$1$Cover $2/g;
    }
    return formatName($TypeName_Covered);
}

sub getNodeType($)
{
    return $LibInfo{$Version}{"info_type"}{$_[0]};
}

sub getNodeIntCst($)
{
    my $CstId = $_[0];
    my $CstTypeId = getTreeAttr($CstId, "type");
    if($EnumMembName_Id{$Version}{$CstId}) {
        return $EnumMembName_Id{$Version}{$CstId};
    }
    elsif((my $Value = getTreeValue($CstId)) ne "")
    {
        if($Value eq "0") {
            if(getNodeType($CstTypeId) eq "boolean_type") {
                return "false";
            }
            else {
                return "0";
            }
        }
        elsif($Value eq "1") {
            if(getNodeType($CstTypeId) eq "boolean_type") {
                return "true";
            }
            else {
                return "1";
            }
        }
        else {
            return $Value;
        }
    }
    else {
        return "";
    }
}

sub getNodeStrCst($)
{
    if($LibInfo{$Version}{"info"}{$_[0]}=~/strg[ ]*: (.+) lngt:[ ]*(\d+)/)
    { # string length is N-1 because of the null terminator
        return substr($1, 0, $2-1);
    }
    else {
        return "";
    }
}

sub getMemPtrAttr($$$$)
{ # function, method and field pointers
    my ($PtrId, $TypeDeclId, $TypeId, $Type) = @_;
    my $MemInfo = $LibInfo{$Version}{"info"}{$PtrId};
    if($Type eq "FieldPtr") {
        $MemInfo = $LibInfo{$Version}{"info"}{$TypeId};
    }
    my $MemInfo_Type = $LibInfo{$Version}{"info_type"}{$PtrId};
    my $MemPtrName = "";
    my %TypeAttr = ("Size"=>$WORD_SIZE{$Version}, "Type"=>$Type, "TDid"=>$TypeDeclId, "Tid"=>$TypeId);
    if($Type eq "MethodPtr")
    { # size of "method pointer" may be greater than WORD size
        $TypeAttr{"Size"} = getSize($TypeId)/$BYTE_SIZE;
    }
    # Return
    if($Type eq "FieldPtr")
    {
        my %ReturnAttr = getTypeAttr(getTypeDeclId($PtrId), $PtrId);
        $MemPtrName .= $ReturnAttr{"Name"};
        $TypeAttr{"Return"} = $PtrId;
    }
    else
    {
        if($MemInfo=~/retn[ ]*:[ ]*\@(\d+) /)
        {
            my $ReturnTypeId = $1;
            my %ReturnAttr = getTypeAttr(getTypeDeclId($ReturnTypeId), $ReturnTypeId);
            $MemPtrName .= $ReturnAttr{"Name"};
            $TypeAttr{"Return"} = $ReturnTypeId;
        }
    }
    # Class
    if($MemInfo=~/(clas|cls)[ ]*:[ ]*@(\d+) /)
    {
        $TypeAttr{"Class"} = $2;
        my %Class = getTypeAttr(getTypeDeclId($TypeAttr{"Class"}), $TypeAttr{"Class"});
        if($Class{"Name"}) {
            $MemPtrName .= " (".$Class{"Name"}."\:\:*)";
        }
        else {
            $MemPtrName .= " (*)";
        }
    }
    else {
        $MemPtrName .= " (*)";
    }
    # Parameters
    if($Type eq "FuncPtr"
    or $Type eq "MethodPtr")
    {
        my @ParamTypeName = ();
        if($MemInfo=~/prms[ ]*:[ ]*@(\d+) /)
        {
            my $ParamTypeInfoId = $1;
            my $Position = 0;
            while($ParamTypeInfoId)
            {
                my $ParamTypeInfo = $LibInfo{$Version}{"info"}{$ParamTypeInfoId};
                last if($ParamTypeInfo!~/valu[ ]*:[ ]*@(\d+) /);
                my $ParamTypeId = $1;
                my %ParamAttr = getTypeAttr(getTypeDeclId($ParamTypeId), $ParamTypeId);
                last if($ParamAttr{"Name"} eq "void");
                if($Position!=0 or $Type ne "MethodPtr")
                {
                    $TypeAttr{"Param"}{$Position}{"type"} = $ParamTypeId;
                    push(@ParamTypeName, $ParamAttr{"Name"});
                }
                last if($ParamTypeInfo!~/(chan|chain)[ ]*:[ ]*@(\d+) /);
                $ParamTypeInfoId = $2;
                $Position+=1;
            }
        }
        $MemPtrName .= " (".join(", ", @ParamTypeName).")";
    }
    $TypeAttr{"Name"} = formatName($MemPtrName);
    return %TypeAttr;
}

sub getTreeTypeName($)
{
    my $Info = $LibInfo{$Version}{"info"}{$_[0]};
    if($Info=~/name[ ]*:[ ]*@(\d+) /) {
        return getNameByInfo($1);
    }
    else
    {
        if($LibInfo{$Version}{"info_type"}{$_[0]} eq "integer_type")
        {
            if($LibInfo{$Version}{"info"}{$_[0]}=~/unsigned/) {
                return "unsigned int";
            }
            else {
                return "int";
            }
        }
        else {
            return "";
        }
    }
}

sub getTypeType($$)
{
    my ($TypeDeclId, $TypeId) = @_;
    if($MissedTypedef{$Version}{$TypeId}{"TDid"}
    and $MissedTypedef{$Version}{$TypeId}{"TDid"} eq $TypeDeclId)
    { # support for old GCC versions
        return "Typedef";
    }
    my $Info = $LibInfo{$Version}{"info"}{$TypeId};
    if($Info=~/unql[ ]*:/ and $Info!~/qual[ ]*:/
    and getNameByInfo($TypeDeclId)) {
        return "Typedef";
    }
    elsif(my ($Qual, $To) = getQual($TypeId))
    {
        if($Qual eq "const volatile") {
            return "ConstVolatile";
        }
        else {
            return ucfirst($Qual);
        }
    }
    my $TypeType = getTypeTypeByTypeId($TypeId);
    if($TypeType eq "Struct")
    {
        if($TypeDeclId
        and $LibInfo{$Version}{"info_type"}{$TypeDeclId} eq "template_decl") {
            return "Template";
        }
        else {
            return "Struct";
        }
    }
    else {
        return $TypeType;
    }
}

sub getQual($)
{
    my $TypeId = $_[0];
    my $Info = $LibInfo{$Version}{"info"}{$TypeId};
    my ($Qual, $To) = ();
    my %UnQual = (
        "r"=>"restrict",
        "v"=>"volatile",
        "c"=>"const",
        "cv"=>"const volatile"
    );
    if($Info=~/qual[ ]*:[ ]*(r|c|v|cv) /) {
        $Qual = $UnQual{$1};
    }
    if($Info=~/unql[ ]*:[ ]*\@(\d+)/) {
        $To = $1;
    }
    if($Qual and $To) {
        return ($Qual, $To);
    }
    return ();
}

sub selectBaseType($$)
{
    my ($TypeDeclId, $TypeId) = @_;
    if($MissedTypedef{$Version}{$TypeId}{"TDid"}
    and $MissedTypedef{$Version}{$TypeId}{"TDid"} eq $TypeDeclId) {
        return ($TypeId, getTypeDeclId($TypeId), "");
    }
    my $TInfo = $LibInfo{$Version}{"info"}{$TypeId};
    if(my ($Qual, $To) = getQual($TypeId)
    and $TInfo=~/name[ ]*:[ ]*\@(\d+) /
    and (getTypeId($1) ne $TypeId)) {
        return (getTypeId($1), $1, $Qual);
    }
    elsif($TInfo!~/qual[ ]*:/
    and $TInfo=~/unql[ ]*:[ ]*\@(\d+) /
    and getNameByInfo($TypeDeclId))
    { # typedefs
        return ($1, getTypeDeclId($1), "");
    }
    elsif(my ($Qual, $To) = getQual($TypeId)) {
        return ($To, getTypeDeclId($To), $Qual);
    }
    elsif($LibInfo{$Version}{"info_type"}{$TypeId} eq "reference_type")
    {
        if($TInfo=~/refd[ ]*:[ ]*@(\d+) /) {
            return ($1, getTypeDeclId($1), "&");
        }
        else {
            return (0, 0, "");
        }
    }
    elsif($LibInfo{$Version}{"info_type"}{$TypeId} eq "array_type")
    {
        if($TInfo=~/elts[ ]*:[ ]*@(\d+) /) {
            return ($1, getTypeDeclId($1), "");
        }
        else {
            return (0, 0, "");
        }
    }
    elsif($LibInfo{$Version}{"info_type"}{$TypeId} eq "pointer_type")
    {
        if($TInfo=~/ptd[ ]*:[ ]*@(\d+) /) {
            return ($1, getTypeDeclId($1), "*");
        }
        else {
            return (0, 0, "");
        }
    }
    else {
        return (0, 0, "");
    }
}

sub getSymbolInfo_All()
{
    foreach (sort {int($b)<=>int($a)} keys(%{$LibInfo{$Version}{"info"}}))
    { # reverse order
        if($LibInfo{$Version}{"info_type"}{$_} eq "function_decl") {
            getSymbolInfo("$_");
        }
    }
}

sub getVarInfo_All()
{
    foreach (sort {int($b)<=>int($a)} keys(%{$LibInfo{$Version}{"info"}}))
    { # reverse order
        if($LibInfo{$Version}{"info_type"}{$_} eq "var_decl") {
            getVarInfo("$_");
        }
    }
}

sub isBuiltIn($) {
    return ($_[0]=~/\<built\-in\>|\<internal\>|\A\./);
}

sub getVarInfo($)
{
    my $InfoId = $_[0];
    if($LibInfo{$Version}{"info_type"}{getNameSpaceId($InfoId)} eq "function_decl") {
        return;
    }
    ($SymbolInfo{$Version}{$InfoId}{"Header"}, $SymbolInfo{$Version}{$InfoId}{"Line"}) = getLocation($InfoId);
    if(not $SymbolInfo{$Version}{$InfoId}{"Header"}
    or isBuiltIn($SymbolInfo{$Version}{$InfoId}{"Header"})) {
        delete($SymbolInfo{$Version}{$InfoId});
        return;
    }
    my $ShortName = $SymbolInfo{$Version}{$InfoId}{"ShortName"} = getVarShortName($InfoId);
    if($SymbolInfo{$Version}{$InfoId}{"ShortName"}=~/\Atmp_add_class_\d+\Z/) {
        delete($SymbolInfo{$Version}{$InfoId});
        return;
    }
    $SymbolInfo{$Version}{$InfoId}{"MnglName"} = getFuncMnglName($InfoId);
    if($SymbolInfo{$Version}{$InfoId}{"MnglName"}
    and $SymbolInfo{$Version}{$InfoId}{"MnglName"}!~/\A_Z/)
    { # validate mangled name
        delete($SymbolInfo{$Version}{$InfoId});
        return;
    }
    $SymbolInfo{$Version}{$InfoId}{"Data"} = 1;
    $SymbolInfo{$Version}{$InfoId}{"Return"} = getTypeId($InfoId);
    if(not $SymbolInfo{$Version}{$InfoId}{"Return"}) {
        delete($SymbolInfo{$Version}{$InfoId}{"Return"});
    }
    set_Class_And_Namespace($InfoId);
    if($LibInfo{$Version}{"info"}{$InfoId}=~/ lang:[ ]*C /i) {
        $SymbolInfo{$Version}{$InfoId}{"Lang"} = "C";
    }
    if($UserLang eq "C")
    { # --lang=C option
        $SymbolInfo{$Version}{$InfoId}{"MnglName"} = $SymbolInfo{$Version}{$InfoId}{"ShortName"};
    }
    if($COMMON_LANGUAGE{$Version} eq "C++")
    {
        if(not $SymbolInfo{$Version}{$InfoId}{"MnglName"})
        { # for some symbols (_ZTI) the short name is the mangled name
            if($ShortName=~/\A_Z/) {
                $SymbolInfo{$Version}{$InfoId}{"MnglName"} = $ShortName;
            }
        }
        if(not $SymbolInfo{$Version}{$InfoId}{"MnglName"})
        { # try to mangle symbol (link with libraries)
            $SymbolInfo{$Version}{$InfoId}{"MnglName"} = linkSymbol($InfoId);
        }
        if($OStarget eq "windows")
        {
            if(my $Mangled = $mangled_name{$Version}{modelUnmangled($InfoId, "MSVC")})
            { # link MS C++ symbols from library with GCC symbols from headers
                $SymbolInfo{$Version}{$InfoId}{"MnglName"} = $Mangled;
            }
        }
    }
    if(not $SymbolInfo{$Version}{$InfoId}{"MnglName"}) {
        $SymbolInfo{$Version}{$InfoId}{"MnglName"} = $ShortName;
    }
    if(not link_symbol($SymbolInfo{$Version}{$InfoId}{"MnglName"}, $Version, "-Deps")
    and not $CheckHeadersOnly)
    {
        if(link_symbol($ShortName, $Version, "-Deps")
        and $SymbolInfo{$Version}{$InfoId}{"MnglName"}=~/_ZL\d+$ShortName\Z/)
        { # const int global_data is mangled as _ZL11global_data in the tree
            $SymbolInfo{$Version}{$InfoId}{"MnglName"} = $ShortName;
        }
        else {
            delete($SymbolInfo{$Version}{$InfoId});
            return;
        }
    }
    if(not $SymbolInfo{$Version}{$InfoId}{"MnglName"}) {
        delete($SymbolInfo{$Version}{$InfoId});
        return;
    }
    if(my $AddedTid = $MissedTypedef{$Version}{$SymbolInfo{$Version}{$InfoId}{"Return"}}{"Tid"}) {
        $SymbolInfo{$Version}{$InfoId}{"Return"} = $AddedTid;
    }
    setFuncAccess($InfoId);
    if($SymbolInfo{$Version}{$InfoId}{"MnglName"}=~/\A_ZTV/) {
        delete($SymbolInfo{$Version}{$InfoId}{"Return"});
    }
    if($ShortName=~/\A(_Z|\?)/) {
        delete($SymbolInfo{$Version}{$InfoId}{"ShortName"});
    }
}

sub getTrivialName($$)
{
    my ($TypeInfoId, $TypeId) = @_;
    my %TypeAttr = ();
    $TypeAttr{"Name"} = getNameByInfo($TypeInfoId);
    if(not $TypeAttr{"Name"}) {
        $TypeAttr{"Name"} = getTreeTypeName($TypeId);
    }
    $TypeAttr{"Name"}=~s/<(.+)\Z//g; # GCC 3.4.4 add template params to the name
    if(my $NameSpaceId = getNameSpaceId($TypeInfoId)) {
        if($NameSpaceId ne $TypeId) {
            $TypeAttr{"NameSpace"} = getNameSpace($TypeInfoId);
        }
    }
    if($TypeAttr{"NameSpace"} and isNotAnon($TypeAttr{"Name"})) {
        $TypeAttr{"Name"} = $TypeAttr{"NameSpace"}."::".$TypeAttr{"Name"};
    }
    $TypeAttr{"Name"} = formatName($TypeAttr{"Name"});
    if(isAnon($TypeAttr{"Name"}))
    {# anon-struct-header.h-line
        $TypeAttr{"Name"} = "anon-".lc(getTypeType($TypeInfoId, $TypeId))."-";
        $TypeAttr{"Name"} .= $TypeAttr{"Header"}."-".$TypeAttr{"Line"};
    }
    if(defined $TemplateInstance{$Version}{$TypeInfoId}{$TypeId})
    {
        my @TParams = getTParams($TypeInfoId, $TypeId);
        if(not @TParams)
        { # template declarations with abstract params
            # vector (tree_vec) of template_type_parm nodes in the TU dump
            return ("", "");
        }
        $TypeAttr{"Name"} = formatName($TypeAttr{"Name"}."< ".join(", ", @TParams)." >");
    }
    return ($TypeAttr{"Name"}, $TypeAttr{"NameSpace"});
}

sub getTrivialTypeAttr($$)
{
    my ($TypeInfoId, $TypeId) = @_;
    my %TypeAttr = ();
    if(getTypeTypeByTypeId($TypeId)!~/\A(Intrinsic|Union|Struct|Enum|Class)\Z/) {
        return ();
    }
    setTypeAccess($TypeId, \%TypeAttr);
    ($TypeAttr{"Header"}, $TypeAttr{"Line"}) = getLocation($TypeInfoId);
    if(isBuiltIn($TypeAttr{"Header"}))
    {
        delete($TypeAttr{"Header"});
        delete($TypeAttr{"Line"});
    }
    $TypeAttr{"Type"} = getTypeType($TypeInfoId, $TypeId);
    ($TypeAttr{"Name"}, $TypeAttr{"NameSpace"}) = getTrivialName($TypeInfoId, $TypeId);
    if(not $TypeAttr{"Name"}) {
        return ();
    }
    if(not $TypeAttr{"NameSpace"}) {
        delete($TypeAttr{"NameSpace"});
    }
    if(isAnon($TypeAttr{"Name"}))
    {
        $TypeAttr{"Name"} = "anon-".lc($TypeAttr{"Type"})."-";
        $TypeAttr{"Name"} .= $TypeAttr{"Header"}."-".$TypeAttr{"Line"};
    }
    if(my $Size = getSize($TypeId)) {
        $TypeAttr{"Size"} = $Size/$BYTE_SIZE;
    }
    if($TypeAttr{"Type"} eq "Struct"
    and detect_lang($TypeId))
    {
        $TypeAttr{"Type"} = "Class";
        $TypeAttr{"Copied"} = 1;# default, will be changed in getSymbolInfo()
    }
    if($TypeAttr{"Type"} eq "Struct"
    or $TypeAttr{"Type"} eq "Class") {
        setBaseClasses($TypeInfoId, $TypeId, \%TypeAttr);
    }
    setSpec($TypeId, \%TypeAttr);
    setTypeMemb($TypeId, \%TypeAttr);
    $TypeAttr{"Tid"} = $TypeId;
    $TypeAttr{"TDid"} = $TypeInfoId;
    if($TypeInfoId) {
        $Tid_TDid{$Version}{$TypeId} = $TypeInfoId;
    }
    if(not $TName_Tid{$Version}{$TypeAttr{"Name"}}) {
        $TName_Tid{$Version}{$TypeAttr{"Name"}} = $TypeId;
    }
    if(my $VTable = $ClassVTable_Content{$Version}{$TypeAttr{"Name"}})
    {
        my @Entries = split(/\n/, $VTable);
        foreach (1 .. $#Entries)
        {
            my $Entry = $Entries[$_];
            if($Entry=~/\A(\d+)\s+(.+)\Z/) {
                $TypeAttr{"VTable"}{$1} = $2;
            }
        }
    }
    return %TypeAttr;
}

sub detect_lang($)
{
    my $TypeId = $_[0];
    my $Info = $LibInfo{$Version}{"info"}{$TypeId};
    if(check_gcc_version($GCC_PATH, "4"))
    {# GCC 4 fncs-node points to only non-artificial methods
        return ($Info=~/(fncs)[ ]*:[ ]*@(\d+) /);
    }
    else
    {# GCC 3
        my $Fncs = getTreeAttr($TypeId, "fncs");
        while($Fncs)
        {
            my $Info = $LibInfo{$Version}{"info"}{$Fncs};
            if($Info!~/artificial/) {
                return 1;
            }
            $Fncs = getTreeAttr($Fncs, "chan");
        }
    }
    return 0;
}

sub setSpec($$)
{
    my ($TypeId, $TypeAttr) = @_;
    my $Info = $LibInfo{$Version}{"info"}{$TypeId};
    if($Info=~/\s+spec\s+/) {
        $TypeAttr->{"Spec"} = 1;
    }
}

sub setBaseClasses($$$)
{
    my ($TypeInfoId, $TypeId, $TypeAttr) = @_;
    my $Info = $LibInfo{$Version}{"info"}{$TypeId};
    if($Info=~/binf[ ]*:[ ]*@(\d+) /)
    {
        $Info = $LibInfo{$Version}{"info"}{$1};
        my $Pos = 0;
        while($Info=~s/(pub|public|prot|protected|priv|private|)[ ]+binf[ ]*:[ ]*@(\d+) //)
        {
            my ($Access, $BInfoId) = ($1, $2);
            my $ClassId = getBinfClassId($BInfoId);
            my $BaseInfo = $LibInfo{$Version}{"info"}{$BInfoId};
            if($Access=~/prot/)
            {
                $TypeAttr->{"Base"}{$ClassId}{"access"} = "protected";
            }
            elsif($Access=~/priv/)
            {
                $TypeAttr->{"Base"}{$ClassId}{"access"} = "private";
            }
            $TypeAttr->{"Base"}{$ClassId}{"pos"} = $Pos++;
            if($BaseInfo=~/virt/)
            {# virtual base
                $TypeAttr->{"Base"}{$ClassId}{"virtual"} = 1;
            }
            $Class_SubClasses{$Version}{$ClassId}{$TypeId}=1;
        }
    }
}

sub getBinfClassId($)
{
    my $Info = $LibInfo{$Version}{"info"}{$_[0]};
    $Info=~/type[ ]*:[ ]*@(\d+) /;
    return $1;
}

sub unmangledFormat($$)
{
    my ($Name, $LibVersion) = @_;
    $Name = uncover_typedefs($Name, $LibVersion);
    while($Name=~s/([^\w>*])(const|volatile)(,|>|\Z)/$1$3/g){};
    $Name=~s/\(\w+\)(\d)/$1/;
    return $Name;
}

sub modelUnmangled($$)
{
    my ($InfoId, $Compiler) = @_;
    if($Cache{"modelUnmangled"}{$Version}{$Compiler}{$InfoId}) {
        return $Cache{"modelUnmangled"}{$Version}{$Compiler}{$InfoId};
    }
    my $PureSignature = $SymbolInfo{$Version}{$InfoId}{"ShortName"};
    if($SymbolInfo{$Version}{$InfoId}{"Destructor"}) {
        $PureSignature = "~".$PureSignature;
    }
    if(not $SymbolInfo{$Version}{$InfoId}{"Data"})
    {
        my (@Params, @ParamTypes) = ();
        if(defined $SymbolInfo{$Version}{$InfoId}{"Param"}
        and not $SymbolInfo{$Version}{$InfoId}{"Destructor"}) {
            @Params = keys(%{$SymbolInfo{$Version}{$InfoId}{"Param"}});
        }
        foreach my $ParamPos (sort {int($a) <=> int($b)} @Params)
        { # checking parameters
            my $PId = $SymbolInfo{$Version}{$InfoId}{"Param"}{$ParamPos}{"type"};
            my %PType = get_PureType($Tid_TDid{$Version}{$PId}, $PId, $Version);
            my $PTName = unmangledFormat($PType{"Name"}, $Version);
            $PTName=~s/(\A|\W)(restrict|register)(\W|\Z)/$1$3/g;
            if($Compiler eq "MSVC") {
                $PTName=~s/(\W|\A)long long(\W|\Z)/$1__int64$2/;
            }
            @ParamTypes = (@ParamTypes, $PTName);
        }
        if(@ParamTypes) {
            $PureSignature .= "(".join(", ", @ParamTypes).")";
        }
        else
        {
            if($Compiler eq "MSVC")
            {
                $PureSignature .= "(void)";
            }
            else
            { # GCC
                $PureSignature .= "()";
            }
        }
        $PureSignature = delete_keywords($PureSignature);
    }
    if(my $ClassId = $SymbolInfo{$Version}{$InfoId}{"Class"})
    {
        my $ClassName = unmangledFormat(get_TypeName($ClassId, $Version), $Version);
        $PureSignature = $ClassName."::".$PureSignature;
    }
    elsif(my $NS = $SymbolInfo{$Version}{$InfoId}{"NameSpace"}) {
        $PureSignature = $NS."::".$PureSignature;
    }
    if($SymbolInfo{$Version}{$InfoId}{"Const"}) {
        $PureSignature .= " const";
    }
    if($SymbolInfo{$Version}{$InfoId}{"Volatile"}) {
        $PureSignature .= " volatile";
    }
    my $ShowReturn = 0;
    if($Compiler eq "MSVC"
    and $SymbolInfo{$Version}{$InfoId}{"Data"})
    {
        $ShowReturn=1;
    }
    elsif(defined $TemplateInstance_Func{$Version}{$InfoId}
    and keys(%{$TemplateInstance_Func{$Version}{$InfoId}}))
    {
        $ShowReturn=1;
    }
    if($ShowReturn)
    { # mangled names for template function specializations include return value
        if(my $ReturnId = $SymbolInfo{$Version}{$InfoId}{"Return"})
        {
            my %RType = get_PureType($Tid_TDid{$Version}{$ReturnId}, $ReturnId, $Version);
            my $ReturnName = unmangledFormat($RType{"Name"}, $Version);
            $PureSignature = $ReturnName." ".$PureSignature;
        }
    }
    return ($Cache{"modelUnmangled"}{$Version}{$Compiler}{$InfoId} = formatName($PureSignature));
}

sub mangle_symbol($$$)
{ # mangling for simple methods
  # see gcc-4.6.0/gcc/cp/mangle.c
    my ($InfoId, $LibVersion, $Compiler) = @_;
    if($Cache{"mangle_symbol"}{$LibVersion}{$InfoId}{$Compiler}) {
        return $Cache{"mangle_symbol"}{$LibVersion}{$InfoId}{$Compiler};
    }
    my $Mangled = "";
    if($Compiler eq "GCC") {
        $Mangled = mangle_symbol_gcc($InfoId, $LibVersion);
    }
    elsif($Compiler eq "MSVC") {
        $Mangled = mangle_symbol_msvc($InfoId, $LibVersion);
    }
    return ($Cache{"mangle_symbol"}{$LibVersion}{$InfoId}{$Compiler} = $Mangled);
}

sub mangle_symbol_msvc($$)
{
    my ($InfoId, $LibVersion) = @_;
    return "";
}

sub mangle_symbol_gcc($$)
{ # see gcc-4.6.0/gcc/cp/mangle.c
    my ($InfoId, $LibVersion) = @_;
    my ($Mangled, $ClassId, $NameSpace) = ("_Z", 0, "");
    my %Repl = ();# SN_ replacements
    if($ClassId = $SymbolInfo{$LibVersion}{$InfoId}{"Class"})
    {
        my $MangledClass = mangle_param($ClassId, $LibVersion, \%Repl);
        if($MangledClass!~/\AN/) {
            $MangledClass = "N".$MangledClass;
        }
        else {
            $MangledClass=~s/E\Z//;
        }
        if($SymbolInfo{$LibVersion}{$InfoId}{"Volatile"}) {
            $MangledClass=~s/\AN/NV/;
        }
        if($SymbolInfo{$LibVersion}{$InfoId}{"Const"}) {
            $MangledClass=~s/\AN/NK/;
        }
        $Mangled .= $MangledClass;
    }
    elsif($NameSpace = $SymbolInfo{$LibVersion}{$InfoId}{"NameSpace"})
    { # mangled by name due to the absence of structured info
        my $MangledNS = mangle_ns($NameSpace, $LibVersion, \%Repl);
        if($MangledNS!~/\AN/) {
            $MangledNS = "N".$MangledNS;
        }
        else {
            $MangledNS=~s/E\Z//;
        }
        $Mangled .= $MangledNS;
    }
    my ($ShortName, $TmplParams) = template_base($SymbolInfo{$LibVersion}{$InfoId}{"ShortName"});
    my @TParams = getTParams_Func($InfoId);
    if(not @TParams and $TmplParams)
    { # support for old ABI dumps
        @TParams = separate_params($TmplParams, 0);
    }
    if($SymbolInfo{$LibVersion}{$InfoId}{"Constructor"}) {
        $Mangled .= "C1";
    }
    elsif($SymbolInfo{$LibVersion}{$InfoId}{"Destructor"}) {
        $Mangled .= "D0";
    }
    elsif($ShortName)
    {
        if(($NameSpace eq "__gnu_cxx"
        or $ShortName=~/\A__(gthrw|gthread)_/)
        and not $ClassId)
        { # _ZN9__gnu_cxxL25__exchange_and_add_singleEPii
          # _ZN9__gnu_cxxL19__atomic_add_singleEPii
          # _ZL19__gthrw_sched_yieldv
          # _ZL21__gthread_setspecificjPKv
            $Mangled .= "L";
        }
        if($ShortName=~/\Aoperator(\W.*)\Z/)
        {
            my $Op = $1;
            $Op=~s/\A[ ]+//g;
            if(my $OpMngl = $OperatorMangling{$Op}) {
                $Mangled .= $OpMngl;
            }
            else { # conversion operator
                $Mangled .= "cv".mangle_param(getTypeIdByName($Op, $LibVersion), $LibVersion, \%Repl);
            }
        }
        else {
            $Mangled .= length($ShortName).$ShortName;
        }
        if(@TParams)
        { # templates
            $Mangled .= "I";
            foreach my $TParam (@TParams) {
                $Mangled .= mangle_template_param($TParam, $LibVersion, \%Repl);
            }
            $Mangled .= "E";
        }
        if(not $ClassId and @TParams) {
            add_substitution($ShortName, \%Repl, 0);
        }
    }
    if($ClassId or $NameSpace) {
        $Mangled .= "E";
    }
    if(@TParams) {
        if(my $Return = $SymbolInfo{$LibVersion}{$InfoId}{"Return"}) {
            $Mangled .= mangle_param($Return, $LibVersion, \%Repl);
        }
    }
    if(not $SymbolInfo{$LibVersion}{$InfoId}{"Data"})
    {
        my @Params = ();
        if(defined $SymbolInfo{$LibVersion}{$InfoId}{"Param"}
        and not $SymbolInfo{$LibVersion}{$InfoId}{"Destructor"}) {
            @Params = keys(%{$SymbolInfo{$LibVersion}{$InfoId}{"Param"}});
        }
        foreach my $ParamPos (sort {int($a) <=> int($b)} @Params)
        { # checking parameters
            my $ParamType_Id = $SymbolInfo{$LibVersion}{$InfoId}{"Param"}{$ParamPos}{"type"};
            $Mangled .= mangle_param($ParamType_Id, $LibVersion, \%Repl);
        }
        if(not @Params) {
            $Mangled .= "v";
        }
    }
    $Mangled = correct_incharge($InfoId, $LibVersion, $Mangled);
    $Mangled = write_stdcxx_substitution($Mangled);
    if($Mangled eq "_Z") {
        return "";
    }
    return $Mangled;
}

sub correct_incharge($$$)
{
    my ($InfoId, $LibVersion, $Mangled) = @_;
    if($SymbolInfo{$LibVersion}{$InfoId}{"Constructor"})
    {
        if($MangledNames{$LibVersion}{$Mangled}) {
            $Mangled=~s/C1E/C2E/;
        }
    }
    elsif($SymbolInfo{$LibVersion}{$InfoId}{"Destructor"})
    {
        if($MangledNames{$LibVersion}{$Mangled}) {
            $Mangled=~s/D0E/D1E/;
        }
        if($MangledNames{$LibVersion}{$Mangled}) {
            $Mangled=~s/D1E/D2E/;
        }
    }
    return $Mangled;
}

sub template_base($)
{ # NOTE: std::_Vector_base<mysqlpp::mysql_type_info>::_Vector_impl
  # NOTE: operator<<
    my $Name = $_[0];
    if($Name!~/>\Z/) {
        return $Name;
    }
    my $TParams = $Name;
    while(my $CPos = detect_center($TParams, "<")) {
        $TParams = substr($TParams, $CPos);
    }
    $Name=~s/\Q$TParams\E\Z//;
    $TParams=~s/\A<(.+)>\Z/$1/;
    return ($Name, $TParams);
}

sub get_sub_ns($)
{
    my $Name = $_[0];
    my @NS = ();
    while(my $CPos = detect_center($Name, ":"))
    {
        push(@NS, substr($Name, 0, $CPos));
        $Name = substr($Name, $CPos);
        $Name=~s/\A:://;
    }
    return (join("::", @NS), $Name);
}

sub mangle_ns($$$)
{
    my ($Name, $LibVersion, $Repl) = @_;
    if(my $Tid = $TName_Tid{$LibVersion}{$Name})
    {
        my $Mangled = mangle_param($Tid, $LibVersion, $Repl);
        $Mangled=~s/\AN(.+)E\Z/$1/;
        return $Mangled;
        
    }
    else
    {
        my ($MangledNS, $SubNS) = ("", "");
        ($SubNS, $Name) = get_sub_ns($Name);
        if($SubNS) {
            $MangledNS .= mangle_ns($SubNS, $LibVersion, $Repl);
        }
        $MangledNS .= length($Name).$Name;
        add_substitution($MangledNS, $Repl, 0);
        return $MangledNS;
    }
}

sub mangle_param($$$)
{
    my ($PTid, $LibVersion, $Repl) = @_;
    my ($MPrefix, $Mangled) = ("", "");
    my %ReplCopy = %{$Repl};
    my %BaseType = get_BaseType($Tid_TDid{$LibVersion}{$PTid}, $PTid, $LibVersion);
    my $BaseType_Name = $BaseType{"Name"};
    if(not $BaseType_Name) {
        return "";
    }
    my ($ShortName, $TmplParams) = template_base($BaseType_Name);
    my $Suffix = get_BaseTypeQual($Tid_TDid{$LibVersion}{$PTid}, $PTid, $LibVersion);
    while($Suffix=~s/\s*(const|volatile|restrict)\Z//g){};
    while($Suffix=~/(&|\*|const)\Z/)
    {
        if($Suffix=~s/[ ]*&\Z//) {
            $MPrefix .= "R";
        }
        if($Suffix=~s/[ ]*\*\Z//) {
            $MPrefix .= "P";
        }
        if($Suffix=~s/[ ]*const\Z//)
        {
            if($MPrefix=~/R|P/
            or $Suffix=~/&|\*/) {
                $MPrefix .= "K";
            }
        }
        if($Suffix=~s/[ ]*volatile\Z//) {
            $MPrefix .= "V";
        }
        #if($Suffix=~s/[ ]*restrict\Z//) {
            #$MPrefix .= "r";
        #}
    }
    if(my $Token = $IntrinsicMangling{$BaseType_Name}) {
        $Mangled .= $Token;
    }
    elsif($BaseType{"Type"}=~/(Class|Struct|Union|Enum)/)
    {
        my @TParams = getTParams($BaseType{"TDid"}, $BaseType{"Tid"});
        if(not @TParams and $TmplParams)
        { # support for old ABI dumps
            @TParams = separate_params($TmplParams, 0);
        }
        my $MangledNS = "";
        my ($SubNS, $SName) = get_sub_ns($ShortName);
        if($SubNS) {
            $MangledNS .= mangle_ns($SubNS, $LibVersion, $Repl);
        }
        $MangledNS .= length($SName).$SName;
        if(@TParams) {
            add_substitution($MangledNS, $Repl, 0);
        }
        $Mangled .= "N".$MangledNS;
        if(@TParams)
        { # templates
            $Mangled .= "I";
            foreach my $TParam (@TParams) {
                $Mangled .= mangle_template_param($TParam, $LibVersion, $Repl);
            }
            $Mangled .= "E";
        }
        $Mangled .= "E";
    }
    elsif($BaseType{"Type"}=~/(FuncPtr|MethodPtr)/)
    {
        if($BaseType{"Type"} eq "MethodPtr") {
            $Mangled .= "M".mangle_param($BaseType{"Class"}, $LibVersion, $Repl)."F";
        }
        else {
            $Mangled .= "PF";
        }
        $Mangled .= mangle_param($BaseType{"Return"}, $LibVersion, $Repl);
        my @Params = keys(%{$BaseType{"Param"}});
        foreach my $Num (sort {int($a)<=>int($b)} @Params) {
            $Mangled .= mangle_param($BaseType{"Param"}{$Num}{"type"}, $LibVersion, $Repl);
        }
        if(not @Params) {
            $Mangled .= "v";
        }
        $Mangled .= "E";
    }
    elsif($BaseType{"Type"} eq "FieldPtr")
    {
        $Mangled .= "M".mangle_param($BaseType{"Class"}, $LibVersion, $Repl);
        $Mangled .= mangle_param($BaseType{"Return"}, $LibVersion, $Repl);
    }
    $Mangled = $MPrefix.$Mangled;# add prefix (RPK)
    if(my $Optimized = write_substitution($Mangled, \%ReplCopy))
    {
        if($Mangled eq $Optimized)
        {
            if($ShortName!~/::/)
            { # remove "N ... E"
                if($MPrefix) {
                    $Mangled=~s/\A($MPrefix)N(.+)E\Z/$1$2/g;
                }
                else {
                    $Mangled=~s/\AN(.+)E\Z/$1/g;
                }
            }
        }
        else {
            $Mangled = $Optimized;
        }
    }
    add_substitution($Mangled, $Repl, 1);
    return $Mangled;
}

sub mangle_template_param($$$)
{ # types + literals
    my ($TParam, $LibVersion, $Repl) = @_;
    if(my $TPTid = $TName_Tid{$LibVersion}{$TParam}) {
        return mangle_param($TPTid, $LibVersion, $Repl);
    }
    elsif($TParam=~/\A(\d+)(\w+)\Z/)
    { # class_name<1u>::method(...)
        return "L".$IntrinsicMangling{$ConstantSuffixR{$2}}.$1."E";
    }
    elsif($TParam=~/\A\(([\w ]+)\)(\d+)\Z/)
    { # class_name<(signed char)1>::method(...)
        return "L".$IntrinsicMangling{$1}.$2."E";
    }
    elsif($TParam eq "true")
    { # class_name<true>::method(...)
        return "Lb1E";
    }
    elsif($TParam eq "false")
    { # class_name<true>::method(...)
        return "Lb0E";
    }
    else { # internal error
        return length($TParam).$TParam;
    }
}

sub add_substitution($$$)
{
    my ($Value, $Repl, $Rec) = @_;
    if($Rec)
    { # subtypes
        my @Subs = ($Value);
        while($Value=~s/\A(R|P|K)//) {
            push(@Subs, $Value);
        }
        foreach (reverse(@Subs)) {
            add_substitution($_, $Repl, 0);
        }
        return;
    }
    return if($Value=~/\AS(\d*)_\Z/);
    $Value=~s/\AN(.+)E\Z/$1/g;
    return if(defined $Repl->{$Value});
    return if(length($Value)<=1);
    return if($StdcxxMangling{$Value});
    # check for duplicates
    my $Base = $Value;
    foreach my $Type (sort {$Repl->{$a}<=>$Repl->{$b}} sort keys(%{$Repl}))
    {
        my $Num = $Repl->{$Type};
        my $Replace = macro_mangle($Num);
        $Base=~s/\Q$Replace\E/$Type/;
    }
    if(my $OldNum = $Repl->{$Base})
    {
        $Repl->{$Value} = $OldNum;
        return;
    }
    my @Repls = sort {$b<=>$a} values(%{$Repl});
    if(@Repls) {
        $Repl->{$Value} = $Repls[0]+1;
    }
    else {
        $Repl->{$Value} = -1;
    }
    # register duplicates
    # upward
    my $Base = $Value;
    foreach my $Type (sort {$Repl->{$a}<=>$Repl->{$b}} sort keys(%{$Repl}))
    {
        next if($Base eq $Type);
        my $Num = $Repl->{$Type};
        my $Replace = macro_mangle($Num);
        $Base=~s/\Q$Type\E/$Replace/;
        $Repl->{$Base} = $Repl->{$Value};
    }
}

sub macro_mangle($)
{
    my $Num = $_[0];
    if($Num==-1) {
        return "S_";
    }
    else
    {
        my $Code = "";
        if($Num<10)
        { # S0_, S1_, S2_, ...
            $Code = $Num;
        }
        elsif($Num>=10 and $Num<=35)
        { # SA_, SB_, SC_, ...
            $Code = chr(55+$Num);
        }
        else
        { # S10_, S11_, S12_
            $Code = $Num-26; # 26 is length of english alphabet
        }
        return "S".$Code."_";
    }
}

sub write_stdcxx_substitution($)
{
    my $Mangled = $_[0];
    if($StdcxxMangling{$Mangled}) {
        return $StdcxxMangling{$Mangled};
    }
    else
    {
        my @Repls = keys(%StdcxxMangling);
        @Repls = sort {length($b)<=>length($a)} sort {$b cmp $a} @Repls;
        foreach my $MangledType (@Repls)
        {
            my $Replace = $StdcxxMangling{$MangledType};
            #if($Mangled!~/$Replace/) {
                $Mangled=~s/N\Q$MangledType\EE/$Replace/g;
                $Mangled=~s/\Q$MangledType\E/$Replace/g;
            #}
        }
    }
    return $Mangled;
}

sub write_substitution($$)
{
    my ($Mangled, $Repl) = @_;
    if(defined $Repl->{$Mangled}
    and my $MnglNum = $Repl->{$Mangled}) {
        $Mangled = macro_mangle($MnglNum);
    }
    else
    {
        my @Repls = keys(%{$Repl});
        #@Repls = sort {$Repl->{$a}<=>$Repl->{$b}} @Repls;
        # FIXME: how to apply replacements? by num or by pos
        @Repls = sort {length($b)<=>length($a)} sort {$b cmp $a} @Repls;
        foreach my $MangledType (@Repls)
        {
            my $Replace = macro_mangle($Repl->{$MangledType});
            if($Mangled!~/$Replace/) {
                $Mangled=~s/N\Q$MangledType\EE/$Replace/g;
                $Mangled=~s/\Q$MangledType\E/$Replace/g;
            }
        }
    }
    return $Mangled;
}

sub delete_keywords($)
{
    my $TypeName = $_[0];
    $TypeName=~s/(\W|\A)(enum |struct |union |class )/$1/g;
    return $TypeName;
}

my %Intrinsic_Keywords = map {$_=>1} (
    "true",
    "false",
    "_Bool",
    "_Complex",
    "const",
    "int",
    "long",
    "void",
    "short",
    "float",
    "volatile",
    "restrict",
    "unsigned",
    "signed",
    "char",
    "double",
    "class",
    "struct",
    "union",
    "enum"
);

sub uncover_typedefs($$)
{
    my ($TypeName, $LibVersion) = @_;
    return "" if(not $TypeName);
    if(defined $Cache{"uncover_typedefs"}{$LibVersion}{$TypeName}) {
        return $Cache{"uncover_typedefs"}{$LibVersion}{$TypeName};
    }
    my ($TypeName_New, $TypeName_Pre) = (formatName($TypeName), "");
    while($TypeName_New ne $TypeName_Pre)
    {
        $TypeName_Pre = $TypeName_New;
        my $TypeName_Copy = $TypeName_New;
        my %Words = ();
        while($TypeName_Copy=~s/(\W|\A)([a-z_][\w:]*)(\W|\Z)//io)
        {
            my $Word = $2;
            next if(not $Word or $Intrinsic_Keywords{$Word});
            $Words{$Word} = 1;
        }
        foreach my $Word (keys(%Words))
        {
            my $BaseType_Name = $Typedef_BaseName{$LibVersion}{$Word};
            next if(not $BaseType_Name);
            next if($TypeName_New=~/(\A|\W)(struct|union|enum)\s\Q$Word\E(\W|\Z)/);
            if($BaseType_Name=~/\([\*]+\)/)
            { # FuncPtr
                if($TypeName_New=~/\Q$Word\E(.*)\Z/)
                {
                    my $Type_Suffix = $1;
                    $TypeName_New = $BaseType_Name;
                    if($TypeName_New=~s/\(([\*]+)\)/($1 $Type_Suffix)/) {
                        $TypeName_New = formatName($TypeName_New);
                    }
                }
            }
            else
            {
                if($TypeName_New=~s/(\W|\A)\Q$Word\E(\W|\Z)/$1$BaseType_Name$2/g) {
                    $TypeName_New = formatName($TypeName_New);
                }
            }
        }
    }
    return ($Cache{"uncover_typedefs"}{$LibVersion}{$TypeName} = $TypeName_New);
}

sub isInternal($)
{
    my $FuncInfoId = $_[0];
    my $FuncInfo = $LibInfo{$Version}{"info"}{$_[0]};
    return 0 if($FuncInfo!~/mngl[ ]*:[ ]*@(\d+) /);
    my $FuncMnglNameInfoId = $1;
    return ($LibInfo{$Version}{"info"}{$FuncMnglNameInfoId}=~/\*[ ]*INTERNAL[ ]*\*/);
}

sub set_Class_And_Namespace($)
{
    my $InfoId = $_[0];
    my $FuncInfo = $LibInfo{$Version}{"info"}{$InfoId};
    if($FuncInfo=~/scpe[ ]*:[ ]*@(\d+) /)
    {
        my $NameSpaceInfoId = $1;
        if($LibInfo{$Version}{"info_type"}{$NameSpaceInfoId} eq "namespace_decl") {
            $SymbolInfo{$Version}{$InfoId}{"NameSpace"} = getNameSpace($InfoId);
        }
        elsif($LibInfo{$Version}{"info_type"}{$NameSpaceInfoId} eq "record_type") {
            $SymbolInfo{$Version}{$InfoId}{"Class"} = $NameSpaceInfoId;
        }
    }
    if($SymbolInfo{$Version}{$InfoId}{"Class"}
    or $SymbolInfo{$Version}{$InfoId}{"NameSpace"})
    { # identify language
        setLanguage($Version, "C++");
    }
}

sub debugType($$)
{
    my ($Tid, $LibVersion) = @_;
    my %Type = get_Type($Tid_TDid{$LibVersion}{$Tid}, $Tid, $LibVersion);
    printMsg("INFO", Dumper(\%Type));
}

sub debugMangling($)
{
    my $LibVersion = $_[0];
    my %Mangled = ();
    foreach my $InfoId (keys(%{$SymbolInfo{$LibVersion}}))
    {
        if(my $Mngl = $SymbolInfo{$LibVersion}{$InfoId}{"MnglName"})
        {
            if($Mngl=~/\A(_Z|\?)/) {
                $Mangled{$Mngl}=$InfoId;
            }
        }
    }
    translateSymbols(keys(%Mangled), $LibVersion);
    foreach my $Mngl (keys(%Mangled))
    {
        my $Unmngl1 = modelUnmangled($Mangled{$Mngl}, "GCC");
        my $Unmngl2 = $tr_name{$Mngl};
        if($Unmngl1 ne $Unmngl2) {
            printMsg("INFO", "INCORRECT MANGLING:\n  $Mngl\n  $Unmngl1\n  $Unmngl2\n");
        }
    }
}

sub linkSymbol($)
{ # link symbols from shared libraries
  # with the symbols from header files
    my $InfoId = $_[0];
    if($SymbolInfo{$Version}{$InfoId}{"Lang"} eq "C")
    { # extern "C"
        return $SymbolInfo{$Version}{$InfoId}{"ShortName"};
    }
    # try to mangle symbol
    if((not check_gcc_version($GCC_PATH, "4") and $SymbolInfo{$Version}{$InfoId}{"Class"})
    or (check_gcc_version($GCC_PATH, "4") and not $SymbolInfo{$Version}{$InfoId}{"Class"}))
    { # 1. GCC 3.x doesn't mangle class methods names in the TU dump (only functions and global data)
      # 2. GCC 4.x doesn't mangle C++ functions in the TU dump (only class methods) except extern "C" functions
        if($CheckHeadersOnly)
        {
            if(my $Mangled = mangle_symbol($InfoId, $Version, "GCC")) {
                return $Mangled;
            }
        }
        else
        {
            if(my $Mangled = $mangled_name_gcc{modelUnmangled($InfoId, "GCC")}) {
                return correct_incharge($InfoId, $Version, $Mangled);
            }
        }
    }
    return "";
}

sub setLanguage($$)
{
    my ($LibVersion, $Lang) = @_;
    if(not $UserLang) {
        $COMMON_LANGUAGE{$LibVersion} = $Lang;
    }
}

sub getSymbolInfo($)
{
    my $FuncInfoId = $_[0];
    return if(isInternal($FuncInfoId));
    ($SymbolInfo{$Version}{$FuncInfoId}{"Header"}, $SymbolInfo{$Version}{$FuncInfoId}{"Line"}) = getLocation($FuncInfoId);
    if(not $SymbolInfo{$Version}{$FuncInfoId}{"Header"}
    or isBuiltIn($SymbolInfo{$Version}{$FuncInfoId}{"Header"})) {
        delete($SymbolInfo{$Version}{$FuncInfoId});
        return;
    }
    setFuncAccess($FuncInfoId);
    setFuncKind($FuncInfoId);
    if($SymbolInfo{$Version}{$FuncInfoId}{"PseudoTemplate"}) {
        delete($SymbolInfo{$Version}{$FuncInfoId});
        return;
    }
    $SymbolInfo{$Version}{$FuncInfoId}{"Type"} = getFuncType($FuncInfoId);
    $SymbolInfo{$Version}{$FuncInfoId}{"Return"} = getFuncReturn($FuncInfoId);
    if(my $AddedTid = $MissedTypedef{$Version}{$SymbolInfo{$Version}{$FuncInfoId}{"Return"}}{"Tid"}) {
        $SymbolInfo{$Version}{$FuncInfoId}{"Return"} = $AddedTid;
    }
    if(not $SymbolInfo{$Version}{$FuncInfoId}{"Return"}) {
        delete($SymbolInfo{$Version}{$FuncInfoId}{"Return"});
    }
    $SymbolInfo{$Version}{$FuncInfoId}{"ShortName"} = getFuncShortName(getFuncOrig($FuncInfoId));
    if($SymbolInfo{$Version}{$FuncInfoId}{"ShortName"}=~/\._/) {
        delete($SymbolInfo{$Version}{$FuncInfoId});
        return;
    }
    if(defined $TemplateInstance_Func{$Version}{$FuncInfoId})
    {
        my @TParams = getTParams_Func($FuncInfoId);
        if(not @TParams) {
            delete($SymbolInfo{$Version}{$FuncInfoId});
            return;
        }
        my $PrmsInLine = join(", ", @TParams);
        $SymbolInfo{$Version}{$FuncInfoId}{"ShortName"} .= "<".$PrmsInLine.">";
        $SymbolInfo{$Version}{$FuncInfoId}{"ShortName"} = formatName($SymbolInfo{$Version}{$FuncInfoId}{"ShortName"});
    }
    else
    { # support for GCC 3.4
        $SymbolInfo{$Version}{$FuncInfoId}{"ShortName"}=~s/<.+>\Z//;
    }
    $SymbolInfo{$Version}{$FuncInfoId}{"MnglName"} = getFuncMnglName($FuncInfoId);
    if($SymbolInfo{$Version}{$FuncInfoId}{"MnglName"}
    and $SymbolInfo{$Version}{$FuncInfoId}{"MnglName"}!~/\A_Z/)
    {
        delete($SymbolInfo{$Version}{$FuncInfoId});
        return;
    }
    if($SymbolInfo{$FuncInfoId}{"MnglName"} and not $STDCXX_TESTING)
    { # stdc++ interfaces
        if($SymbolInfo{$Version}{$FuncInfoId}{"MnglName"}=~/\A(_ZS|_ZNS|_ZNKS)/) {
            delete($SymbolInfo{$Version}{$FuncInfoId});
            return;
        }
    }
    if(not $SymbolInfo{$Version}{$FuncInfoId}{"Destructor"})
    { # destructors have an empty parameter list
        my $Skip = setFuncParams($FuncInfoId);
        if($CheckHeadersOnly and $Skip)
        { # skip template symbols that cannot be
          # filtered without access to the library
            delete($SymbolInfo{$Version}{$FuncInfoId});
            return;
        }
    }
    set_Class_And_Namespace($FuncInfoId);
    if(not $CheckHeadersOnly and $SymbolInfo{$Version}{$FuncInfoId}{"Type"} eq "Function"
    and not $SymbolInfo{$Version}{$FuncInfoId}{"Class"}
    and link_symbol($SymbolInfo{$Version}{$FuncInfoId}{"ShortName"}, $Version, "-Deps"))
    { # functions (C++): not mangled in library, but are mangled in TU dump
        if(not $SymbolInfo{$Version}{$FuncInfoId}{"MnglName"}
        or not link_symbol($SymbolInfo{$Version}{$FuncInfoId}{"MnglName"}, $Version, "-Deps")) {
            $SymbolInfo{$Version}{$FuncInfoId}{"MnglName"} = $SymbolInfo{$Version}{$FuncInfoId}{"ShortName"};
        }
    }
    if($LibInfo{$Version}{"info"}{$FuncInfoId}=~/ lang:[ ]*C /i) {
        $SymbolInfo{$Version}{$FuncInfoId}{"Lang"} = "C";
    }
    if($UserLang eq "C")
    { # --lang=C option
        $SymbolInfo{$Version}{$FuncInfoId}{"MnglName"} = $SymbolInfo{$Version}{$FuncInfoId}{"ShortName"};
    }
    if($COMMON_LANGUAGE{$Version} eq "C++")
    { # correct mangled & short names
        if($SymbolInfo{$Version}{$FuncInfoId}{"ShortName"}=~/\A__(comp|base|deleting)_(c|d)tor\Z/)
        { # support for old GCC versions: reconstruct real names for constructors and destructors
            $SymbolInfo{$Version}{$FuncInfoId}{"ShortName"}=getNameByInfo(getTypeDeclId($SymbolInfo{$Version}{$FuncInfoId}{"Class"}));
            $SymbolInfo{$Version}{$FuncInfoId}{"ShortName"}=~s/<.+>\Z//;
        }
        if(not $SymbolInfo{$Version}{$FuncInfoId}{"MnglName"})
        { # try to mangle symbol (link with libraries)
            if(my $Mangled = linkSymbol($FuncInfoId)) {
                $SymbolInfo{$Version}{$FuncInfoId}{"MnglName"} = $Mangled;
            }
        }
        if($OStarget eq "windows")
        { # link MS C++ symbols from library with GCC symbols from headers
            if(my $Mangled = $mangled_name{$Version}{modelUnmangled($FuncInfoId, "MSVC")})
            { # exported symbols
                $SymbolInfo{$Version}{$FuncInfoId}{"MnglName"} = $Mangled;
            }
            elsif(my $Mangled = mangle_symbol($FuncInfoId, $Version, "MSVC"))
            { # pure virtual symbols
                $SymbolInfo{$Version}{$FuncInfoId}{"MnglName"} = $Mangled;
            }
        }
    }
    if(not $SymbolInfo{$Version}{$FuncInfoId}{"MnglName"})
    { # can't detect symbol name
        delete($SymbolInfo{$Version}{$FuncInfoId});
        return;
    }
    if(getFuncSpec($FuncInfoId) eq "Virt")
    { # virtual methods
        $SymbolInfo{$Version}{$FuncInfoId}{"Virt"} = 1;
    }
    if(getFuncSpec($FuncInfoId) eq "PureVirt")
    { # pure virtual methods
        $SymbolInfo{$Version}{$FuncInfoId}{"PureVirt"} = 1;
    }
    if(isInline($FuncInfoId)) {
        $SymbolInfo{$Version}{$FuncInfoId}{"InLine"} = 1;
    }
    if($SymbolInfo{$Version}{$FuncInfoId}{"Constructor"}
    and my $ClassId = $SymbolInfo{$Version}{$FuncInfoId}{"Class"})
    {
        if(not $SymbolInfo{$Version}{$FuncInfoId}{"InLine"}
        and $LibInfo{$Version}{"info"}{$FuncInfoId}!~/ artificial /i)
        { # inline or auto-generated constructor
            delete($TypeInfo{$Version}{$Tid_TDid{$Version}{$ClassId}}{$ClassId}{"Copied"});
        }
    }
    if(not link_symbol($SymbolInfo{$Version}{$FuncInfoId}{"MnglName"}, $Version, "-Deps")
    and not $SymbolInfo{$Version}{$FuncInfoId}{"Virt"}
    and not $SymbolInfo{$Version}{$FuncInfoId}{"PureVirt"})
    { # removing src only and external non-virtual functions
      # non-virtual template instances going here
        if(not $CheckHeadersOnly) {
            delete($SymbolInfo{$Version}{$FuncInfoId});
            return;
        }
    }
    if($SymbolInfo{$Version}{$FuncInfoId}{"Type"} eq "Method"
    or $SymbolInfo{$Version}{$FuncInfoId}{"Constructor"}
    or $SymbolInfo{$Version}{$FuncInfoId}{"Destructor"}
    or $SymbolInfo{$Version}{$FuncInfoId}{"Class"})
    {
        if($SymbolInfo{$Version}{$FuncInfoId}{"MnglName"}!~/\A(_Z|\?)/) {
            delete($SymbolInfo{$Version}{$FuncInfoId});
            return;
        }
    }
    if($SymbolInfo{$Version}{$FuncInfoId}{"MnglName"})
    {
        if($MangledNames{$Version}{$SymbolInfo{$Version}{$FuncInfoId}{"MnglName"}})
        { # one instance for one mangled name only
            delete($SymbolInfo{$Version}{$FuncInfoId});
            return;
        }
        else {
            $MangledNames{$Version}{$SymbolInfo{$Version}{$FuncInfoId}{"MnglName"}} = 1;
        }
    }
    if($SymbolInfo{$Version}{$FuncInfoId}{"Constructor"}
    or $SymbolInfo{$Version}{$FuncInfoId}{"Destructor"}) {
        delete($SymbolInfo{$Version}{$FuncInfoId}{"Return"});
    }
    if($SymbolInfo{$Version}{$FuncInfoId}{"MnglName"}=~/\A(_Z|\?)/
    and $SymbolInfo{$Version}{$FuncInfoId}{"Class"})
    {
        if($SymbolInfo{$Version}{$FuncInfoId}{"Type"} eq "Function")
        { # static methods
            $SymbolInfo{$Version}{$FuncInfoId}{"Static"} = 1;
        }
    }
    if(getFuncLink($FuncInfoId) eq "Static") {
        $SymbolInfo{$Version}{$FuncInfoId}{"Static"} = 1;
    }
    if($SymbolInfo{$Version}{$FuncInfoId}{"MnglName"}=~/\A(_Z|\?)/
    and $tr_name{$SymbolInfo{$Version}{$FuncInfoId}{"MnglName"}}=~/\.\_\d/) {
        delete($SymbolInfo{$Version}{$FuncInfoId});
        return;
    }
    delete($SymbolInfo{$Version}{$FuncInfoId}{"Type"});
    if($SymbolInfo{$Version}{$FuncInfoId}{"MnglName"}=~/\A_ZN(V|)K/) {
        $SymbolInfo{$Version}{$FuncInfoId}{"Const"} = 1;
    }
    if($SymbolInfo{$Version}{$FuncInfoId}{"MnglName"}=~/\A_ZN(K|)V/) {
        $SymbolInfo{$Version}{$FuncInfoId}{"Volatile"} = 1;
    }
}

sub isInline($)
{ # "body: undefined" in the tree
  # -fkeep-inline-functions GCC option should be specified
    my $FuncInfo = $LibInfo{$Version}{"info"}{$_[0]};
    if($FuncInfo=~/ undefined /i) {
        return 0;
    }
    return 1;
}

sub getTypeId($)
{
    if($LibInfo{$Version}{"info"}{$_[0]}=~/type[ ]*:[ ]*@(\d+) /) {
        return $1;
    }
    else {
        return "";
    }
}

sub setTypeMemb($$)
{
    my ($TypeId, $TypeAttr) = @_;
    my $TypeType = $TypeAttr->{"Type"};
    my ($Position, $UnnamedPos) = (0, 0);
    if($TypeType eq "Enum")
    {
        my $TypeMembInfoId = getEnumMembInfoId($TypeId);
        while($TypeMembInfoId)
        {
            $TypeAttr->{"Memb"}{$Position}{"value"} = getEnumMembVal($TypeMembInfoId);
            my $MembName = getEnumMembName($TypeMembInfoId);
            $TypeAttr->{"Memb"}{$Position}{"name"} = getEnumMembName($TypeMembInfoId);
            $EnumMembName_Id{$Version}{getTreeAttr($TypeMembInfoId, "valu")} = ($TypeAttr->{"NameSpace"})?$TypeAttr->{"NameSpace"}."::".$MembName:$MembName;
            $TypeMembInfoId = getNextMembInfoId($TypeMembInfoId);
            $Position += 1;
        }
    }
    elsif($TypeType=~/\A(Struct|Class|Union)\Z/)
    {
        my $TypeMembInfoId = getStructMembInfoId($TypeId);
        while($TypeMembInfoId)
        {
            if($LibInfo{$Version}{"info_type"}{$TypeMembInfoId} ne "field_decl") {
                $TypeMembInfoId = getNextStructMembInfoId($TypeMembInfoId);
                next;
            }
            my $StructMembName = getStructMembName($TypeMembInfoId);
            if($StructMembName=~/_vptr\./)
            {# virtual tables
                $TypeMembInfoId = getNextStructMembInfoId($TypeMembInfoId);
                next;
            }
            if(not $StructMembName)
            { # unnamed fields
                if($TypeAttr->{"Name"}!~/_type_info_pseudo/)
                {
                    my $UnnamedTid = getTreeAttr($TypeMembInfoId, "type");
                    my $UnnamedTName = getNameByInfo(getTypeDeclId($UnnamedTid));
                    if(isAnon($UnnamedTName))
                    { # rename unnamed fields to unnamed0, unnamed1, ...
                        $StructMembName = "unnamed".($UnnamedPos++);
                    }
                }
            }
            if(not $StructMembName)
            { # unnamed fields and base classes
                $TypeMembInfoId = getNextStructMembInfoId($TypeMembInfoId);
                next;
            }
            my $MembTypeId = getTreeAttr($TypeMembInfoId, "type");
            if(my $AddedTid = $MissedTypedef{$Version}{$MembTypeId}{"Tid"}) {
                $MembTypeId = $AddedTid;
            }
            $TypeAttr->{"Memb"}{$Position}{"type"} = $MembTypeId;
            $TypeAttr->{"Memb"}{$Position}{"name"} = $StructMembName;
            if((my $Access = getTreeAccess($TypeMembInfoId)) ne "public")
            {# marked only protected and private, public by default
                $TypeAttr->{"Memb"}{$Position}{"access"} = $Access;
            }
            if(my $BFSize = getStructMembBitFieldSize($TypeMembInfoId)) {
                $TypeAttr->{"Memb"}{$Position}{"bitfield"} = $BFSize;
            }
            else
            { # set alignment for non-bit fields
              # alignment for bitfields is always equal to 1 bit
                $TypeAttr->{"Memb"}{$Position}{"algn"} = getAlgn($TypeMembInfoId)/$BYTE_SIZE;
            }
            $TypeMembInfoId = getNextStructMembInfoId($TypeMembInfoId);
            $Position += 1;
        }
    }
}

sub setFuncParams($)
{
    my $FuncInfoId = $_[0];
    my $ParamInfoId = getFuncParamInfoId($FuncInfoId);
    my $FunctionType = getFuncType($FuncInfoId);
    if($FunctionType eq "Method")
    { # check type of "this" pointer
        my $ObjectTypeId = getFuncParamType($ParamInfoId);
        if(get_TypeName($ObjectTypeId, $Version)=~/(\A|\W)const(| volatile)\*const(\W|\Z)/) {
            $SymbolInfo{$Version}{$FuncInfoId}{"Const"} = 1;
        }
        if(get_TypeName($ObjectTypeId, $Version)=~/(\A|\W)volatile(\W|\Z)/) {
            $SymbolInfo{$Version}{$FuncInfoId}{"Volatile"} = 1;
        }
        $ParamInfoId = getNextElem($ParamInfoId);
    }
    my ($Position, $Vtt_Pos) = (0, -1);
    while($ParamInfoId)
    {
        my $ParamTypeId = getFuncParamType($ParamInfoId);
        my $ParamName = getFuncParamName($ParamInfoId);
        if($TypeInfo{$Version}{getTypeDeclId($ParamTypeId)}{$ParamTypeId}{"Name"} eq "void") {
            last;
        }
        if(my $AddedTid = $MissedTypedef{$Version}{$ParamTypeId}{"Tid"}) {
            $ParamTypeId = $AddedTid;
        }
        my $PType = get_TypeAttr($ParamTypeId, $Version, "Type");
        if(not $PType or $PType eq "Unknown") {
            return 1;
        }
        if($ParamName eq "__vtt_parm"
        and get_TypeName($ParamTypeId, $Version) eq "void const**")
        {
            $Vtt_Pos = $Position;
            $ParamInfoId = getNextElem($ParamInfoId);
            next;
        }
        $SymbolInfo{$Version}{$FuncInfoId}{"Param"}{$Position}{"type"} = $ParamTypeId;
        $SymbolInfo{$Version}{$FuncInfoId}{"Param"}{$Position}{"name"} = $ParamName;
        $SymbolInfo{$Version}{$FuncInfoId}{"Param"}{$Position}{"algn"} = getAlgn($ParamInfoId)/$BYTE_SIZE;
        if(not $SymbolInfo{$Version}{$FuncInfoId}{"Param"}{$Position}{"name"}) {
            $SymbolInfo{$Version}{$FuncInfoId}{"Param"}{$Position}{"name"} = "p".($Position+1);
        }
        if($LibInfo{$Version}{"info"}{$ParamInfoId}=~/spec:\s*register /)
        { # foo(register type arg)
            $SymbolInfo{$Version}{$FuncInfoId}{"Param"}{$Position}{"reg"} = 1;
        }
        $ParamInfoId = getNextElem($ParamInfoId);
        $Position += 1;
    }
    if(detect_nolimit_args($FuncInfoId, $Vtt_Pos)) {
        $SymbolInfo{$Version}{$FuncInfoId}{"Param"}{$Position}{"type"} = -1;
    }
    return 0;
}

sub detect_nolimit_args($$)
{
    my ($FuncInfoId, $Vtt_Pos) = @_;
    my $FuncTypeId = getFuncTypeId($FuncInfoId);
    my $ParamListElemId = getTreeAttr($FuncTypeId, "prms");
    if(getFuncType($FuncInfoId) eq "Method") {
        $ParamListElemId = getNextElem($ParamListElemId);
    }
    return 1 if(not $ParamListElemId);# foo(...)
    my $HaveVoid = 0;
    my $Position = 0;
    while($ParamListElemId)
    {
        if($Vtt_Pos!=-1 and $Position==$Vtt_Pos)
        {
            $Vtt_Pos=-1;
            $ParamListElemId = getNextElem($ParamListElemId);
            next;
        }
        my $ParamTypeId = getTreeAttr($ParamListElemId, "valu");
        if(my $PurpId = getTreeAttr($ParamListElemId, "purp"))
        {
            if($LibInfo{$Version}{"info_type"}{$PurpId} eq "integer_cst") {
                $SymbolInfo{$Version}{$FuncInfoId}{"Param"}{$Position}{"default"} = getTreeValue($PurpId);
            }
            elsif($LibInfo{$Version}{"info_type"}{$PurpId} eq "string_cst") {
                $SymbolInfo{$Version}{$FuncInfoId}{"Param"}{$Position}{"default"} = getNodeStrCst($PurpId);
            }
        }
        if($TypeInfo{$Version}{getTypeDeclId($ParamTypeId)}{$ParamTypeId}{"Name"} eq "void") {
            $HaveVoid = 1;
            last;
        }
        elsif(not defined $SymbolInfo{$Version}{$FuncInfoId}{"Param"}{$Position}{"type"})
        {
            $SymbolInfo{$Version}{$FuncInfoId}{"Param"}{$Position}{"type"} = $ParamTypeId;
            if(not $SymbolInfo{$Version}{$FuncInfoId}{"Param"}{$Position}{"name"}) {
                $SymbolInfo{$Version}{$FuncInfoId}{"Param"}{$Position}{"name"} = "p".($Position+1);
            }
        }
        $ParamListElemId = getNextElem($ParamListElemId);
        $Position += 1;
    }
    return ($Position>=1 and not $HaveVoid);
}

sub getTreeAttr($$)
{
    my $Attr = $_[1];
    if($LibInfo{$Version}{"info"}{$_[0]}=~/\Q$Attr\E\s*:\s*\@(\d+) /) {
        return $1;
    }
    return "";
}

sub getTreeValue($)
{
    if($LibInfo{$Version}{"info"}{$_[0]}=~/low[ ]*:[ ]*([^ ]+) /) {
        return $1;
    }
    return "";
}

sub getTreeAccess($)
{
    my $InfoId = $_[0];
    if($LibInfo{$Version}{"info"}{$InfoId}=~/accs[ ]*:[ ]*([a-zA-Z]+) /)
    {
        my $Access = $1;
        if($Access eq "prot") {
            return "protected";
        }
        elsif($Access eq "priv") {
            return "private";
        }
    }
    elsif($LibInfo{$Version}{"info"}{$InfoId}=~/ protected /)
    {# support for old GCC versions
        return "protected";
    }
    elsif($LibInfo{$Version}{"info"}{$InfoId}=~/ private /)
    {# support for old GCC versions
        return "private";
    }
    return "public";
}

sub setFuncAccess($)
{
    my $FuncInfoId = $_[0];
    my $Access = getTreeAccess($FuncInfoId);
    if($Access eq "protected") {
        $SymbolInfo{$Version}{$FuncInfoId}{"Protected"} = 1;
    }
    elsif($Access eq "private") {
        $SymbolInfo{$Version}{$FuncInfoId}{"Private"} = 1;
    }
}

sub setTypeAccess($$)
{
    my ($TypeId, $TypeAttr) = @_;
    my $Access = getTreeAccess($TypeId);
    if($Access eq "protected") {
        $TypeAttr->{"Protected"} = 1;
    }
    elsif($Access eq "private") {
        $TypeAttr->{"Private"} = 1;
    }
}

sub setFuncKind($)
{
    my $FuncInfoId = $_[0];
    if($LibInfo{$Version}{"info"}{$FuncInfoId}=~/pseudo tmpl/) {
        $SymbolInfo{$Version}{$FuncInfoId}{"PseudoTemplate"} = 1;
    }
    elsif($LibInfo{$Version}{"info"}{$FuncInfoId}=~/ constructor /) {
        $SymbolInfo{$Version}{$FuncInfoId}{"Constructor"} = 1;
    }
    elsif($LibInfo{$Version}{"info"}{$FuncInfoId}=~/ destructor /) {
        $SymbolInfo{$Version}{$FuncInfoId}{"Destructor"} = 1;
    }
}

sub getFuncSpec($)
{
    my $FuncInfoId = $_[0];
    my $FuncInfo = $LibInfo{$Version}{"info"}{$FuncInfoId};
    if($FuncInfo=~/spec[ ]*:[ ]*pure /) {
        return "PureVirt";
    }
    elsif($FuncInfo=~/spec[ ]*:[ ]*virt /) {
        return "Virt";
    }
    elsif($FuncInfo=~/ pure\s+virtual /)
    {# support for old GCC versions
        return "PureVirt";
    }
    elsif($FuncInfo=~/ virtual /)
    {# support for old GCC versions
        return "Virt";
    }
    return "";
}

sub getFuncClass($)
{
    my $FuncInfoId = $_[0];
    my $FuncInfo = $LibInfo{$Version}{"info"}{$FuncInfoId};
    if($FuncInfo=~/scpe[ ]*:[ ]*@(\d+) /) {
        return $1;
    }
    else {
        return "";
    }
}

sub getFuncLink($)
{
    my $FuncInfoId = $_[0];
    my $FuncInfo = $LibInfo{$Version}{"info"}{$FuncInfoId};
    if($FuncInfo=~/link[ ]*:[ ]*static /) {
        return "Static";
    }
    else {
        if($FuncInfo=~/link[ ]*:[ ]*([a-zA-Z]+) /) {
            return $1;
        }
        else {
            return "";
        }
    }
}

sub getNextElem($)
{
    my $FuncInfoId = $_[0];
    my $FuncInfo = $LibInfo{$Version}{"info"}{$FuncInfoId};
    if($FuncInfo=~/(chan|chain)[ ]*:[ ]*@(\d+) /) {
        return $2;
    }
    else {
        return "";
    }
}

sub getFuncParamInfoId($)
{
    my $FuncInfoId = $_[0];
    my $FuncInfo = $LibInfo{$Version}{"info"}{$FuncInfoId};
    if($FuncInfo=~/args[ ]*:[ ]*@(\d+) /) {
        return $1;
    }
    else {
        return "";
    }
}

sub getFuncParamType($)
{
    my $ParamInfoId = $_[0];
    my $ParamInfo = $LibInfo{$Version}{"info"}{$ParamInfoId};
    if($ParamInfo=~/type[ ]*:[ ]*@(\d+) /) {
        return $1;
    }
    else {
        return "";
    }
}

sub getFuncParamName($)
{
    my $ParamInfoId = $_[0];
    my $ParamInfo = $LibInfo{$Version}{"info"}{$ParamInfoId};
    if($ParamInfo=~/name[ ]*:[ ]*@(\d+) /) {
        return getTreeStr($1);
    }
    return "";
}

sub getEnumMembInfoId($)
{
    if($LibInfo{$Version}{"info"}{$_[0]}=~/csts[ ]*:[ ]*@(\d+) /) {
        return $1;
    }
    else {
        return "";
    }
}

sub getStructMembInfoId($)
{
    if($LibInfo{$Version}{"info"}{$_[0]}=~/flds[ ]*:[ ]*@(\d+) /) {
        return $1;
    }
    else {
        return "";
    }
}

sub get_IntNameSpace($$)
{
    my ($Interface, $LibVersion) = @_;
    return "" if(not $Interface or not $LibVersion);
    if(defined $Cache{"get_IntNameSpace"}{$Interface}{$LibVersion}) {
        return $Cache{"get_IntNameSpace"}{$Interface}{$LibVersion};
    }
    my $Signature = get_Signature($Interface, $LibVersion);
    if($Signature=~/\:\:/)
    {
        my $FounNameSpace = 0;
        foreach my $NameSpace (sort {get_depth($b)<=>get_depth($a)} keys(%{$NestedNameSpaces{$LibVersion}}))
        {
            if($Signature=~/(\A|\s+for\s+)\Q$NameSpace\E\:\:/) {
                return ($Cache{"get_IntNameSpace"}{$Interface}{$LibVersion} = $NameSpace);
            }
        }
    }
    else {
        return ($Cache{"get_IntNameSpace"}{$Interface}{$LibVersion} = "");
    }
}

sub parse_TypeNameSpace($$)
{
    my ($TypeName, $LibVersion) = @_;
    return "" if(not $TypeName or not $LibVersion);
    if(defined $Cache{"parse_TypeNameSpace"}{$TypeName}{$LibVersion}) {
        return $Cache{"parse_TypeNameSpace"}{$TypeName}{$LibVersion};
    }
    if($TypeName=~/\:\:/)
    {
        my $FounNameSpace = 0;
        foreach my $NameSpace (sort {get_depth($b)<=>get_depth($a)} keys(%{$NestedNameSpaces{$LibVersion}}))
        {
            if($TypeName=~/\A\Q$NameSpace\E\:\:/) {
                return ($Cache{"parse_TypeNameSpace"}{$TypeName}{$LibVersion} = $NameSpace);
            }
        }
    }
    else {
        return ($Cache{"parse_TypeNameSpace"}{$TypeName}{$LibVersion} = "");
    }
}

sub getNameSpace($)
{
    my $TypeInfoId = $_[0];
    my $NameSpaceInfoId = getTreeAttr($TypeInfoId, "scpe");
    return "" if(not $NameSpaceInfoId);
    if($LibInfo{$Version}{"info_type"}{$NameSpaceInfoId} eq "namespace_decl")
    {
        my $NameSpaceInfo = $LibInfo{$Version}{"info"}{$NameSpaceInfoId};
        if($NameSpaceInfo=~/name[ ]*:[ ]*@(\d+) /)
        {
            my $NameSpace = getTreeStr($1);
            return "" if($NameSpace eq "::");
            if(my $BaseNameSpace = getNameSpace($NameSpaceInfoId)) {
                $NameSpace = $BaseNameSpace."::".$NameSpace;
            }
            $NestedNameSpaces{$Version}{$NameSpace} = 1;
            return $NameSpace;
        }
        else {
            return "";
        }
    }
    elsif($LibInfo{$Version}{"info_type"}{$NameSpaceInfoId} eq "record_type")
    {# inside data type
        my ($Name, $NameNS) = getTrivialName(getTypeDeclId($NameSpaceInfoId), $NameSpaceInfoId);
        return $Name;
    }
    else {
        return "";
    }
}

sub getNameSpaceId($)
{
    if($LibInfo{$Version}{"info"}{$_[0]}=~/scpe[ ]*:[ ]*\@(\d+)/) {
        return $1;
    }
    else {
        return "";
    }
}

sub getEnumMembName($)
{
    if($LibInfo{$Version}{"info"}{$_[0]}=~/purp[ ]*:[ ]*\@(\d+)/) {
        return getTreeStr($1);
    }
    else {
        return "";
    }
}

sub getStructMembName($)
{
    if($LibInfo{$Version}{"info"}{$_[0]}=~/name[ ]*:[ ]*\@(\d+)/) {
        return getTreeStr($1);
    }
    else {
        return "";
    }
}

sub getEnumMembVal($)
{
    if($LibInfo{$Version}{"info"}{$_[0]}=~/valu[ ]*:[ ]*\@(\d+)/)
    {
        if($LibInfo{$Version}{"info"}{$1}=~/cnst[ ]*:[ ]*\@(\d+)/)
        {# in newer versions of GCC the value is in the "const_decl->cnst" node
            return getTreeValue($1);
        }
        else
        {# some old versions of GCC (3.3) have the value in the "integer_cst" node
            return getTreeValue($1);
        }
    }
    return "";
}

sub getSize($)
{
    if($LibInfo{$Version}{"info"}{$_[0]}=~/size[ ]*:[ ]*\@(\d+)/) {
        return getTreeValue($1);
    }
    else {
        return 0;
    }
}

sub getAlgn($)
{
    if($LibInfo{$Version}{"info"}{$_[0]}=~/algn[ ]*:[ ]*(\d+) /) {
        return $1;
    }
    else {
        return "";
    }
}

sub getStructMembBitFieldSize($)
{
    if($LibInfo{$Version}{"info"}{$_[0]}=~/ bitfield /) {
        return getSize($_[0]);
    }
    else {
        return 0;
    }
}

sub getNextMembInfoId($)
{
    if($LibInfo{$Version}{"info"}{$_[0]}=~/(chan|chain)[ ]*:[ ]*@(\d+) /) {
        return $2;
    }
    else {
        return "";
    }
}

sub getNextStructMembInfoId($)
{
    if($LibInfo{$Version}{"info"}{$_[0]}=~/(chan|chain)[ ]*:[ ]*@(\d+) /) {
        return $2;
    }
    else {
        return "";
    }
}

sub fieldHasName($)
{
    my $TypeMembInfoId = $_[0];
    if($LibInfo{$Version}{"info_type"}{$TypeMembInfoId} eq "field_decl")
    {
        if($LibInfo{$Version}{"info"}{$TypeMembInfoId}=~/name[ ]*:[ ]*@(\d+) /) {
            return $1;
        }
        else {
            return "";
        }
    }
    else {
        return 0;
    }
}

sub register_header($$)
{ # input: header absolute path, relative path or name
    my ($Header, $LibVersion) = @_;
    return "" if(not $Header);
    if(is_abs($Header) and not -f $Header) {
        exitStatus("Access_Error", "can't access \'$Header\'");
    }
    return "" if(skip_header($Header, $LibVersion));
    my $Header_Path = identify_header($Header, $LibVersion);
    return "" if(not $Header_Path);
    detect_header_includes($Header_Path, $LibVersion);
    if(my $RHeader_Path = $Header_ErrorRedirect{$LibVersion}{$Header_Path})
    {
        return "" if(skip_header($RHeader_Path, $LibVersion));
        $Header_Path = $RHeader_Path;
        return "" if($Registered_Headers{$LibVersion}{$Header_Path}{"Identity"});
    }
    elsif($Header_ShouldNotBeUsed{$LibVersion}{$Header_Path}) {
        return "";
    }
    $Registered_Headers{$LibVersion}{$Header_Path}{"Identity"} = get_filename($Header_Path);
    $HeaderName_Paths{$LibVersion}{get_filename($Header_Path)}{$Header_Path} = 1;
    if(($Header=~/\.(\w+)\Z/ and $1 ne "h")
    or $Header!~/\.(\w+)\Z/)
    { # hpp, hh
        setLanguage($LibVersion, "C++");
    }
    if($CheckHeadersOnly
    and $Header=~/(\A|\/)c\+\+(\/|\Z)/)
    { # /usr/include/c++/4.6.1/...
        $STDCXX_TESTING = 1;
    }
    return $Header_Path;
}

sub register_directory($$$)
{
    my ($Dir, $WithDeps, $LibVersion) = @_;
    $Dir=~s/[\/\\]+\Z//g;
    return if(not $LibVersion or not $Dir or not -d $Dir);
    return if(skip_header($Dir, $LibVersion));
    $Dir = get_abs_path($Dir);
    my $Mode = "All";
    if($WithDeps) {
        if($RegisteredDirs{$LibVersion}{$Dir}{1}) {
            return;
        }
        elsif($RegisteredDirs{$LibVersion}{$Dir}{0}) {
            $Mode = "DepsOnly";
        }
    }
    else {
        if($RegisteredDirs{$LibVersion}{$Dir}{1}
        or $RegisteredDirs{$LibVersion}{$Dir}{0}) {
            return;
        }
    }
    $Header_Dependency{$LibVersion}{$Dir} = 1;
    $RegisteredDirs{$LibVersion}{$Dir}{$WithDeps} = 1;
    if($Mode eq "DepsOnly")
    {
        foreach my $Path (cmd_find($Dir,"d","","")) {
            $Header_Dependency{$LibVersion}{$Path} = 1;
        }
        return;
    }
    foreach my $Path (sort {length($b)<=>length($a)} cmd_find($Dir,"f","",""))
    {
        if($WithDeps)
        { 
            my $SubDir = $Path;
            while(($SubDir = get_dirname($SubDir)) ne $Dir)
            { # register all sub directories
                $Header_Dependency{$LibVersion}{$SubDir} = 1;
            }
        }
        next if(is_not_header($Path));
        next if(ignore_path($Path));
        next if(skip_header($Path, $LibVersion));
        # Neighbors
        foreach my $Part (get_path_prefixes($Path)) {
            $Include_Neighbors{$LibVersion}{$Part} = $Path;
        }
    }
    if(get_filename($Dir) eq "include")
    { # search for "lib/include/" directory
        my $LibDir = $Dir;
        if($LibDir=~s/([\/\\])include\Z/$1lib/g and -d $LibDir) {
            register_directory($LibDir, $WithDeps, $LibVersion);
        }
    }
}

sub parse_redirect($$$)
{
    my ($Content, $Path, $LibVersion) = @_;
    if(defined $Cache{"parse_redirect"}{$LibVersion}{$Path}) {
        return $Cache{"parse_redirect"}{$LibVersion}{$Path};
    }
    return "" if(not $Content);
    my @Errors = ();
    while($Content=~s/#\s*error\s+([^\n]+?)\s*(\n|\Z)//) {
        push(@Errors, $1);
    }
    my $Redirect = "";
    foreach (@Errors)
    {
        s/\s{2,}/ /g;
        if(/(only|must\ include
        |update\ to\ include
        |replaced\ with
        |replaced\ by|renamed\ to
        |is\ in|use)\ (<[^<>]+>|[\w\-\/\\]+\.($HEADER_EXT))/ix)
        {
            $Redirect = $2;
            last;
        }
        elsif(/(include|use|is\ in)\ (<[^<>]+>|[\w\-\/\\]+\.($HEADER_EXT))\ instead/i)
        {
            $Redirect = $2;
            last;
        }
        elsif(/this\ header\ should\ not\ be\ used
         |programs\ should\ not\ directly\ include
         |you\ should\ not\ (include|be\ (including|using)\ this\ (file|header))
         |is\ not\ supported\ API\ for\ general\ use
         |do\ not\ use
         |should\ not\ be\ used
         |cannot\ be\ included\ directly/ix and not /\ from\ /i) {
            $Header_ShouldNotBeUsed{$LibVersion}{$Path} = 1;
        }
    }
    $Redirect=~s/\A<//g;
    $Redirect=~s/>\Z//g;
    return ($Cache{"parse_redirect"}{$LibVersion}{$Path} = $Redirect);
}

sub parse_includes($$)
{
    my ($Content, $Path) = @_;
    my %Includes = ();
    while($Content=~s/#([ \t]*)(include|include_next|import)([ \t]*)(<|")([^<>"]+)(>|")//)
    {# C/C++: include, Objective C/C++: import directive
        my ($Header, $Method) = ($5, $4);
        $Header = path_format($Header, $OSgroup);
        if(($Method eq "\"" and -e joinPath(get_dirname($Path), $Header))
        or is_abs($Header)) {
        # include "path/header.h" that doesn't exist is equal to include <path/header.h>
            $Includes{$Header} = -1;
        }
        else {
            $Includes{$Header} = 1;
        }
    }
    return \%Includes;
}

sub ignore_path($)
{
    my $Path = $_[0];
    if($Path=~/\~\Z/)
    {# skipping system backup files
        return 1;
    }
    if($Path=~/(\A|[\/\\]+)(\.(svn|git|bzr|hg)|CVS)([\/\\]+|\Z)/)
    {# skipping hidden .svn, .git, .bzr, .hg and CVS directories
        return 1;
    }
    return 0;
}

sub sort_by_word($$)
{
    my ($ArrRef, $W) = @_;
    return if(length($W)<2);
    @{$ArrRef} = sort {get_filename($b)=~/\Q$W\E/i<=>get_filename($a)=~/\Q$W\E/i} @{$ArrRef};
}

sub natural_sorting($$)
{
    my ($H1, $H2) = @_;
    $H1=~s/\.[a-z]+\Z//ig;
    $H2=~s/\.[a-z]+\Z//ig;
    my ($HDir1, $Hname1) = separate_path($H1);
    my ($HDir2, $Hname2) = separate_path($H2);
    my $Dirname1 = get_filename($HDir1);
    my $Dirname2 = get_filename($HDir2);
    if($H1 eq $H2) {
        return 0;
    }
    elsif($H1=~/\A\Q$H2\E/) {
        return 1;
    }
    elsif($H2=~/\A\Q$H1\E/) {
        return -1;
    }
    elsif($HDir1=~/\Q$Hname1\E/i
    and $HDir2!~/\Q$Hname2\E/i)
    {# include/glib-2.0/glib.h
        return -1;
    }
    elsif($HDir2=~/\Q$Hname2\E/i
    and $HDir1!~/\Q$Hname1\E/i)
    {# include/glib-2.0/glib.h
        return 1;
    }
    elsif($Hname1=~/\Q$Dirname1\E/i
    and $Hname2!~/\Q$Dirname2\E/i)
    {# include/hildon-thumbnail/hildon-thumbnail-factory.h
        return -1;
    }
    elsif($Hname2=~/\Q$Dirname2\E/i
    and $Hname1!~/\Q$Dirname1\E/i)
    {# include/hildon-thumbnail/hildon-thumbnail-factory.h
        return 1;
    }
    elsif($Hname1=~/(config|lib)/i
    and $Hname2!~/(config|lib)/i)
    {# include/alsa/asoundlib.h
        return -1;
    }
    elsif($Hname2=~/(config|lib)/i
    and $Hname1!~/(config|lib)/i)
    {# include/alsa/asoundlib.h
        return 1;
    }
    elsif(checkRelevance($H1)
    and not checkRelevance($H2))
    {# libebook/e-book.h
        return -1;
    }
    elsif(checkRelevance($H2)
    and not checkRelevance($H1))
    {# libebook/e-book.h
        return 1;
    }
    else {
        return (lc($H1) cmp lc($H2));
    }
}

sub searchForHeaders($)
{
    my $LibVersion = $_[0];
    # gcc standard include paths
    find_gcc_cxx_headers($LibVersion);
    # processing header paths
    foreach my $Path (keys(%{$Descriptor{$LibVersion}{"IncludePaths"}}),
    keys(%{$Descriptor{$LibVersion}{"AddIncludePaths"}}))
    {
        my $IPath = $Path;
        if(not -e $Path) {
            exitStatus("Access_Error", "can't access \'$Path\'");
        }
        elsif(-f $Path) {
            exitStatus("Access_Error", "\'$Path\' - not a directory");
        }
        elsif(-d $Path)
        {
            $Path = get_abs_path($Path);
            register_directory($Path, 0, $LibVersion);
            if($Descriptor{$LibVersion}{"AddIncludePaths"}{$IPath}) {
                $Add_Include_Paths{$LibVersion}{$Path} = 1;
            }
            else {
                $Include_Paths{$LibVersion}{$Path} = 1;
            }
        }
    }
    if(keys(%{$Include_Paths{$LibVersion}})) {
        $INC_PATH_AUTODETECT{$LibVersion} = 0;
    }
    # registering directories
    foreach my $Path (split(/\s*\n\s*/, $Descriptor{$LibVersion}{"Headers"}))
    {
        next if(not -e $Path);
        $Path = get_abs_path($Path);
        $Path = path_format($Path, $OSgroup);
        if(-d $Path) {
            register_directory($Path, 1, $LibVersion);
        }
        elsif(-f $Path)
        {
            my $Dir = get_dirname($Path);
            if(not $SystemPaths{"include"}{$Dir}
            and not $LocalIncludes{$Dir})
            {
                register_directory($Dir, 1, $LibVersion);
                if(my $OutDir = get_dirname($Dir))
                { # registering the outer directory
                    if(not $SystemPaths{"include"}{$OutDir}
                    and not $LocalIncludes{$OutDir}) {
                        register_directory($OutDir, 0, $LibVersion);
                    }
                }
            }
        }
    }
    # registering headers
    my $Position = 0;
    foreach my $Dest (split(/\s*\n\s*/, $Descriptor{$LibVersion}{"Headers"}))
    {
        if(is_abs($Dest) and not -e $Dest) {
            exitStatus("Access_Error", "can't access \'$Dest\'");
        }
        $Dest = path_format($Dest, $OSgroup);
        if(is_header($Dest, 1, $LibVersion))
        {
            if(my $HPath = register_header($Dest, $LibVersion)) {
                $Registered_Headers{$LibVersion}{$HPath}{"Pos"} = $Position++;
            }
        }
        elsif(-d $Dest)
        {
            my @Registered = ();
            foreach my $Path (cmd_find($Dest,"f","",""))
            {
                next if(ignore_path($Path));
                next if(not is_header($Path, 0, $LibVersion));
                if(my $HPath = register_header($Path, $LibVersion)) {
                    push(@Registered, $HPath);
                }
            }
            @Registered = sort {natural_sorting($a, $b)} @Registered;
            sort_by_word(\@Registered, $TargetLibraryShortName);
            foreach my $Path (@Registered) {
                $Registered_Headers{$LibVersion}{$Path}{"Pos"} = $Position++;
            }
        }
        else {
            exitStatus("Access_Error", "can't identify \'$Dest\' as a header file");
        }
    }
    # preparing preamble headers
    my $Preamble_Position=0;
    foreach my $Header (split(/\s*\n\s*/, $Descriptor{$LibVersion}{"IncludePreamble"}))
    {
        if(is_abs($Header) and not -f $Header) {
            exitStatus("Access_Error", "can't access file \'$Header\'");
        }
        $Header = path_format($Header, $OSgroup);
        if(my $Header_Path = is_header($Header, 1, $LibVersion))
        {
            next if(skip_header($Header_Path, $LibVersion));
            $Include_Preamble{$LibVersion}{$Header_Path}{"Position"} = $Preamble_Position++;
        }
        else {
            exitStatus("Access_Error", "can't identify \'$Header\' as a header file");
        }
    }
    foreach my $Header_Name (keys(%{$HeaderName_Paths{$LibVersion}}))
    { # set relative paths (for duplicates)
        if(keys(%{$HeaderName_Paths{$LibVersion}{$Header_Name}})>=2)
        { # search for duplicates
            my $FirstPath = (keys(%{$HeaderName_Paths{$LibVersion}{$Header_Name}}))[0];
            my $Prefix = get_dirname($FirstPath);
            while($Prefix=~/\A(.+)[\/\\]+[^\/\\]+\Z/)
            { # detect a shortest distinguishing prefix
                my $NewPrefix = $1;
                my %Identity = ();
                foreach my $Path (keys(%{$HeaderName_Paths{$LibVersion}{$Header_Name}}))
                {
                    if($Path=~/\A\Q$Prefix\E[\/\\]+(.*)\Z/) {
                        $Identity{$Path} = $1;
                    }
                }
                if(keys(%Identity)==keys(%{$HeaderName_Paths{$LibVersion}{$Header_Name}}))
                { # all names are differend with current prefix
                    foreach my $Path (keys(%{$HeaderName_Paths{$LibVersion}{$Header_Name}})) {
                        $Registered_Headers{$LibVersion}{$Path}{"Identity"} = $Identity{$Path};
                    }
                    last;
                }
                $Prefix = $NewPrefix; # increase prefix
            }
        }
    }
    foreach my $HeaderName (keys(%{$Include_Order{$LibVersion}}))
    { # ordering headers according to descriptor
        my $PairName=$Include_Order{$LibVersion}{$HeaderName};
        my ($Pos, $PairPos) = (-1, -1);
        my ($Path, $PairPath) = ();
        my @Paths = keys(%{$Registered_Headers{$LibVersion}});
        @Paths = sort {int($Registered_Headers{$LibVersion}{$a}{"Pos"})<=>int($Registered_Headers{$LibVersion}{$b}{"Pos"})} @Paths;
        foreach my $Header_Path (@Paths) 
        {
            if(get_filename($Header_Path) eq $PairName)
            {
                $PairPos = $Registered_Headers{$LibVersion}{$Header_Path}{"Pos"};
                $PairPath = $Header_Path;
            }
            if(get_filename($Header_Path) eq $HeaderName)
            {
                $Pos = $Registered_Headers{$LibVersion}{$Header_Path}{"Pos"};
                $Path = $Header_Path;
            }
        }
        if($PairPos!=-1 and $Pos!=-1
        and int($PairPos)<int($Pos))
        {
            my %Tmp = %{$Registered_Headers{$LibVersion}{$Path}};
            %{$Registered_Headers{$LibVersion}{$Path}} = %{$Registered_Headers{$LibVersion}{$PairPath}};
            %{$Registered_Headers{$LibVersion}{$PairPath}} = %Tmp;
        }
    }
    if(not keys(%{$Registered_Headers{$LibVersion}})) {
        exitStatus("Error", "header files are not found in the ".$Descriptor{$LibVersion}{"Version"});
    }
}

sub detect_real_includes($$)
{
    my ($AbsPath, $LibVersion) = @_;
    return () if(not $LibVersion or not $AbsPath or not -e $AbsPath);
    if($Cache{"detect_real_includes"}{$LibVersion}{$AbsPath}
    or keys(%{$RecursiveIncludes{$LibVersion}{$AbsPath}})) {
        return keys(%{$RecursiveIncludes{$LibVersion}{$AbsPath}});
    }
    my $Path = callPreprocessor($AbsPath, "", $LibVersion);
    return () if(not $Path);
    open(PREPROC, $Path);
    while(<PREPROC>)
    {
        if(/#\s+\d+\s+"([^"]+)"[\s\d]*\n/)
        {
            my $Include = path_format($1, $OSgroup);
            if($Include=~/\<(built\-in|internal|command(\-|\s)line)\>|\A\./) {
                next;
            }
            if($Include eq $AbsPath) {
                next;
            }
            $RecursiveIncludes{$LibVersion}{$AbsPath}{$Include} = 1;
        }
    }
    close(PREPROC);
    $Cache{"detect_real_includes"}{$LibVersion}{$AbsPath}=1;
    return keys(%{$RecursiveIncludes{$LibVersion}{$AbsPath}});
}

sub detect_header_includes($$)
{
    my ($Path, $LibVersion) = @_;
    return if(not $LibVersion or not $Path or not -e $Path);
    return if($Cache{"detect_header_includes"}{$LibVersion}{$Path});
    my $Content = readFile($Path);
    if($Content=~/#[ \t]*error[ \t]+/ and (my $Redirect = parse_redirect($Content, $Path, $LibVersion)))
    {# detecting error directive in the headers
        if(my $RedirectPath = identify_header($Redirect, $LibVersion))
        {
            if($RedirectPath=~/\/usr\/include\// and $Path!~/\/usr\/include\//) {
                $RedirectPath = identify_header(get_filename($Redirect), $LibVersion);
            }
            if($RedirectPath ne $Path) {
                $Header_ErrorRedirect{$LibVersion}{$Path} = $RedirectPath;
            }
        }
    }
    my $Inc = parse_includes($Content, $Path);
    foreach my $Include (keys(%{$Inc}))
    {# detecting includes
        #if(is_not_header($Include))
        #{ #include<*.c> and others
            # next;
        #}
        $Header_Includes{$LibVersion}{$Path}{$Include} = $Inc->{$Include};
    }
    $Cache{"detect_header_includes"}{$LibVersion}{$Path} = 1;
}

sub simplify_path($)
{
    my $Path = $_[0];
    while($Path=~s&([\/\\])[^\/\\]+[\/\\]\.\.[\/\\]&$1&){};
    return $Path;
}

sub fromLibc($)
{ # GLIBC header
    my $Path = $_[0];
    my ($Dir, $Name) = separate_path($Path);
    if(get_filename($Dir)=~/\A(include|libc)\Z/ and $GlibcHeader{$Name})
    { # /usr/include/{stdio.h, ...}
      # epoc32/include/libc/{stdio.h, ...}
        return 1;
    }
    if(isLibcDir($Dir)) {
        return 1;
    }
    return 0;
}

sub isLibcDir($)
{ # GLIBC directory
    my $Dir = $_[0];
    my ($OutDir, $Name) = separate_path($Dir);
    if(get_filename($OutDir)=~/\A(include|libc)\Z/
    and ($Name=~/\Aasm(|-.+)\Z/ or $GlibcDir{$Name}))
    { # /usr/include/{sys,bits,asm,asm-*}/*.h
        return 1;
    }
    return 0;
}

sub detect_recursive_includes($$)
{
    my ($AbsPath, $LibVersion) = @_;
    return () if(not $AbsPath);
    if(isCyclical(\@RecurInclude, $AbsPath)) {
        return keys(%{$RecursiveIncludes{$LibVersion}{$AbsPath}});
    }
    my ($AbsDir, $Name) = separate_path($AbsPath);
    if(isLibcDir($AbsDir))
    { # GLIBC internals
        return ();
    }
    if(keys(%{$RecursiveIncludes{$LibVersion}{$AbsPath}})) {
        return keys(%{$RecursiveIncludes{$LibVersion}{$AbsPath}});
    }
    return () if($OSgroup ne "windows" and $Name=~/windows|win32|win64/i);
    return () if($MAIN_CPP_DIR and $AbsPath=~/\A\Q$MAIN_CPP_DIR\E/ and not $STDCXX_TESTING);
    push(@RecurInclude, $AbsPath);
    if($DefaultGccPaths{$AbsDir}
    or fromLibc($AbsPath))
    { # check "real" (non-"model") include paths
        my @Paths = detect_real_includes($AbsPath, $LibVersion);
        pop(@RecurInclude);
        return @Paths;
    }
    if(not keys(%{$Header_Includes{$LibVersion}{$AbsPath}})) {
        detect_header_includes($AbsPath, $LibVersion);
    }
    foreach my $Include (keys(%{$Header_Includes{$LibVersion}{$AbsPath}}))
    {
        my $HPath = "";
        if($Header_Includes{$LibVersion}{$AbsPath}{$Include}==-1)
        { # for #include "..."
            my $Candidate = joinPath($AbsDir, $Include);
            if(-f $Candidate) {
                $HPath = simplify_path($Candidate);
            }
        }
        elsif($Header_Includes{$LibVersion}{$AbsPath}{$Include}==1
        and $Include=~/[\/\\]/ and not find_in_defaults($Include))
        { # search for the nearest header
          # QtCore/qabstractanimation.h includes <QtCore/qobject.h>
            my $Candidate = joinPath(get_dirname($AbsDir), $Include);
            if(-f $Candidate) {
                $HPath = $Candidate;
            }
        }
        if(not $HPath) {
            $HPath = identify_header($Include, $LibVersion);
        }
        next if(not $HPath);
        if($HPath eq $AbsPath) {
            next;
        }
        $RecursiveIncludes{$LibVersion}{$AbsPath}{$HPath} = 1;
        if($Header_Includes{$LibVersion}{$AbsPath}{$Include}==1)
        { # only include <...>, skip include "..." prefixes
            $Header_Include_Prefix{$LibVersion}{$AbsPath}{$HPath}{get_dirname($Include)} = 1;
        }
        foreach my $IncPath (detect_recursive_includes($HPath, $LibVersion))
        {
            if($IncPath eq $AbsPath) {
                next;
            }
            $RecursiveIncludes{$LibVersion}{$AbsPath}{$IncPath} = 1;
            foreach my $Prefix (keys(%{$Header_Include_Prefix{$LibVersion}{$HPath}{$IncPath}})) {
                $Header_Include_Prefix{$LibVersion}{$AbsPath}{$IncPath}{$Prefix} = 1;
            }
        }
        foreach my $Dep (keys(%{$Header_Include_Prefix{$LibVersion}{$AbsPath}}))
        {
            if($GlibcHeader{get_filename($Dep)} and keys(%{$Header_Include_Prefix{$LibVersion}{$AbsPath}{$Dep}})>=2
            and defined $Header_Include_Prefix{$LibVersion}{$AbsPath}{$Dep}{""})
            { # distinguish math.h from glibc and math.h from the tested library
                delete($Header_Include_Prefix{$LibVersion}{$AbsPath}{$Dep}{""});
                last;
            }
        }
    }
    pop(@RecurInclude);
    return keys(%{$RecursiveIncludes{$LibVersion}{$AbsPath}});
}

sub find_in_framework($$$)
{
    my ($Header, $Framework, $LibVersion) = @_;
    return "" if(not $Header or not $Framework or not $LibVersion);
    if(defined $Cache{"find_in_framework"}{$LibVersion}{$Framework}{$Header}) {
        return $Cache{"find_in_framework"}{$LibVersion}{$Framework}{$Header};
    }
    foreach my $Dependency (sort {get_depth($a)<=>get_depth($b)} keys(%{$Header_Dependency{$LibVersion}}))
    {
        if(get_filename($Dependency) eq $Framework
        and -f get_dirname($Dependency)."/".$Header) {
            return ($Cache{"find_in_framework"}{$LibVersion}{$Framework}{$Header} = get_dirname($Dependency));
        }
    }
    return ($Cache{"find_in_framework"}{$LibVersion}{$Framework}{$Header} = "");
}

sub find_in_defaults($)
{
    my $Header = $_[0];
    return "" if(not $Header);
    if(defined $Cache{"find_in_defaults"}{$Header}) {
        return $Cache{"find_in_defaults"}{$Header};
    }
    foreach my $Dir (sort {get_depth($a)<=>get_depth($b)}
    (keys(%DefaultIncPaths), keys(%DefaultGccPaths), keys(%DefaultCppPaths), keys(%UserIncPath)))
    {
        next if(not $Dir);
        if(-f $Dir."/".$Header) {
            return ($Cache{"find_in_defaults"}{$Header}=$Dir);
        }
    }
    return ($Cache{"find_in_defaults"}{$Header}="");
}

sub cmp_paths($$)
{
    my ($Path1, $Path2) = @_;
    my @Parts1 = split(/[\/\\]/, $Path1);
    my @Parts2 = split(/[\/\\]/, $Path2);
    foreach my $Num (0 .. $#Parts1)
    {
        my $Part1 = $Parts1[$Num];
        my $Part2 = $Parts2[$Num];
        if($GlibcDir{$Part1}
        and not $GlibcDir{$Part2}) {
            return 1;
        }
        elsif($GlibcDir{$Part2}
        and not $GlibcDir{$Part1}) {
            return -1;
        }
        elsif($Part1=~/glib/
        and $Part2!~/glib/) {
            return 1;
        }
        elsif($Part1!~/glib/
        and $Part2=~/glib/) {
            return -1;
        }
        elsif(my $CmpRes = ($Part1 cmp $Part2)) {
            return $CmpRes;
        }
    }
    return 0;
}

sub checkRelevance($)
{
    my ($Path) = @_;
    return 0 if(not $Path);
    if($SystemRoot) {
        $Path=~s/\A\Q$SystemRoot\E//g;
    }
    my ($Dir, $Name) = separate_path($Path);
    $Name=~s/\.\w+\Z//g;# remove extension (.h)
    my @Tokens = split(/[_\d\W]+/, $Name);
    foreach (@Tokens)
    {
        next if(not $_);
        if($Dir=~/(\A|lib|[_\d\W])\Q$_\E([_\d\W]|lib|\Z)/i
        or length($_)>=4 and $Dir=~/\Q$_\E/i)
        { # include/gupnp-1.0/libgupnp/gupnp-context.h
          # include/evolution-data-server-1.4/libebook/e-book.h
            return 1;
        }
    }
    return 0;
}

sub checkFamily(@)
{
    my @Paths = @_;
    return 1 if($#Paths<=0);
    my %Prefix = ();
    foreach my $Path (@Paths)
    {
        if($SystemRoot) {
            $Path = cut_path_prefix($Path, $SystemRoot);
        }
        if(my $Dir = get_dirname($Path))
        {
            $Dir=~s/(\/[^\/]+?)[\d\.\-\_]+\Z/$1/g; # remove version suffix
            $Prefix{$Dir} += 1;
            $Prefix{get_dirname($Dir)} += 1;
        }
    }
    foreach (sort keys(%Prefix))
    {
        if(get_depth($_)>=3
        and $Prefix{$_}==$#Paths+1) {
            return 1;
        }
    }
    return 0;
}

sub isAcceptable($$$)
{
    my ($Header, $Candidate, $LibVersion) = @_;
    my $HName = get_filename($Header);
    if(get_dirname($Header))
    { # with prefix
        return 1;
    }
    if($HName=~/config|setup/i and $Candidate=~/[\/\\]lib\d*[\/\\]/)
    { # allow to search for glibconfig.h in /usr/lib/glib-2.0/include/
        return 1;
    }
    if(checkRelevance($Candidate))
    { # allow to search for atk.h in /usr/include/atk-1.0/atk/
        return 1;
    }
    if(checkFamily(getSystemHeaders($HName, $LibVersion)))
    { # /usr/include/qt4/QtNetwork/qsslconfiguration.h
      # /usr/include/qt4/Qt/qsslconfiguration.h
        return 1;
    }
    if($OStarget eq "symbian")
    {
        if($Candidate=~/[\/\\]stdapis[\/\\]/) {
            return 1;
        }
    }
    return 0;
}

sub isRelevant($$$)
{ # disallow to search for "abstract" headers in too deep directories
    my ($Header, $Candidate, $LibVersion) = @_;
    my $HName = get_filename($Header);
    if($OStarget eq "symbian")
    {
        if($Candidate=~/[\/\\](tools|stlportv5)[\/\\]/) {
            return 0;
        }
    }
    if($OStarget ne "bsd") {
        if($Candidate=~/[\/\\]include[\/\\]bsd[\/\\]/)
        { # openssh: skip /usr/lib/bcc/include/bsd/signal.h
            return 0;
        }
    }
    if(not get_dirname($Header)
    and $Candidate=~/[\/\\]wx[\/\\]/)
    { # do NOT search in system /wx/ directory
      # for headers without a prefix: sstream.h
        return 0;
    }
    if($Candidate=~/c\+\+[\/\\]\d+/ and $MAIN_CPP_DIR
    and $Candidate!~/\A\Q$MAIN_CPP_DIR\E/)
    { # skip ../c++/3.3.3/ if using ../c++/4.5/
        return 0;
    }
    if($Candidate=~/[\/\\]asm-/
    and (my $Arch = getArch($LibVersion)) ne "unknown")
    { # arch-specific header files
        if($Candidate!~/[\/\\]asm-\Q$Arch\E/)
        {# skip ../asm-arm/ if using x86 architecture
            return 0;
        }
    }
    my @Candidates = getSystemHeaders($HName, $LibVersion);
    if($#Candidates==1)
    { # unique header
        return 1;
    }
    my @SCandidates = getSystemHeaders($Header, $LibVersion);
    if($#SCandidates==1)
    { # unique name
        return 1;
    }
    my $SystemDepth = $SystemRoot?get_depth($SystemRoot):0;
    if(get_depth($Candidate)-$SystemDepth>=5)
    { # abstract headers in too deep directories
      # sstream.h or typeinfo.h in /usr/include/wx-2.9/wx/
        if(not isAcceptable($Header, $Candidate, $LibVersion)) {
            return 0;
        }
    }
    if($Header eq "parser.h"
    and $Candidate!~/\/libxml2\//)
    { # select parser.h from xml2 library
        return 0;
    }
    if(not get_dirname($Header)
    and keys(%{$SystemHeaders{$HName}})>=3)
    { # many headers with the same name
      # like thread.h included without a prefix
        if(not checkFamily(@Candidates)) {
            return 0;
        }
    }
    return 1;
}

sub selectSystemHeader($$)
{
    my ($Header, $LibVersion) = @_;
    return $Header if(-f $Header);
    return "" if(is_abs($Header) and not -f $Header);
    return "" if($Header=~/\A(atomic|config|configure|build|conf|setup)\.h\Z/i);
    if($OSgroup ne "windows")
    {
        if(get_filename($Header)=~/windows|win32|win64|\A(dos|process|winsock|config-win)\.h\Z/i) {
            return "";
        }
        elsif($Header=~/\A(mem)\.h\Z/)
        { # pngconf.h include mem.h for __MSDOS__
            return "";
        }
    }
    if($OSgroup ne "solaris")
    {
        if($Header=~/\A(thread)\.h\Z/)
        { # thread.h in Solaris
            return "";
        }
    }
    if(defined $Cache{"selectSystemHeader"}{$LibVersion}{$Header}) {
        return $Cache{"selectSystemHeader"}{$LibVersion}{$Header};
    }
    foreach my $Path (keys(%{$SystemPaths{"include"}}))
    { # search in default paths
        if(-f $Path."/".$Header) {
            return ($Cache{"selectSystemHeader"}{$LibVersion}{$Header} = joinPath($Path,$Header));
        }
    }
    if(not keys(%SystemHeaders)) {
        detectSystemHeaders();
    }
    foreach my $Candidate (sort {get_depth($a)<=>get_depth($b)}
    sort {cmp_paths($b, $a)} getSystemHeaders($Header, $LibVersion))
    {
        if(isRelevant($Header, $Candidate, $LibVersion)) {
            return ($Cache{"selectSystemHeader"}{$LibVersion}{$Header} = $Candidate);
        }
    }
    return ($Cache{"selectSystemHeader"}{$LibVersion}{$Header} = ""); # error
}

sub getSystemHeaders($$)
{
    my ($Header, $LibVersion) = @_;
    my @Candidates = ();
    foreach my $Candidate (sort keys(%{$SystemHeaders{$Header}}))
    {
        if(skip_header($Candidate, $LibVersion)) {
            next;
        }
        push(@Candidates, $Candidate);
    }
    return @Candidates;
}

sub cut_path_prefix($$)
{
    my ($Path, $Prefix) = @_;
    return $Path if(not $Prefix);
    $Prefix=~s/[\/\\]+\Z//;
    $Path=~s/\A\Q$Prefix\E([\/\\]+|\Z)//;
    return $Path;
}

sub is_default_include_dir($)
{
    my $Dir = $_[0];
    $Dir=~s/[\/\\]+\Z//;
    return ($DefaultGccPaths{$Dir} or $DefaultCppPaths{$Dir} or $DefaultIncPaths{$Dir});
}

sub identify_header($$)
{
    my ($Header, $LibVersion) = @_;
    $Header=~s/\A(\.\.[\\\/])+//g;
    if(defined $Cache{"identify_header"}{$Header}{$LibVersion}) {
        return $Cache{"identify_header"}{$Header}{$LibVersion};
    }
    my $Path = identify_header_internal($Header, $LibVersion);
    if(not $Path and $OSgroup eq "macos" and my $Dir = get_dirname($Header))
    { # search in frameworks: "OpenGL/gl.h" is "OpenGL.framework/Headers/gl.h"
        my $RelPath = "Headers\/".get_filename($Header);
        if(my $HeaderDir = find_in_framework($RelPath, $Dir.".framework", $LibVersion)) {
            $Path = joinPath($HeaderDir, $RelPath);
        }
    }
    return ($Cache{"identify_header"}{$Header}{$LibVersion} = $Path);
}

sub identify_header_internal($$)
{ # search for header by absolute path, relative path or name
    my ($Header, $LibVersion) = @_;
    return "" if(not $Header);
    if(-f $Header)
    { # it's relative or absolute path
        return get_abs_path($Header);
    }
    elsif($GlibcHeader{$Header} and not $GLIBC_TESTING
    and my $HeaderDir = find_in_defaults($Header))
    { # search for libc headers in the /usr/include
      # for non-libc target library before searching
      # in the library paths
        return joinPath($HeaderDir,$Header);
    }
    elsif(my $Path = $Include_Neighbors{$LibVersion}{$Header})
    { # search in the target library paths
        return $Path;
    }
    elsif($DefaultGccHeader{$Header})
    { # search in the internal GCC include paths
        return $DefaultGccHeader{$Header};
    }
    elsif(my $HeaderDir = find_in_defaults($Header))
    { # search in the default GCC include paths
        return joinPath($HeaderDir,$Header);
    }
    elsif($DefaultCppHeader{$Header})
    { # search in the default G++ include paths
        return $DefaultCppHeader{$Header};
    }
    elsif(my $AnyPath = selectSystemHeader($Header, $LibVersion))
    { # search everywhere in the system
        return $AnyPath;
    }
    else
    { # cannot find anything
        return "";
    }
}

sub getLocation($)
{
    if($LibInfo{$Version}{"info"}{$_[0]}=~/srcp[ ]*:[ ]*([\w\-\<\>\.\+\/\\]+):(\d+) /) {
        return ($1, $2);
    }
    else {
        return ();
    }
}

sub getTypeTypeByTypeId($)
{
    my $TypeId = $_[0];
    my $TypeType = $LibInfo{$Version}{"info_type"}{$TypeId};
    if($TypeType=~/integer_type|real_type|boolean_type|void_type|complex_type/)
    {
        return "Intrinsic";
    }
    elsif(isFuncPtr($TypeId)) {
        return "FuncPtr";
    }
    elsif(isMethodPtr($TypeId)) {
        return "MethodPtr";
    }
    elsif(isFieldPtr($TypeId)) {
        return "FieldPtr";
    }
    elsif($TypeType eq "pointer_type") {
        return "Pointer";
    }
    elsif($TypeType eq "reference_type") {
        return "Ref";
    }
    elsif($TypeType eq "union_type") {
        return "Union";
    }
    elsif($TypeType eq "enumeral_type") {
        return "Enum";
    }
    elsif($TypeType eq "record_type") {
        return "Struct";
    }
    elsif($TypeType eq "array_type") {
        return "Array";
    }
    elsif($TypeType eq "complex_type") {
        return "Intrinsic";
    }
    elsif($TypeType eq "function_type") {
        return "FunctionType";
    }
    elsif($TypeType eq "method_type") {
        return "MethodType";
    }
    else {
        return "Unknown";
    }
}

sub getNameByInfo($)
{
    return "" if($LibInfo{$Version}{"info"}{$_[0]}!~/name[ ]*:[ ]*@(\d+) /);
    if($LibInfo{$Version}{"info"}{$1}=~/strg[ ]*:[ ]*(.*?)[ ]+lngt/)
    {# short unsigned int (may include spaces)
        return $1;
    }
    else {
        return "";
    }
}

sub getTreeStr($)
{
    my $Info = $LibInfo{$Version}{"info"}{$_[0]};
    if($Info=~/strg[ ]*:[ ]*([^ ]*)/)
    {
        my $Str = $1;
        if($C99Mode{$Version}
        and $Str=~/\Ac99_(.+)\Z/) {
            if($CppKeywords_A{$1}) {
                $Str=$1;
            }
        }
        return $Str;
    }
    else {
        return "";
    }
}

sub getVarShortName($)
{
    my $VarInfo = $LibInfo{$Version}{"info"}{$_[0]};
    return "" if($VarInfo!~/name[ ]*:[ ]*@(\d+) /);
    return getTreeStr($1);
}

sub getFuncShortName($)
{
    my $FuncInfo = $LibInfo{$Version}{"info"}{$_[0]};
    if($FuncInfo=~/ operator /)
    {
        if($FuncInfo=~/ conversion /) {
            return "operator ".get_TypeName($SymbolInfo{$Version}{$_[0]}{"Return"}, $Version);
        }
        else
        {
            return "" if($FuncInfo!~/ operator[ ]+([a-zA-Z]+) /);
            return "operator".$Operator_Indication{$1};
        }
    }
    else
    {
        return "" if($FuncInfo!~/name[ ]*:[ ]*@(\d+) /);
        return getTreeStr($1);
    }
}

sub getFuncMnglName($)
{
    my $FuncInfo = $LibInfo{$Version}{"info"}{$_[0]};
    return "" if($FuncInfo!~/mngl[ ]*:[ ]*@(\d+) /);
    return getTreeStr($1);
}

sub getFuncReturn($)
{
    my $FuncInfo = $LibInfo{$Version}{"info"}{$_[0]};
    if($FuncInfo=~/type[ ]*:[ ]*@(\d+) /) {
        if($LibInfo{$Version}{"info"}{$1}=~/retn[ ]*:[ ]*@(\d+) /) {
            return $1;
        }
    }
    return "";
}

sub getFuncOrig($)
{
    my $FuncInfo = $LibInfo{$Version}{"info"}{$_[0]};
    if($FuncInfo=~/orig[ ]*:[ ]*@(\d+) /) {
        return $1;
    }
    else {
        return $_[0];
    }
}

sub unmangleSymbol($)
{
    my $Symbol = $_[0];
    my @Unmngl = unmangleArray($Symbol);
    return $Unmngl[0];
}

sub unmangleArray(@)
{
    if(@_[0]=~/\A\?/)
    { # MSVC mangling
        my $UndNameCmd = get_CmdPath("undname");
        if(not $UndNameCmd) {
            exitStatus("Not_Found", "can't find \"undname\"");
        }
        writeFile("$TMP_DIR/unmangle", join("\n", @_));
        return split(/\n/, `$UndNameCmd 0x8386 $TMP_DIR/unmangle`);
    }
    else
    { # GCC mangling
        my $CppFiltCmd = get_CmdPath("c++filt");
        if(not $CppFiltCmd) {
            exitStatus("Not_Found", "can't find c++filt in PATH");
        }
        my $Info = `$CppFiltCmd -h 2>&1`;
        if($Info=~/\@<file>/)
        {# new version of c++filt can take a file
            my $NoStrip = "";
            if($OSgroup eq "macos"
            or $OSgroup eq "windows") {
                $NoStrip = "-n";
            }
            writeFile("$TMP_DIR/unmangle", join("\n", @_));
            return split(/\n/, `$CppFiltCmd $NoStrip \@\"$TMP_DIR/unmangle\"`);
        }
        else
        { # old-style unmangling
            if($#_>$MAX_COMMAND_LINE_ARGUMENTS) {
                my @Half = splice(@_, 0, ($#_+1)/2);
                return (unmangleArray(@Half), unmangleArray(@_))
            }
            else
            {
                my $NoStrip = "";
                if($OSgroup eq "macos"
                or $OSgroup eq "windows") {
                    $NoStrip = "-n";
                }
                my $Strings = join(" ", @_);
                return split(/\n/, `$CppFiltCmd $NoStrip $Strings`);
            }
        }
    }
}

sub get_SignatureNoInfo($$)
{
    my ($Interface, $LibVersion) = @_;
    if($Cache{"get_SignatureNoInfo"}{$LibVersion}{$Interface}) {
        return $Cache{"get_SignatureNoInfo"}{$LibVersion}{$Interface};
    }
    my ($MnglName, $VersionSpec, $SymbolVersion) = separate_symbol($Interface);
    my $Signature = $tr_name{$MnglName}?$tr_name{$MnglName}:$MnglName;
    if($Interface=~/\A(_Z|\?)/)
    { # C++
        $Signature=~s/\Qstd::basic_string<char, std::char_traits<char>, std::allocator<char> >\E/std::string/g;
        $Signature=~s/\Qstd::map<std::string, std::string, std::less<std::string >, std::allocator<std::pair<std::string const, std::string > > >\E/std::map<std::string, std::string>/g;
    }
    if(not $CheckObjectsOnly or $OSgroup=~/linux|bsd|beos/)
    { # ELF format marks data as OBJECT
        if($CompleteSignature{$LibVersion}{$Interface}{"Object"}) {
            $Signature .= " [data]";
        }
        elsif($Interface!~/\A(_Z|\?)/) {
            $Signature .= " (...)";
        }
    }
    if(my $ChargeLevel = get_ChargeLevel($Interface, $LibVersion))
    {
        my $ShortName = substr($Signature, 0, detect_center($Signature, "("));
        $Signature=~s/\A\Q$ShortName\E/$ShortName $ChargeLevel/g;
    }
    if($SymbolVersion) {
        $Signature .= $VersionSpec.$SymbolVersion;
    }
    return ($Cache{"get_SignatureNoInfo"}{$LibVersion}{$Interface} = $Signature);
}

sub get_ChargeLevel($$)
{
    my ($Interface, $LibVersion) = @_;
    return "" if($Interface!~/\A(_Z|\?)/);
    if(defined $CompleteSignature{$LibVersion}{$Interface}
    and $CompleteSignature{$LibVersion}{$Interface}{"Header"})
    {
        if($CompleteSignature{$LibVersion}{$Interface}{"Constructor"})
        {
            if($Interface=~/C1E/) {
                return "[in-charge]";
            }
            elsif($Interface=~/C2E/) {
                return "[not-in-charge]";
            }
        }
        elsif($CompleteSignature{$LibVersion}{$Interface}{"Destructor"})
        {
            if($Interface=~/D1E/) {
                return "[in-charge]";
            }
            elsif($Interface=~/D2E/) {
                return "[not-in-charge]";
            }
            elsif($Interface=~/D0E/) {
                return "[in-charge-deleting]";
            }
        }
    }
    else
    {
        if($Interface=~/C1E/) {
            return "[in-charge]";
        }
        elsif($Interface=~/C2E/) {
            return "[not-in-charge]";
        }
        elsif($Interface=~/D1E/) {
            return "[in-charge]";
        }
        elsif($Interface=~/D2E/) {
            return "[not-in-charge]";
        }
        elsif($Interface=~/D0E/) {
            return "[in-charge-deleting]";
        }
    }
    return "";
}

sub get_Signature($$)
{
    my ($Interface, $LibVersion) = @_;
    if($Cache{"get_Signature"}{$LibVersion}{$Interface}) {
        return $Cache{"get_Signature"}{$LibVersion}{$Interface};
    }
    my ($MnglName, $VersionSpec, $SymbolVersion) = separate_symbol($Interface);
    if(skipGlobalData($MnglName) or not $CompleteSignature{$LibVersion}{$Interface}{"Header"}) {
        return get_SignatureNoInfo($Interface, $LibVersion);
    }
    my ($Func_Signature, @Param_Types_FromUnmangledName) = ();
    my $ShortName = $CompleteSignature{$LibVersion}{$Interface}{"ShortName"};
    if($Interface=~/\A(_Z|\?)/)
    {
        if(my $ClassId = $CompleteSignature{$LibVersion}{$Interface}{"Class"}) {
            $Func_Signature = get_TypeName($ClassId, $LibVersion)."::".(($CompleteSignature{$LibVersion}{$Interface}{"Destructor"})?"~":"").$ShortName;
        }
        elsif(my $NameSpace = $CompleteSignature{$LibVersion}{$Interface}{"NameSpace"}) {
            $Func_Signature = $NameSpace."::".$ShortName;
        }
        else {
            $Func_Signature = $ShortName;
        }
        @Param_Types_FromUnmangledName = get_s_params($tr_name{$MnglName}, 0);
    }
    else {
        $Func_Signature = $MnglName;
    }
    my @ParamArray = ();
    foreach my $Pos (sort {int($a) <=> int($b)} keys(%{$CompleteSignature{$LibVersion}{$Interface}{"Param"}}))
    {
        next if($Pos eq "");
        my $ParamTypeId = $CompleteSignature{$LibVersion}{$Interface}{"Param"}{$Pos}{"type"};
        next if(not $ParamTypeId);
        my $ParamTypeName = get_TypeName($ParamTypeId, $LibVersion);
        if(not $ParamTypeName) {
            $ParamTypeName = $Param_Types_FromUnmangledName[$Pos];
        }
        foreach my $Typedef (keys(%ChangedTypedef))
        {
            my $Base = $Typedef_BaseName{$LibVersion}{$Typedef};
            $ParamTypeName=~s/(\A|\W)\Q$Typedef\E(\W|\Z)/$1$Base$2/g;
        }
        if(my $ParamName = $CompleteSignature{$LibVersion}{$Interface}{"Param"}{$Pos}{"name"}) {
            push(@ParamArray, create_member_decl($ParamTypeName, $ParamName));
        }
        else {
            push(@ParamArray, $ParamTypeName);
        }
    }
    if($CompleteSignature{$LibVersion}{$Interface}{"Data"}
    or $CompleteSignature{$LibVersion}{$Interface}{"Object"}) {
        $Func_Signature .= " [data]";
    }
    else
    {
        if(my $ChargeLevel = get_ChargeLevel($Interface, $LibVersion))
        { # add [in-charge]
            $Func_Signature .= " ".$ChargeLevel;
        }
        $Func_Signature .= " (".join(", ", @ParamArray).")";
        if($CompleteSignature{$LibVersion}{$Interface}{"Const"}
        or $Interface=~/\A_ZN(V|)K/) {
            $Func_Signature .= " const";
        }
        if($CompleteSignature{$LibVersion}{$Interface}{"Volatile"}
        or $Interface=~/\A_ZN(K|)V/) {
            $Func_Signature .= " volatile";
        }
        if($CompleteSignature{$LibVersion}{$Interface}{"Static"}
        and $Interface=~/\A(_Z|\?)/)
        {# for static methods
            $Func_Signature .= " [static]";
        }
    }
    if(defined $ShowRetVal
    and my $ReturnTId = $CompleteSignature{$LibVersion}{$Interface}{"Return"}) {
        $Func_Signature .= ":".get_TypeName($ReturnTId, $LibVersion);
    }
    if($SymbolVersion) {
        $Func_Signature .= $VersionSpec.$SymbolVersion;
    }
    return ($Cache{"get_Signature"}{$LibVersion}{$Interface} = $Func_Signature);
}

sub create_member_decl($$)
{
    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 getFuncType($)
{
    my $FuncInfo = $LibInfo{$Version}{"info"}{$_[0]};
    return "" if($FuncInfo!~/type[ ]*:[ ]*@(\d+) /);
    my $FuncTypeInfoId = $1;
    my $FunctionType = $LibInfo{$Version}{"info_type"}{$FuncTypeInfoId};
    if($FunctionType eq "method_type") {
        return "Method";
    }
    elsif($FunctionType eq "function_type") {
        return "Function";
    }
    else {
        return $FunctionType;
    }
}

sub getFuncTypeId($)
{
    my $FuncInfo = $LibInfo{$Version}{"info"}{$_[0]};
    if($FuncInfo=~/type[ ]*:[ ]*@(\d+)( |\Z)/) {
        return $1;
    }
    else {
        return 0;
    }
}

sub isNotAnon($) {
    return (not isAnon($_[0]));
}

sub isAnon($)
{# "._N" or "$_N" in older GCC versions
    return ($_[0]=~/(\.|\$)\_\d+|anon\-/);
}

sub unmangled_Compact($)
{ # Removes all non-essential (for C++ language) whitespace from a string.  If 
  # the whitespace is essential it will be replaced with exactly one ' ' 
  # character. Works correctly only for unmangled names.
    my $Name = $_[0];
    if(defined $Cache{"unmangled_Compact"}{$Name}) {
        return $Cache{"unmangled_Compact"}{$Name};
    }
    # First, we reduce all spaces that we can
    my $coms='[-()<>:*&~!|+=%@~"?.,/[^'."']";
    my $coms_nobr='[-()<:*&~!|+=%@~"?.,'."']";
    my $clos='[),;:\]]';
    $_ = $Name;
    s/^\s+//gm;
    s/\s+$//gm;
    s/((?!\n)\s)+/ /g;
    s/(\w+)\s+($coms+)/$1$2/gm;
    s/($coms+)\s+(\w+)/$1$2/gm;
    s/(\w)\s+($clos)/$1$2/gm;
    s/($coms+)\s+($coms+)/$1 $2/gm;
    s/($coms_nobr+)\s+($coms+)/$1$2/gm;
    s/($coms+)\s+($coms_nobr+)/$1$2/gm;
    # don't forget about >> and <:.  In unmangled names global-scope modifier 
    # is not used, so <: will always be a digraph and requires no special treatment.
    # We also try to remove other parts that are better to be removed here than in other places
    # double-cv
    s/\bconst\s+const\b/const/gm;
    s/\bvolatile\s+volatile\b/volatile/gm;
    s/\bconst\s+volatile\b\s+const\b/const volatile/gm;
    s/\bvolatile\s+const\b\s+volatile\b/const volatile/gm;
    # Place cv in proper order
    s/\bvolatile\s+const\b/const volatile/gm;
    return ($Cache{"unmangled_Compact"}{$Name} = $_);
}

sub unmangled_PostProcess($)
{
    my $Name = $_[0];
    $_ = $Name;
    #s/\bunsigned int\b/unsigned/g;
    s/\bshort unsigned int\b/unsigned short/g;
    s/\bshort int\b/short/g;
    s/\blong long unsigned int\b/unsigned long long/g;
    s/\blong unsigned int\b/unsigned long/g;
    s/\blong long int\b/long long/g;
    s/\blong int\b/long/g;
    s/\)const\b/\) const/g;
    s/\blong long unsigned\b/unsigned long long/g;
    s/\blong unsigned\b/unsigned long/g;
    return $_;
}

sub formatName($)
{# type name correction
    my $Name = $_[0];
    $Name=unmangled_Compact($Name);
    $Name=unmangled_PostProcess($Name);
    $Name=~s/>>/> >/g; # double templates
    $Name=~s/(operator\s*)> >/$1>>/;
    return $Name;
}

sub get_HeaderDeps($$)
{
    my ($AbsPath, $LibVersion) = @_;
    return () if(not $AbsPath or not $LibVersion);
    if(defined $Cache{"get_HeaderDeps"}{$LibVersion}{$AbsPath}) {
        return @{$Cache{"get_HeaderDeps"}{$LibVersion}{$AbsPath}};
    }
    my %IncDir = ();
    detect_recursive_includes($AbsPath, $LibVersion);
    foreach my $HeaderPath (keys(%{$RecursiveIncludes{$LibVersion}{$AbsPath}}))
    {
        next if(not $HeaderPath);
        next if($MAIN_CPP_DIR and $HeaderPath=~/\A\Q$MAIN_CPP_DIR\E([\/\\]|\Z)/);
        my $Dir = get_dirname($HeaderPath);
        foreach my $Prefix (keys(%{$Header_Include_Prefix{$LibVersion}{$AbsPath}{$HeaderPath}}))
        {
            my $Dep = $Dir;
            if($Prefix)
            {
                if($OSgroup eq "windows")
                { # case insensitive seach on windows
                    if(not $Dep=~s/[\/\\]+\Q$Prefix\E\Z//ig) {
                        next;
                    }
                }
                elsif($OSgroup eq "macos")
                { # seach in frameworks
                    if(not $Dep=~s/[\/\\]+\Q$Prefix\E\Z//g)
                    {
                        if($HeaderPath=~/(.+\.framework)\/Headers\/([^\/]+)/)
                        {# frameworks
                            my ($HFramework, $HName) = ($1, $2);
                            $Dep = $HFramework;
                        }
                        else
                        {# mismatch
                            next;
                        }
                    }
                }
                elsif(not $Dep=~s/[\/\\]+\Q$Prefix\E\Z//g)
                { # Linux, FreeBSD
                    next;
                }
            }
            if(not $Dep)
            { # nothing to include
                next;
            }
            if(is_default_include_dir($Dep))
            { # included by the compiler
                next;
            }
            if(get_depth($Dep)==1)
            { # too short
                next;
            }
            if(isLibcDir($Dep))
            { # do NOT include /usr/include/{sys,bits}
                next;
            }
            $IncDir{$Dep}=1;
        }
    }
    $Cache{"get_HeaderDeps"}{$LibVersion}{$AbsPath} = sortIncPaths([keys(%IncDir)], $LibVersion);
    return @{$Cache{"get_HeaderDeps"}{$LibVersion}{$AbsPath}};
}

sub sortIncPaths($$)
{
    my ($ArrRef, $LibVersion) = @_;
    @{$ArrRef} = sort {$b cmp $a} @{$ArrRef};
    @{$ArrRef} = sort {get_depth($a)<=>get_depth($b)} @{$ArrRef};
    @{$ArrRef} = sort {$Header_Dependency{$LibVersion}{$b}<=>$Header_Dependency{$LibVersion}{$a}} @{$ArrRef};
    return $ArrRef;
}

sub joinPath($$) {
    return join($SLASH, @_);
}

sub get_namespace_additions($)
{
    my $NameSpaces = $_[0];
    my ($Additions, $AddNameSpaceId) = ("", 1);
    foreach my $NS (sort {$a=~/_/ <=> $b=~/_/} sort {lc($a) cmp lc($b)} keys(%{$NameSpaces}))
    {
        next if($SkipNameSpaces{$Version}{$NS});
        next if(not $NS or $NameSpaces->{$NS}==-1);
        next if($NS=~/(\A|::)iterator(::|\Z)/i);
        next if($NS=~/\A__/i);
        next if(($NS=~/\Astd::/ or $NS=~/\A(std|tr1|rel_ops|fcntl)\Z/) and not $STDCXX_TESTING);
        $NestedNameSpaces{$Version}{$NS} = 1;# for future use in reports
        my ($TypeDecl_Prefix, $TypeDecl_Suffix) = ();
        my @NS_Parts = split(/::/, $NS);
        next if($#NS_Parts==-1);
        next if($NS_Parts[0]=~/\A(random|or)\Z/);
        foreach my $NS_Part (@NS_Parts)
        {
            $TypeDecl_Prefix .= "namespace $NS_Part\{";
            $TypeDecl_Suffix .= "}";
        }
        my $TypeDecl = $TypeDecl_Prefix."typedef int tmp_add_type_$AddNameSpaceId;".$TypeDecl_Suffix;
        my $FuncDecl = "$NS\:\:tmp_add_type_$AddNameSpaceId tmp_add_func_$AddNameSpaceId(){return 0;};";
        $Additions.="  $TypeDecl\n  $FuncDecl\n";
        $AddNameSpaceId+=1;
    }
    return $Additions;
}

sub path_format($$)
{# forward slash to pass into MinGW GCC
    my ($Path, $Fmt) = @_;
    if($Fmt eq "windows") {
        $Path=~s/\//\\/g;
        $Path=lc($Path);
    }
    else {
        $Path=~s/\\/\//g;
    }
    return $Path;
}

sub inc_opt($$)
{
    my ($Path, $Style) = @_;
    if($Style eq "GCC")
    {# GCC options
        if($OSgroup eq "windows")
        {# to MinGW GCC
            return "-I\"".path_format($Path, "unix")."\"";
        }
        elsif($OSgroup eq "macos"
        and $Path=~/\.framework\Z/)
        {# to Apple's GCC
            return "-F".esc(get_dirname($Path));
        }
        else {
            return "-I".esc($Path);
        }
    }
    elsif($Style eq "CL") {
        return "/I \"$Path\"";
    }
    return "";
}

sub platformSpecs($)
{
    my $LibVersion = $_[0];
    my $Arch = getArch($LibVersion);
    if($OStarget eq "symbian")
    { # options for GCCE compiler
        my %Symbian_Opts = map {$_=>1} (
            "-D__GCCE__",
            "-DUNICODE",
            "-fexceptions",
            "-D__SYMBIAN32__",
            "-D__MARM_INTERWORK__",
            "-D_UNICODE",
            "-D__S60_50__",
            "-D__S60_3X__",
            "-D__SERIES60_3X__",
            "-D__EPOC32__",
            "-D__MARM__",
            "-D__EABI__",
            "-D__MARM_ARMV5__",
            "-D__SUPPORT_CPP_EXCEPTIONS__",
            "-march=armv5t",
            "-mapcs",
            "-mthumb-interwork",
            "-DEKA2",
            "-DSYMBIAN_ENABLE_SPLIT_HEADERS"
        );
        return join(" ", keys(%Symbian_Opts));
    }
    elsif($OSgroup eq "windows"
    and get_dumpmachine($GCC_PATH)=~/mingw/i)
    { # add options to MinGW compiler
      # to simulate the MSVC compiler
        my %MinGW_Opts = map {$_=>1} (
            "-D_WIN32",
            "-D_STDCALL_SUPPORTED",
            "-D__int64=\"long long\"",
            "-D__int32=int",
            "-D__int16=short",
            "-D__int8=char",
            "-D__possibly_notnullterminated=\" \"",
            "-D__nullterminated=\" \"",
            "-D__nullnullterminated=\" \"",
            "-D__w64=\" \"",
            "-D__ptr32=\" \"",
            "-D__ptr64=\" \"",
            "-D__forceinline=inline",
            "-D__inline=inline",
            "-D__uuidof(x)=IID()",
            "-D__try=",
            "-D__except(x)=",
            "-D__declspec(x)=__attribute__((x))",
            "-D__pragma(x)=",
            "-D_inline=inline",
            "-D__forceinline=__inline",
            "-D__stdcall=__attribute__((__stdcall__))",
            "-D__cdecl=__attribute__((__cdecl__))",
            "-D__fastcall=__attribute__((__fastcall__))",
            "-D__thiscall=__attribute__((__thiscall__))",
            "-D_stdcall=__attribute__((__stdcall__))",
            "-D_cdecl=__attribute__((__cdecl__))",
            "-D_fastcall=__attribute__((__fastcall__))",
            "-D_thiscall=__attribute__((__thiscall__))",
            "-DSHSTDAPI_(x)=x",
            "-D_MSC_EXTENSIONS",
            "-DSECURITY_WIN32",
            "-D_MSC_VER=1500",
            "-D_USE_DECLSPECS_FOR_SAL",
            "-D__noop=\" \"",
            "-DDECLSPEC_DEPRECATED=\" \"",
            "-D__builtin_alignof(x)=__alignof__(x)",
            "-DSORTPP_PASS");
        if($Arch eq "x86") {
            $MinGW_Opts{"-D_M_IX86=300"}=1;
        }
        elsif($Arch eq "x86_64") {
            $MinGW_Opts{"-D_M_AMD64=300"}=1;
        }
        elsif($Arch eq "ia64") {
            $MinGW_Opts{"-D_M_IA64=300"}=1;
        }
        return join(" ", keys(%MinGW_Opts));
    }
    return "";
}

my %C_Structure = map {$_=>1} (
# FIXME: Can't separate union and struct data types before dumping,
# so it sometimes cause compilation errors for unknown reason
# when trying to declare TYPE* tmp_add_class_N
# This is a list of such structures + list of other C structures
    "sigval",
    "sigevent",
    "sigaction",
    "sigvec",
    "sigstack",
    "timeval",
    "timezone",
    "rusage",
    "rlimit",
    "wait",
    "flock",
    "stat",
    "_stat",
    "stat32",
    "_stat32",
    "stat64",
    "_stat64",
    "_stati64",
    "if_nameindex",
    "usb_device",
    "sigaltstack",
    "sysinfo",
    "timeLocale",
    "tcp_debug",
    "rpc_createerr",
# Other C structures appearing in every dump
    "timespec",
    "random_data",
    "drand48_data",
    "_IO_marker",
    "_IO_FILE",
    "lconv",
    "sched_param",
    "tm",
    "itimerspec",
    "_pthread_cleanup_buffer",
    "fd_set",
    "siginfo"
);

sub getCompileCmd($$$)
{
    my ($Path, $Opt, $Inc) = @_;
    my $GccCall = $GCC_PATH;
    if($Opt) {
        $GccCall .= " ".$Opt;
    }
    $GccCall .= " -x ";
    if($OSgroup eq "macos") {
        $GccCall .= "objective-";
    }
    if(check_gcc_version($GCC_PATH, "4"))
    { # compile as "C++" header
      # to obtain complete dump using GCC 4.0
        $GccCall .= "c++-header";
    }
    else
    { # compile as "C++" source
      # GCC 3.3 cannot compile headers
        $GccCall .= "c++";
    }
    if(my $Opts = platformSpecs($Version))
    {# platform-specific options
        $GccCall .= " ".$Opts;
    }
    # allow extra qualifications
    # and other nonconformant code
    $GccCall .= " -fpermissive -w";
    if($NoStdInc)
    {
        $GccCall .= " -nostdinc";
        $GccCall .= " -nostdinc++";
    }
    if($CompilerOptions{$Version})
    { # user-defined options
        $GccCall .= " ".$CompilerOptions{$Version};
    }
    $GccCall .= " \"$Path\"";
    if($Inc)
    { # include paths
        $GccCall .= " ".$Inc;
    }
    return $GccCall;
}

sub getDump()
{
    if(not $GCC_PATH) {
        exitStatus("Error", "internal error - GCC path is not set");
    }
    my %HeaderElems = (
        # Types
        "stdio.h" => ["FILE", "va_list"],
        "stddef.h" => ["NULL"],
        "stdint.h" => ["uint32_t", "int32_t", "uint64_t"],
        "time.h" => ["time_t"],
        "sys/types.h" => ["ssize_t", "u_int32_t", "u_short", "u_char",
             "u_int", "off_t", "u_quad_t", "u_long", "size_t", "mode_t"],
        "unistd.h" => ["gid_t", "uid_t"],
        "stdbool.h" => ["_Bool"],
        "rpc/xdr.h" => ["bool_t"],
        "in_systm.h" => ["n_long", "n_short"],
        # Fields
        "arpa/inet.h" => ["fw_src", "ip_src"]
    );
    my %AutoPreamble = ();
    foreach (keys(%HeaderElems)) {
        foreach my $Elem (@{$HeaderElems{$_}}) {
            $AutoPreamble{$Elem}=$_;
        }
    }
    my $TmpHeaderPath = "$TMP_DIR/dump$Version.h";
    my $MHeaderPath = $TmpHeaderPath;
    open(LIB_HEADER, ">$TmpHeaderPath") || die ("can't open file \'$TmpHeaderPath\': $!\n");
    if(my $AddDefines = $Descriptor{$Version}{"Defines"})
    {
        $AddDefines=~s/\n\s+/\n  /g;
        print LIB_HEADER "\n  // add defines\n  ".$AddDefines."\n";
    }
    print LIB_HEADER "\n  // add includes\n";
    my @PreambleHeaders = keys(%{$Include_Preamble{$Version}});
    @PreambleHeaders = sort {int($Include_Preamble{$Version}{$a}{"Position"})<=>int($Include_Preamble{$Version}{$b}{"Position"})} @PreambleHeaders;
    foreach my $Header_Path (@PreambleHeaders) {
        print LIB_HEADER "  #include \"".path_format($Header_Path, "unix")."\"\n";
    }
    my @Headers = keys(%{$Registered_Headers{$Version}});
    @Headers = sort {int($Registered_Headers{$Version}{$a}{"Pos"})<=>int($Registered_Headers{$Version}{$b}{"Pos"})} @Headers;
    foreach my $Header_Path (@Headers)
    {
        next if($Include_Preamble{$Version}{$Header_Path});
        print LIB_HEADER "  #include \"".path_format($Header_Path, "unix")."\"\n";
    }
    close(LIB_HEADER);
    my $IncludeString = getIncString(getIncPaths(@PreambleHeaders, @Headers), "GCC");
    if($Debug)
    { # debug mode
        writeFile($DEBUG_PATH{$Version}."/recursive-includes.txt", Dumper(\%RecursiveIncludes));
        writeFile($DEBUG_PATH{$Version}."/include-paths.txt", Dumper($Cache{"get_HeaderDeps"}));
        writeFile($DEBUG_PATH{$Version}."/includes.txt", Dumper(\%Header_Includes));
        writeFile($DEBUG_PATH{$Version}."/default-includes.txt", Dumper(\%DefaultIncPaths));
    }
    # preprocessing stage
    checkPreprocessedUnit(callPreprocessor($TmpHeaderPath, $IncludeString, $Version));
    my $MContent = "";
    my $PreprocessCmd = getCompileCmd($TmpHeaderPath, "-E", $IncludeString);
    if($OStarget eq "windows"
    and get_dumpmachine($GCC_PATH)=~/mingw/i
    and $MinGWMode{$Version}!=-1)
    { # modify headers to compile by MinGW
        if(not $MContent)
        { # preprocessing
            $MContent = `$PreprocessCmd 2>$TMP_DIR/null`;
        }
        if($MContent=~s/__asm\s*(\{[^{}]*?\}|[^{};]*)//g)
        { # __asm { ... }
            $MinGWMode{$Version}=1;
        }
        if($MContent=~s/\s+(\/ \/.*?)\n/\n/g)
        { # comments after preprocessing
            $MinGWMode{$Version}=1;
        }
        if($MContent=~s/(\W)(0x[a-f]+|\d+)(i|ui)(8|16|32|64)(\W)/$1$2$5/g)
        { # 0xffui8
            $MinGWMode{$Version}=1;
        }
        if($MinGWMode{$Version}) {
            printMsg("INFO", "Using MinGW compatibility mode");
            $MHeaderPath = "$TMP_DIR/dump$Version.i";
        }
    }
    if(($COMMON_LANGUAGE{$Version} eq "C" or $CheckHeadersOnly)
    and $C99Mode{$Version}!=-1 and not $Cpp2003)
    { # rename "C++" keywords in "C" code
        if(not $MContent)
        { # preprocessing
            $MContent = `$PreprocessCmd 2>$TMP_DIR/null`;
        }
        my $RegExp_C = join("|", keys(%CppKeywords_C));
        my $RegExp_F = join("|", keys(%CppKeywords_F));
        my $RegExp_O = join("|", keys(%CppKeywords_O));
        while($MContent=~s/(\A|\n[^\#\/\n][^\n]*?|\n)(\*\s*|\s+|\@|\,|\()($RegExp_C|$RegExp_F)(\s*(\,|\)|\;|\-\>|\.|\:\s*\d))/$1$2c99_$3$4/g)
        { # MATCH:
          # int foo(int new, int class, int (*new)(int));
          # unsigned private: 8;
          # DO NOT MATCH:
          # #pragma GCC visibility push(default)
            $C99Mode{$Version} = 1;
        }
        if($MContent=~s/([^\w\s]|\w\s+)(?<!operator )(delete)(\s*(\())/$1c99_$2$3/g)
        { # MATCH:
          # int delete(...);
          # int explicit(...);
          # DO NOT MATCH:
          # void operator delete(...)
            $C99Mode{$Version} = 1;
        }
        if($MContent=~s/(\s+)($RegExp_O)(\s*(\;|\:))/$1c99_$2$3/g)
        { # MATCH:
          # int bool;
          # DO NOT MATCH:
          # bool X;
          # return *this;
          # throw;
            $C99Mode{$Version} = 1;
        }
        if($MContent=~s/(\s+)(operator)(\s*(\(\s*\)\s*[^\(\s]|\(\s*[^\)\s]))/$1c99_$2$3/g)
        { # MATCH:
          # int operator(...);
          # DO NOT MATCH:
          # int operator()(...);
            $C99Mode{$Version} = 1;
        }
        if($MContent=~s/([^\w\(\,\s]\s*|\s+)(operator)(\s*(\,\s*[^\(\s]|\)))/$1c99_$2$3/g)
        { # MATCH:
          # int foo(int operator);
          # int foo(int operator, int other);
          # DO NOT MATCH:
          # int operator,(...);
            $C99Mode{$Version} = 1;
        }
        if($MContent=~s/(\*\s*|\w\s+)(bool)(\s*(\,|\)))/$1c99_$2$3/g)
        { # MATCH:
          # int foo(gboolean *bool);
          # DO NOT MATCH:
          # void setTabEnabled(int index, bool);
            $C99Mode{$Version} = 1;
        }
        if($MContent=~s/(\w)([^\w\(\,\s]\s*|\s+)(this)(\s*(\,|\)))/$1$2c99_$3$4/g)
        { # MATCH:
          # int foo(int* this);
          # int bar(int this);
          # DO NOT MATCH:
          # baz(X, this);
            $C99Mode{$Version} = 1;
        }
        if($C99Mode{$Version}==1)
        { # try to change C++ "keyword" to "c99_keyword"
            printMsg("INFO", "Using C99 compatibility mode");
            $MHeaderPath = "$TMP_DIR/dump$Version.i";
        }
    }
    if($C99Mode{$Version}==1
    or $MinGWMode{$Version}==1)
    { # compile the corrected preprocessor output
        writeFile($MHeaderPath, $MContent);
    }
    if($COMMON_LANGUAGE{$Version} eq "C++")
    { # add classes and namespaces to the dump
        my $CHdump = "-fdump-class-hierarchy -c";
        if($C99Mode{$Version}==1
        or $MinGWMode{$Version}==1) {
            $CHdump .= " -fpreprocessed";
        }
        my $ClassHierarchyCmd = getCompileCmd($MHeaderPath, $CHdump, $IncludeString);
        chdir($TMP_DIR);
        system("$ClassHierarchyCmd >null 2>&1");
        chdir($ORIG_DIR);
        if(my $ClassDump = (cmd_find($TMP_DIR,"f","*.class",1))[0])
        {
            my %AddClasses = ();
            my $Content = readFile($ClassDump);
            foreach my $ClassInfo (split(/\n\n/, $Content))
            {
                if($ClassInfo=~/\AClass\s+(.+)\s*/i)
                {
                    my $CName = $1;
                    next if($CName=~/\A(__|_objc_|_opaque_)/);
                    $TUnit_NameSpaces{$Version}{$CName} = -1;
                    if($CName=~/\A[\w:]+\Z/)
                    { # classes
                        $AddClasses{$CName} = 1;
                    }
                    if($CName=~/(\w[\w:]*)::/)
                    { # namespaces
                        my $NS = $1;
                        if(not defined $TUnit_NameSpaces{$Version}{$NS}) {
                            $TUnit_NameSpaces{$Version}{$NS} = 1;
                        }
                    }
                }
                elsif($ClassInfo=~/\AVtable\s+for\s+(.+)\n((.|\n)+)\Z/i)
                { # read v-tables (advanced approach)
                    my ($CName, $VTable) = ($1, $2);
                    $ClassVTable_Content{$Version}{$CName} = $VTable;
                }
            }
            if($Debug)
            { # debug mode
                mkpath($DEBUG_PATH{$Version});
                copy($ClassDump, $DEBUG_PATH{$Version}."/class-hierarchy.txt");
            }
            unlink($ClassDump);
            if(my $NS_Add = get_namespace_additions($TUnit_NameSpaces{$Version}))
            { # GCC on all supported platforms does not include namespaces to the dump by default
                appendFile($MHeaderPath, "\n  // add namespaces\n".$NS_Add);
            }
            # some GCC versions don't include class methods to the TU dump by default
            my ($AddClass, $ClassNum) = ("", 0);
            foreach my $CName (sort keys(%AddClasses))
            {
                next if($C_Structure{$CName});
                next if(not $STDCXX_TESTING and $CName=~/\Astd::/);
                next if(($CName=~tr![:]!!)>2);
                next if($SkipTypes{$Version}{$CName});
                if($CName=~/\A(.+)::[^:]+\Z/
                and $AddClasses{$1}) {
                    next;
                }
                $AddClass .= "  $CName* tmp_add_class_".($ClassNum++).";\n";
            }
            appendFile($MHeaderPath, "\n  // add classes\n".$AddClass);
        }
    }
    writeLog($Version, "Temporary header file \'$TmpHeaderPath\' with the following content will be compiled to create GCC translation unit dump:\n".readFile($TmpHeaderPath)."\n");
    # create TU dump
    my $TUdump = "-fdump-translation-unit -fkeep-inline-functions -c";
    if($C99Mode{$Version}==1
    or $MinGWMode{$Version}==1) {
        $TUdump .= " -fpreprocessed";
    }
    my $SyntaxTreeCmd = getCompileCmd($MHeaderPath, $TUdump, $IncludeString);
    writeLog($Version, "The GCC parameters:\n  $SyntaxTreeCmd\n\n");
    chdir($TMP_DIR);
    system($SyntaxTreeCmd." >$TMP_DIR/tu_errors 2>&1");
    if($?)
    { # failed to compile, but the TU dump still can be created
        my $Errors = readFile("$TMP_DIR/tu_errors");
        if($Errors=~/c99_/)
        { # disable c99 mode
            $C99Mode{$Version}=-1;
            printMsg("INFO", "Disabling C99 compatibility mode");
            resetLogging($Version);
            $TMP_DIR = tempdir(CLEANUP=>1);
            return getDump();
        }
        elsif($AutoPreambleMode{$Version}!=-1
        and my $TErrors = $Errors)
        {
            my %Types = ();
            while($TErrors=~s/error\:\s*(field\s*|)\W+(.+?)\W+//)
            { # error: 'FILE' has not been declared
                $Types{$2}=1;
            }
            my %AddHeaders = ();
            foreach my $Type (keys(%Types))
            {
                if(my $Header = $AutoPreamble{$Type}) {
                    $AddHeaders{path_format($Header, $OSgroup)}=$Type;
                }
            }
            if(my @Headers = sort {$b cmp $a} keys(%AddHeaders))
            { # sys/types.h should be the first
                foreach my $Num (0 .. $#Headers)
                {
                    my $Name = $Headers[$Num];
                    if(my $Path = identify_header($Name, $Version))
                    { # add automatic preamble headers
                        if(defined $Include_Preamble{$Version}{$Path})
                        { # already added
                            next;
                        }
                        $Include_Preamble{$Version}{$Path}{"Position"} = keys(%{$Include_Preamble{$Version}});
                        my $Type = $AddHeaders{$Name};
                        printMsg("INFO", "Add \'$Name\' preamble header for \'$Type\'");
                    }
                }
                $AutoPreambleMode{$Version}=-1;
                resetLogging($Version);
                $TMP_DIR = tempdir(CLEANUP=>1);
                return getDump();
            }
        }
        elsif($MinGWMode{$Version}!=-1)
        {
            $MinGWMode{$Version}=-1;
            resetLogging($Version);
            $TMP_DIR = tempdir(CLEANUP=>1);
            return getDump();
        }
        # FIXME: handle other errors and try to recompile
        writeLog($Version, $Errors);
        printMsg("ERROR", "some errors occurred when compiling headers");
        printErrorLog($Version);
        $COMPILE_ERRORS = $ERROR_CODE{"Compile_Error"};
        writeLog($Version, "\n");# new line
    }
    chdir($ORIG_DIR);
    unlink($TmpHeaderPath, $MHeaderPath);
    return (cmd_find($TMP_DIR,"f","*.tu",1))[0];
}

sub cmd_file($)
{
    my $Path = $_[0];
    return "" if(not $Path or not -e $Path);
    if(my $CmdPath = get_CmdPath("file")) {
        return `$CmdPath -b \"$Path\"`;
    }
    return "";
}

sub getIncString($$)
{
    my ($ArrRef, $Style) = @_;
    return if(not $ArrRef or $#{$ArrRef}<0);
    my $String = "";
    foreach (@{$ArrRef}) {
        $String .= " ".inc_opt($_, $Style);
    }
    return $String;
}

sub getIncPaths(@)
{
    my @HeaderPaths = @_;
    my @IncPaths = ();
    if($INC_PATH_AUTODETECT{$Version})
    { # auto-detecting dependencies
        my %Includes = ();
        foreach my $HPath (@HeaderPaths)
        {
            foreach my $Dir (get_HeaderDeps($HPath, $Version))
            {
                if($Skip_Include_Paths{$Version}{$Dir}) {
                    next;
                }
                $Includes{$Dir}=1;
            }
        }
        foreach my $Dir (keys(%{$Add_Include_Paths{$Version}}))
        { # added by user
            next if($Includes{$Dir});
            push(@IncPaths, $Dir);
        }
        foreach my $Dir (@{sortIncPaths([keys(%Includes)], $Version)}) {
            push(@IncPaths, $Dir);
        }
    }
    else
    { # user-defined paths
        foreach my $Dir (sort {get_depth($a)<=>get_depth($b)}
        sort {$b cmp $a} keys(%{$Include_Paths{$Version}})) {
            push(@IncPaths, $Dir);
        }
    }
    return \@IncPaths;
}

sub callPreprocessor($$$)
{
    my ($Path, $Inc, $LibVersion) = @_;
    return "" if(not $Path or not -f $Path);
    my $IncludeString=$Inc;
    if(not $Inc) {
        $IncludeString = getIncString(getIncPaths($Path), "GCC");
    }
    my $Cmd = getCompileCmd($Path, "-dD -E", $IncludeString);
    my $Path = "$TMP_DIR/preprocessed";
    system("$Cmd >$Path 2>$TMP_DIR/null");
    return $Path;
}

sub cmd_find($$$$)
{ # native "find" is much faster than File::Find (~6x)
  # also the File::Find doesn't support --maxdepth N option
  # so using the cross-platform wrapper for the native one
    my ($Path, $Type, $Name, $MaxDepth) = @_;
    return () if(not $Path or not -e $Path);
    if($OSgroup eq "windows")
    {
        my $DirCmd = get_CmdPath("dir");
        if(not $DirCmd) {
            exitStatus("Not_Found", "can't find \"dir\" command");
        }
        $Path=~s/[\\]+\Z//;
        $Path = get_abs_path($Path);
        $Path = path_format($Path, $OSgroup);
        my $Cmd = $DirCmd." \"$Path\" /B /O";
        if($MaxDepth!=1) {
            $Cmd .= " /S";
        }
        if($Type eq "d") {
            $Cmd .= " /AD";
        }
        my @Files = ();
        if($Name)
        { # FIXME: how to search file names in MS shell?
            $Name=~s/\*/.*/g if($Name!~/\]/);
            foreach my $File (split(/\n/, `$Cmd`))
            {
                if($File=~/$Name\Z/i) {
                    push(@Files, $File);
                }
            }
        }
        else {
            @Files = split(/\n/, `$Cmd 2>$TMP_DIR/null`);
        }
        my @AbsPaths = ();
        foreach my $File (@Files)
        {
            if(not is_abs($File)) {
                $File = joinPath($Path, $File);
            }
            if($Type eq "f" and not -f $File)
            { # skip dirs
                next;
            }
            push(@AbsPaths, path_format($File, $OSgroup));
        }
        if($Type eq "d") {
            push(@AbsPaths, $Path);
        }
        return @AbsPaths;
    }
    else
    {
        my $FindCmd = get_CmdPath("find");
        if(not $FindCmd) {
            exitStatus("Not_Found", "can't find a \"find\" command");
        }
        $Path = get_abs_path($Path);
        if(-d $Path and -l $Path
        and $Path!~/\/\Z/)
        { # for directories that are symlinks
            $Path.="/";
        }
        my $Cmd = $FindCmd." \"$Path\"";
        if($MaxDepth) {
            $Cmd .= " -maxdepth $MaxDepth";
        }
        if($Type) {
            $Cmd .= " -type $Type";
        }
        if($Name)
        { # file name
            if($Name=~/\]/) {
                $Cmd .= " -regex \"$Name\"";
            }
            else {
                $Cmd .= " -name \"$Name\"";
            }
        }
        return split(/\n/, `$Cmd 2>$TMP_DIR/null`);
    }
}

sub unpackDump($)
{
    my $Path = $_[0];
    return "" if(not $Path or not -e $Path);
    $Path = get_abs_path($Path);
    $Path = path_format($Path, $OSgroup);
    my ($Dir, $FileName) = separate_path($Path);
    my $UnpackDir = $TMP_DIR."/unpack";
    rmtree($UnpackDir);
    mkpath($UnpackDir);
    if($FileName=~s/\Q.zip\E\Z//g)
    { # *.zip
        my $UnzipCmd = get_CmdPath("unzip");
        if(not $UnzipCmd) {
            exitStatus("Not_Found", "can't find \"unzip\" command");
        }
        chdir($UnpackDir);
        system("$UnzipCmd \"$Path\" >$UnpackDir/contents.txt");
        if($?) {
            exitStatus("Error", "can't extract \'$Path\'");
        }
        chdir($ORIG_DIR);
        my @Contents = ();
        foreach (split("\n", readFile("$UnpackDir/contents.txt")))
        {
            if(/inflating:\s*([^\s]+)/) {
                push(@Contents, $1);
            }
        }
        if(not @Contents) {
            exitStatus("Error", "can't extract \'$Path\'");
        }
        return joinPath($UnpackDir, $Contents[0]);
    }
    elsif($FileName=~s/\Q.tar.gz\E\Z//g)
    { # *.tar.gz
        if($OSgroup eq "windows")
        { # -xvzf option is not implemented in tar.exe (2003)
          # use "gzip.exe -k -d -f" + "tar.exe -xvf" instead
            my $TarCmd = get_CmdPath("tar");
            if(not $TarCmd) {
                exitStatus("Not_Found", "can't find \"tar\" command");
            }
            my $GzipCmd = get_CmdPath("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\" >$UnpackDir/contents.txt");
            if($?) {
                exitStatus("Error", "can't extract \'$Path\'");
            }
            chdir($ORIG_DIR);
            unlink($Dir."/".$FileName.".tar");
            my @Contents = split("\n", readFile("$UnpackDir/contents.txt"));
            if(not @Contents) {
                exitStatus("Error", "can't extract \'$Path\'");
            }
            return joinPath($UnpackDir, $Contents[0]);
        }
        else
        { # Unix
            my $TarCmd = get_CmdPath("tar");
            if(not $TarCmd) {
                exitStatus("Not_Found", "can't find \"tar\" command");
            }
            chdir($UnpackDir);
            system("$TarCmd -xvzf \"$Path\" >$UnpackDir/contents.txt");
            if($?) {
                exitStatus("Error", "can't extract \'$Path\'");
            }
            chdir($ORIG_DIR);
            # The content file name may be different
            # from the package file name
            my @Contents = split("\n", readFile("$UnpackDir/contents.txt"));
            if(not @Contents) {
                exitStatus("Error", "can't extract \'$Path\'");
            }
            return joinPath($UnpackDir, $Contents[0]);
        }
    }
}

sub createArchive($$)
{
    my ($Path, $To) = @_;
    if(not $Path or not -e $Path
    or not -d $To) {
        return "";
    }
    my ($From, $Name) = separate_path($Path);
    if($OSgroup eq "windows")
    { # *.zip
        my $ZipCmd = get_CmdPath("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\" >$TMP_DIR/null");
        if($?)
        { # cannot allocate memory (or other problems with "zip")
            unlink($Path);
            exitStatus("Error", "can't pack the ABI dump: ".$!);
        }
        chdir($ORIG_DIR);
        unlink($Path);
        return $Pkg;
    }
    else
    { # *.tar.gz
        my $TarCmd = get_CmdPath("tar");
        if(not $TarCmd) {
            exitStatus("Not_Found", "can't find \"tar\"");
        }
        my $GzipCmd = get_CmdPath("gzip");
        if(not $GzipCmd) {
            exitStatus("Not_Found", "can't find \"gzip\"");
        }
        my $Pkg = abs_path($To)."/".$Name.".tar.gz";
        unlink($Pkg);
        chdir($From);
        system($TarCmd, "-czf", $Pkg, $Name);
        if($?)
        { # cannot allocate memory (or other problems with "tar")
            unlink($Path);
            exitStatus("Error", "can't pack the ABI dump: ".$!);
        }
        chdir($ORIG_DIR);
        unlink($Path);
        return $To."/".$Name.".tar.gz";
    }
}

sub is_header_file($)
{
    if($_[0]=~/\.($HEADER_EXT)\Z/i) {
        return $_[0];
    }
    return 0;
}

sub is_not_header($)
{
    if($_[0]=~/\.\w+\Z/
    and $_[0]!~/\.($HEADER_EXT)\Z/i) {
        return 1;
    }
    return 0;
}

sub is_header($$$)
{
    my ($Header, $UserDefined, $LibVersion) = @_;
    return 0 if(-d $Header);
    if(-f $Header) {
        $Header = get_abs_path($Header);
    }
    else
    {
        if(is_abs($Header))
        { # incorrect absolute path
            return 0;
        }
        if(my $HPath = identify_header($Header, $LibVersion)) {
            $Header = $HPath;
        }
        else
        { # can't find header
            return 0;
        }
    }
    if($Header=~/\.\w+\Z/)
    { # have an extension
        return is_header_file($Header);
    }
    else
    {
        if($UserDefined==2)
        { # specified on the command line
            if(cmd_file($Header)!~/HTML|XML/i) {
                return $Header;
            }
        }
        elsif($UserDefined)
        { # specified in the XML-descriptor
          # header file without an extension
            return $Header;
        }
        else
        {
            if(cmd_file($Header)=~/C[\+]*\s+program/i)
            { # !~/HTML|XML|shared|dynamic/i
                return $Header;
            }
        }
    }
    return 0;
}

sub detectTargetHeaders($)
{
    my $LibVersion = $_[0];
    foreach my $RegHeader (keys(%{$Registered_Headers{$LibVersion}}))
    {
        my $RegDir = get_dirname($RegHeader);
        $TargetHeaders{$LibVersion}{get_filename($RegHeader)}=1;
        foreach my $RecInc (keys(%{$RecursiveIncludes{$LibVersion}{$RegHeader}}))
        {
            my $Dir = get_dirname($RecInc);
            if($Dir=~/\A$RegDir([\/\\]|\Z)/)
            { # in the same directory
                $TargetHeaders{$LibVersion}{get_filename($RecInc)}=1;
            }
        }
    }
}

sub readHeaders($)
{
    $Version = $_[0];
    printMsg("INFO", "checking header(s) ".$Descriptor{$Version}{"Version"}." ...");
    my $DumpPath = getDump();
    if(not $DumpPath) {
        exitStatus("Cannot_Compile", "can't compile header(s)");
    }
    if($Debug)
    { # debug mode
        mkpath($DEBUG_PATH{$Version});
        copy($DumpPath, $DEBUG_PATH{$Version}."/translation-unit.txt");
    }
    getInfo($DumpPath);
    if($CheckHeadersOnly)
    { # --headers-only mode
        detectTargetHeaders($Version);
    }
}

sub prepareTypes($)
{
    my $LibVersion = $_[0];
    if(cmpVersions($UsedDump{$LibVersion}{"V"}, "2.0")<0)
    { # support for old ABI dumps
      # type names have been corrected in ACC 1.22 (dump 2.0 format)
        foreach my $TypeDeclId (keys(%{$TypeInfo{$LibVersion}}))
        {
            foreach my $TypeId (keys(%{$TypeInfo{$LibVersion}{$TypeDeclId}}))
            {
                my $TName = $TypeInfo{$LibVersion}{$TypeDeclId}{$TypeId}{"Name"};
                if($TName=~/\A(\w+)::(\w+)/) {
                    my ($P1, $P2) = ($1, $2);
                    if($P1 eq $P2) {
                        $TName=~s/\A$P1:\:$P1(\W)/$P1$1/;
                    }
                    else {
                        $TName=~s/\A(\w+:\:)$P2:\:$P2(\W)/$1$P2$2/;
                    }
                }
                $TypeInfo{$LibVersion}{$TypeDeclId}{$TypeId}{"Name"} = $TName;
            }
        }
    }
    if(cmpVersions($UsedDump{$LibVersion}{"V"}, "2.5")<0)
    { # support for old ABI dumps
      # V < 2.5: array size == "number of elements"
      # V >= 2.5: array size in bytes
        foreach my $TypeDeclId (sort {int($a)<=>int($b)} keys(%{$TypeInfo{$LibVersion}}))
        {
            foreach my $TypeId (sort {int($a)<=>int($b)} keys(%{$TypeInfo{$LibVersion}{$TypeDeclId}}))
            {
                my %Type = get_PureType($TypeDeclId, $TypeId, $LibVersion);
                if($Type{"Type"} eq "Array")
                {
                    if($Type{"Size"})
                    { # array[N]
                        my %Base = get_OneStep_BaseType($Type{"TDid"}, $Type{"Tid"}, $LibVersion);
                        $TypeInfo{$LibVersion}{$TypeDeclId}{$TypeId}{"Size"} = $Type{"Size"}*$Base{"Size"};
                    }
                    else
                    { # array[] is a pointer
                        $TypeInfo{$LibVersion}{$TypeDeclId}{$TypeId}{"Size"} = $WORD_SIZE{$LibVersion};
                    }
                }
            }
        }
    }
    my $V2 = ($LibVersion==1)?2:1;
    if(cmpVersions($UsedDump{1}{"V"}, "2.7")<0)
    { # support for old ABI dumps
      # size of "method ptr" corrected in 2.7
        foreach my $TypeDeclId (sort {int($a)<=>int($b)} keys(%{$TypeInfo{$LibVersion}}))
        {
            foreach my $TypeId (sort {int($a)<=>int($b)} keys(%{$TypeInfo{$LibVersion}{$TypeDeclId}}))
            {
                my %PureType = get_PureType($TypeDeclId, $TypeId, $LibVersion);
                if($PureType{"Type"} eq "MethodPtr")
                {
                    my %Type = get_Type($TypeDeclId, $TypeId, $LibVersion);
                    my $TypeId_2 = getTypeIdByName($PureType{"Name"}, $V2);
                    my %Type2 = get_Type($Tid_TDid{$V2}{$TypeId_2}, $TypeId_2, $V2);
                    if($Type{"Size"} ne $Type2{"Size"}) {
                        $TypeInfo{$LibVersion}{$TypeDeclId}{$TypeId}{"Size"} = $Type2{"Size"};
                    }
                }
            }
        }
    }
}

sub prepareInterfaces($)
{
    my $LibVersion = $_[0];
    my $Remangle = 0;
    if(($UsedDump{1}{"V"} and cmpVersions($UsedDump{1}{"V"}, "2.10")<0)
    or ($UsedDump{2}{"V"} and cmpVersions($UsedDump{2}{"V"}, "2.10")<0))
    { # different formats
        $Remangle = 1;
    }
    if($CheckHeadersOnly)
    { # different languages
        if($UserLang)
        { # --lang=LANG for both versions
            if(($UsedDump{1}{"V"} and $UserLang ne $UsedDump{1}{"L"})
            or ($UsedDump{2}{"V"} and $UserLang ne $UsedDump{2}{"L"}))
            {
                if($UserLang eq "C++")
                { # remangle symbols
                    $Remangle = 1;
                }
                elsif($UserLang eq "C")
                { # remove mangling
                    $Remangle = -1;
                }
            }
        }
    }
    foreach my $FuncInfoId (sort {int($b)<=>int($a)} keys(%{$SymbolInfo{$LibVersion}}))
    { # reverse order: D0, D1, D2, D0 (artificial, GCC < 4.5), C1, C2
        if($SymbolInfo{$LibVersion}{$FuncInfoId}{"Destructor"})
        {
            if(defined $SymbolInfo{$LibVersion}{$FuncInfoId}{"Param"}
            and keys(%{$SymbolInfo{$LibVersion}{$FuncInfoId}{"Param"}})
            and $SymbolInfo{$LibVersion}{$FuncInfoId}{"Param"}{"0"}{"name"})
            { # support for old GCC < 4.5: skip artificial ~dtor(int __in_chrg)
              # + support for old ABI dumps
                next;
            }
        }
        my $MnglName = $SymbolInfo{$LibVersion}{$FuncInfoId}{"MnglName"};
        if($Remangle==1)
        { # support for old ABI dumps: some symbols are not mangled in old dumps
          # mangle both sets of symbols (old and new)
          # NOTE: remangling all symbols by the same mangler
            if($MnglName=~/\A_ZN(V|)K/)
            { # mangling may be incorrect on old ABI dumps
              # because of absent "Const" attribute
                $SymbolInfo{$LibVersion}{$FuncInfoId}{"Const"} = 1;
            }
            if($MnglName=~/\A_ZN(K|)V/)
            { # mangling may be incorrect on old ABI dumps
              # because of absent "Volatile" attribute
                $SymbolInfo{$LibVersion}{$FuncInfoId}{"Volatile"} = 1;
            }
            if(($SymbolInfo{$LibVersion}{$FuncInfoId}{"Class"} and ($MnglName!~/\A_Z/ or not link_symbol($MnglName, $LibVersion, "-Deps")))
            or (not $SymbolInfo{$LibVersion}{$FuncInfoId}{"Class"} and $CheckHeadersOnly))
            { # GCC >= 4.0
              # remangling C++-functions (not mangled in the TU dump)
              # remangling broken C++-methods (without a mangled name)
              # remangling all inline virtual C++-methods
                if($MnglName = mangle_symbol($FuncInfoId, $LibVersion, "GCC"))
                {
                    $SymbolInfo{$LibVersion}{$FuncInfoId}{"MnglName"} = $MnglName;
                    $MangledNames{$LibVersion}{$MnglName} = 1;
                }
            }
        }
        elsif($Remangle==-1)
        { # remove mangling
            $MnglName = "";
            $SymbolInfo{$LibVersion}{$FuncInfoId}{"MnglName"} = "";
        }
        if(not $MnglName)
        { # ABI dumps don't contain mangled names for C-functions
            $MnglName = $SymbolInfo{$LibVersion}{$FuncInfoId}{"ShortName"};
            $SymbolInfo{$LibVersion}{$FuncInfoId}{"MnglName"} = $MnglName;
        }
        if(not $MnglName) {
            next;
        }
        if(not $CompleteSignature{$LibVersion}{$MnglName}{"MnglName"})
        { # NOTE: global data may enter here twice
            %{$CompleteSignature{$LibVersion}{$MnglName}} = %{$SymbolInfo{$LibVersion}{$FuncInfoId}};
        }
        if($UsedDump{$LibVersion}{"V"}
        and cmpVersions($UsedDump{$LibVersion}{"V"}, "2.6")<0)
        { # support for old dumps
          # add "Volatile" attribute
            if($MnglName=~/_Z(K|)V/) {
                $CompleteSignature{$LibVersion}{$MnglName}{"Volatile"}=1;
            }
        }
        # symbol and its symlink have same signatures
        if($SymVer{$LibVersion}{$MnglName}) {
            %{$CompleteSignature{$LibVersion}{$SymVer{$LibVersion}{$MnglName}}} = %{$SymbolInfo{$LibVersion}{$FuncInfoId}};
        }
        delete($SymbolInfo{$LibVersion}{$FuncInfoId});
    }
    if($COMMON_LANGUAGE{$LibVersion} eq "C++" or $OSgroup eq "windows") {
        translateSymbols(keys(%{$CompleteSignature{$LibVersion}}), $LibVersion);
    }
    if($ExtendedCheck)
    { # --ext option
        addExtension($LibVersion);
    }
    if(not keys(%{$CompleteSignature{$LibVersion}}))
    { # check if input is valid
        if(not $ExtendedCheck and not $CheckObjectsOnly)
        {
            if($CheckHeadersOnly) {
                exitStatus("Empty_Set", "the set of public symbols is empty (".$Descriptor{$LibVersion}{"Version"}.")");
            }
            else {
                exitStatus("Empty_Intersection", "the sets of public symbols in headers and libraries have empty intersection (".$Descriptor{$LibVersion}{"Version"}.")");
            }
        }
    }
    foreach my $MnglName (keys(%{$CompleteSignature{$LibVersion}}))
    { # detect allocable classes with public exported constructors
      # or classes with auto-generated or inline-only constructors
        if(my $ClassId = $CompleteSignature{$LibVersion}{$MnglName}{"Class"})
        {
            my $ClassName = get_TypeName($ClassId, $LibVersion);
            if($CompleteSignature{$LibVersion}{$MnglName}{"Constructor"}
            and not $CompleteSignature{$LibVersion}{$MnglName}{"InLine"})
            { # Class() { ... } will not be exported
                if(not $CompleteSignature{$LibVersion}{$MnglName}{"Private"})
                {
                    if(link_symbol($MnglName, $LibVersion, "-Deps")) {
                        $AllocableClass{$LibVersion}{$ClassName} = 1;
                    }
                }
            }
            if(not $CompleteSignature{$LibVersion}{$MnglName}{"Private"})
            { # all imported class methods
                if($CheckHeadersOnly)
                {
                    if(not $CompleteSignature{$LibVersion}{$MnglName}{"InLine"}
                    or $CompleteSignature{$LibVersion}{$MnglName}{"Virt"})
                    {# all symbols except non-virtual inline
                        $ClassMethods{$LibVersion}{$ClassName}{$MnglName} = 1;
                    }
                }
                elsif(link_symbol($MnglName, $LibVersion, "-Deps"))
                { # all symbols
                    $ClassMethods{$LibVersion}{$ClassName}{$MnglName} = 1;
                }
            }
            $ClassToId{$LibVersion}{$ClassName} = $ClassId;
        }
        if(my $RetId = $CompleteSignature{$LibVersion}{$MnglName}{"Return"})
        {
            my %Base = get_BaseType($Tid_TDid{$LibVersion}{$RetId}, $RetId, $LibVersion);
            if($Base{"Type"}=~/Struct|Class/)
            {
                my $Name = get_TypeName($Base{"Tid"}, $LibVersion);
                if($Name=~/<([^<>\s]+)>/)
                {
                    if(my $Tid = getTypeIdByName($1, $LibVersion)) {
                        $ReturnedClass{$LibVersion}{$Tid} = 1;
                    }
                }
                else {
                    $ReturnedClass{$LibVersion}{$Base{"Tid"}} = 1;
                }
            }
        }
        foreach my $Num (keys(%{$CompleteSignature{$LibVersion}{$MnglName}{"Param"}}))
        {
            my $PId = $CompleteSignature{$LibVersion}{$MnglName}{"Param"}{$Num}{"type"};
            if(get_PointerLevel($Tid_TDid{1}{$PId}, $PId, $LibVersion)>=1)
            {
                my %Base = get_BaseType($Tid_TDid{$LibVersion}{$PId}, $PId, $LibVersion);
                if($Base{"Type"}=~/Struct|Class/)
                {
                    $ParamClass{$LibVersion}{$Base{"Tid"}}{$MnglName} = 1;
                    foreach my $SubId (get_sub_classes($Base{"Tid"}, $LibVersion, 1))
                    { # mark all derived classes
                        $ParamClass{$LibVersion}{$SubId}{$MnglName} = 1;
                    }
                }
            }
        }
    }
    foreach my $MnglName (keys(%{$VTableClass{$LibVersion}}))
    { # reconstruct header name for v-tables
        if($MnglName=~/\A_ZTV/)
        {
            if(my $ClassName = $VTableClass{$LibVersion}{$MnglName})
            {
                if(my $ClassId = $TName_Tid{$LibVersion}{$ClassName}) {
                    $CompleteSignature{$LibVersion}{$MnglName}{"Header"} = get_TypeAttr($ClassId, $LibVersion, "Header");
                }
            }
        }
    }
}

sub addExtension($)
{
    my $LibVersion = $_[0];
    foreach my $TDid (keys(%{$TypeInfo{$LibVersion}}))
    {
        foreach my $Tid (keys(%{$TypeInfo{$LibVersion}{$TDid}}))
        {
            my $TType = $TypeInfo{$LibVersion}{$TDid}{$Tid}{"Type"};
            if($TType=~/Struct|Union|Enum|Class/)
            {
                my $HName = $TypeInfo{$LibVersion}{$TDid}{$Tid}{"Header"};
                if(not $HName or isBuiltIn($HName)) {
                    next;
                }
                my $TName = $TypeInfo{$LibVersion}{$TDid}{$Tid}{"Name"};
                if(isAnon($TName))
                { # anon-struct-header.h-265
                    next;
                }
                my $FuncName = "external_func_".$TName;
                $ExtendedFuncs{$FuncName}=1;
                my %Attrs = (
                    "Header" => "extended.h",
                    "ShortName" => $FuncName,
                    "MnglName" => $FuncName,
                    "Param" => { "0" => { "type"=>$Tid, "name"=>"p1" } }
                );
                %{$CompleteSignature{$LibVersion}{$FuncName}} = %Attrs;
                register_TypeUsing($TDid, $Tid, $LibVersion);
                $GeneratedSymbols{$FuncName}=1;
                $CheckedSymbols{$FuncName}=1;
            }
        }
    }
    my $ConstFunc = "external_func_0";
    $GeneratedSymbols{$ConstFunc}=1;
    $CheckedSymbols{$ConstFunc}=1;
}

sub formatDump($)
{ # remove unnecessary data from the ABI dump
    my $LibVersion = $_[0];
    foreach my $FuncInfoId (keys(%{$SymbolInfo{$LibVersion}}))
    {
        my $MnglName = $SymbolInfo{$LibVersion}{$FuncInfoId}{"MnglName"};
        if(not $MnglName) {
            delete($SymbolInfo{$LibVersion}{$FuncInfoId});
            next;
        }
        if($MnglName eq $SymbolInfo{$LibVersion}{$FuncInfoId}{"ShortName"}) {
            delete($SymbolInfo{$LibVersion}{$FuncInfoId}{"MnglName"});
        }
        if(not is_target_header($SymbolInfo{$LibVersion}{$FuncInfoId}{"Header"}))
        { # user-defined header
            delete($SymbolInfo{$LibVersion}{$FuncInfoId});
            next;
        }
        if(not link_symbol($MnglName, $LibVersion, "-Deps")
        and not $SymbolInfo{$LibVersion}{$FuncInfoId}{"Virt"}
        and not $SymbolInfo{$LibVersion}{$FuncInfoId}{"PureVirt"})
        { # removing src only and all external functions
            if(not $CheckHeadersOnly) {
                delete($SymbolInfo{$LibVersion}{$FuncInfoId});
                next;
            }
        }
        if(not symbolFilter($MnglName, $LibVersion, "Public")) {
            delete($SymbolInfo{$LibVersion}{$FuncInfoId});
            next;
        }
        my %FuncInfo = %{$SymbolInfo{$LibVersion}{$FuncInfoId}};
        register_TypeUsing($Tid_TDid{$LibVersion}{$FuncInfo{"Return"}}, $FuncInfo{"Return"}, $LibVersion);
        register_TypeUsing($Tid_TDid{$LibVersion}{$FuncInfo{"Class"}}, $FuncInfo{"Class"}, $LibVersion);
        foreach my $Param_Pos (keys(%{$FuncInfo{"Param"}}))
        {
            my $Param_TypeId = $FuncInfo{"Param"}{$Param_Pos}{"type"};
            register_TypeUsing($Tid_TDid{$LibVersion}{$Param_TypeId}, $Param_TypeId, $LibVersion);
        }
        if(not keys(%{$SymbolInfo{$LibVersion}{$FuncInfoId}{"Param"}})) {
            delete($SymbolInfo{$LibVersion}{$FuncInfoId}{"Param"});
        }
    }
    foreach my $TDid (keys(%{$TypeInfo{$LibVersion}}))
    {
        if(not keys(%{$TypeInfo{$LibVersion}{$TDid}})) {
            delete($TypeInfo{$LibVersion}{$TDid});
        }
        else
        {
            foreach my $Tid (keys(%{$TypeInfo{$LibVersion}{$TDid}}))
            {
                if(not $UsedType{$LibVersion}{$TDid}{$Tid})
                {
                    delete($TypeInfo{$LibVersion}{$TDid}{$Tid});
                    if(not keys(%{$TypeInfo{$LibVersion}{$TDid}})) {
                        delete($TypeInfo{$LibVersion}{$TDid});
                    }
                    if($Tid_TDid{$LibVersion}{$Tid} eq $TDid) {
                        delete($Tid_TDid{$LibVersion}{$Tid});
                    }
                }
                else
                { # clean attributes
                    if(not $TypeInfo{$LibVersion}{$TDid}{$Tid}{"TDid"}) {
                        delete($TypeInfo{$LibVersion}{$TDid}{$Tid}{"TDid"});
                    }
                    if(not $TypeInfo{$LibVersion}{$TDid}{$Tid}{"NameSpace"}) {
                        delete($TypeInfo{$LibVersion}{$TDid}{$Tid}{"NameSpace"});
                    }
                    if(defined $TypeInfo{$LibVersion}{$TDid}{$Tid}{"BaseType"}
                    and not $TypeInfo{$LibVersion}{$TDid}{$Tid}{"BaseType"}{"TDid"}) {
                        delete($TypeInfo{$LibVersion}{$TDid}{$Tid}{"BaseType"}{"TDid"});
                    }
                    if(not $TypeInfo{$LibVersion}{$TDid}{$Tid}{"Header"}) {
                        delete($TypeInfo{$LibVersion}{$TDid}{$Tid}{"Header"});
                    }
                    if(not $TypeInfo{$LibVersion}{$TDid}{$Tid}{"Line"}) {
                        delete($TypeInfo{$LibVersion}{$TDid}{$Tid}{"Line"});
                    }
                    if(not $TypeInfo{$LibVersion}{$TDid}{$Tid}{"Size"}) {
                        delete($TypeInfo{$LibVersion}{$TDid}{$Tid}{"Size"});
                    }
                }
            }
        }
    }
}

sub register_TypeUsing($$$)
{
    my ($TypeDeclId, $TypeId, $LibVersion) = @_;
    return if($UsedType{$LibVersion}{$TypeDeclId}{$TypeId});
    my %Type = get_Type($TypeDeclId, $TypeId, $LibVersion);
    if($Type{"Type"}=~/\A(Struct|Union|Class|FuncPtr|MethodPtr|FieldPtr|Enum)\Z/)
    {
        $UsedType{$LibVersion}{$TypeDeclId}{$TypeId} = 1;
        if($Type{"Type"}=~/\A(Struct|Class)\Z/)
        {
            if(my $ThisPtrId = getTypeIdByName(get_TypeName($TypeId, $LibVersion)."*const", $LibVersion))
            {# register "this" pointer
                my $ThisPtrDId = $Tid_TDid{$LibVersion}{$ThisPtrId};
                my %ThisPtrType = get_Type($ThisPtrDId, $ThisPtrId, $LibVersion);
                $UsedType{$LibVersion}{$ThisPtrDId}{$ThisPtrId} = 1;
                register_TypeUsing($ThisPtrType{"BaseType"}{"TDid"}, $ThisPtrType{"BaseType"}{"Tid"}, $LibVersion);
            }
            foreach my $BaseId (keys(%{$Type{"Base"}}))
            {# register base classes
                register_TypeUsing($Tid_TDid{$LibVersion}{$BaseId}, $BaseId, $LibVersion);
            }
        }
        foreach my $Memb_Pos (keys(%{$Type{"Memb"}}))
        {
            my $Member_TypeId = $Type{"Memb"}{$Memb_Pos}{"type"};
            register_TypeUsing($Tid_TDid{$LibVersion}{$Member_TypeId}, $Member_TypeId, $LibVersion);
        }
        if($Type{"Type"} eq "FuncPtr"
        or $Type{"Type"} eq "MethodPtr") {
            my $ReturnType = $Type{"Return"};
            register_TypeUsing($Tid_TDid{$LibVersion}{$ReturnType}, $ReturnType, $LibVersion);
            foreach my $Memb_Pos (keys(%{$Type{"Param"}}))
            {
                my $Member_TypeId = $Type{"Param"}{$Memb_Pos}{"type"};
                register_TypeUsing($Tid_TDid{$LibVersion}{$Member_TypeId}, $Member_TypeId, $LibVersion);
            }
        }
    }
    elsif($Type{"Type"}=~/\A(Const|Pointer|Ref|Volatile|Restrict|Array|Typedef)\Z/)
    {
        $UsedType{$LibVersion}{$TypeDeclId}{$TypeId} = 1;
        register_TypeUsing($Type{"BaseType"}{"TDid"}, $Type{"BaseType"}{"Tid"}, $LibVersion);
    }
    elsif($Type{"Type"} eq "Intrinsic") {
        $UsedType{$LibVersion}{$TypeDeclId}{$TypeId} = 1;
    }
    else
    {
        delete($TypeInfo{$LibVersion}{$TypeDeclId}{$TypeId});
        if(not keys(%{$TypeInfo{$LibVersion}{$TypeDeclId}})) {
            delete($TypeInfo{$LibVersion}{$TypeDeclId});
        }
        if($Tid_TDid{$LibVersion}{$TypeId} eq $TypeDeclId) {
            delete($Tid_TDid{$LibVersion}{$TypeId});
        }
    }
}

sub findMethod($$$)
{
    my ($VirtFunc, $ClassId, $LibVersion) = @_;
    foreach my $BaseClass_Id (keys(%{$TypeInfo{$LibVersion}{$Tid_TDid{$LibVersion}{$ClassId}}{$ClassId}{"Base"}}))
    {
        if(my $VirtMethodInClass = findMethod_Class($VirtFunc, $BaseClass_Id, $LibVersion)) {
            return $VirtMethodInClass;
        }
        elsif(my $VirtMethodInBaseClasses = findMethod($VirtFunc, $BaseClass_Id, $LibVersion)) {
            return $VirtMethodInBaseClasses;
        }
    }
    return "";
}

sub findMethod_Class($$$)
{
    my ($VirtFunc, $ClassId, $LibVersion) = @_;
    my $ClassName = get_TypeName($ClassId, $LibVersion);
    return "" if(not defined $VirtualTable{$LibVersion}{$ClassName});
    my $TargetSuffix = get_symbol_suffix($VirtFunc, 1);
    my $TargetShortName = $CompleteSignature{$LibVersion}{$VirtFunc}{"ShortName"};
    foreach my $Candidate (keys(%{$VirtualTable{$LibVersion}{$ClassName}}))
    { # search for interface with the same parameters suffix (overridden)
        if($TargetSuffix eq get_symbol_suffix($Candidate, 1))
        {
            if($CompleteSignature{$LibVersion}{$VirtFunc}{"Destructor"}) {
                if($CompleteSignature{$LibVersion}{$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 $CompleteSignature{$LibVersion}{$Candidate}{"ShortName"}) {
                    return $Candidate;
                }
            }
        }
    }
    return "";
}

sub registerVirtualTable($)
{
    my $LibVersion = $_[0];
    foreach my $Interface (keys(%{$CompleteSignature{$LibVersion}}))
    {
        if($CompleteSignature{$LibVersion}{$Interface}{"Virt"}
        or $CompleteSignature{$LibVersion}{$Interface}{"PureVirt"})
        {
            my $ClassName = get_TypeName($CompleteSignature{$LibVersion}{$Interface}{"Class"}, $LibVersion);
            next if(not $STDCXX_TESTING and $ClassName=~/\A(std::|__cxxabi)/);
            if($CompleteSignature{$LibVersion}{$Interface}{"Destructor"}
            and $Interface=~/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) = separate_symbol($Interface);
            $VirtualTable{$LibVersion}{$ClassName}{$MnglName} = 1;
        }
        if($CheckHeadersOnly
        and $CompleteSignature{$LibVersion}{$Interface}{"Virt"})
        { # Register added and removed virtual symbols
          # This is necessary for --headers-only mode
          # Virtual function cannot be inline, so:
          # presence in headers <=> presence in shared libs
            if($LibVersion==2 and not $CompleteSignature{1}{$Interface}{"Header"})
            { # not presented in old-version headers
                $AddedInt{$Interface} = 1;
            }
            if($LibVersion==1 and not $CompleteSignature{2}{$Interface}{"Header"})
            { # not presented in new-version headers
                $RemovedInt{$Interface} = 1;
            }
        }
    }
}

sub registerOverriding($)
{
    my $LibVersion = $_[0];
    my @Classes = keys(%{$VirtualTable{$LibVersion}});
    @Classes = sort {int($ClassToId{$LibVersion}{$a})<=>int($ClassToId{$LibVersion}{$b})} @Classes;
    foreach my $ClassName (@Classes)
    {
        foreach my $VirtFunc (keys(%{$VirtualTable{$LibVersion}{$ClassName}}))
        {
            next if($CompleteSignature{$LibVersion}{$VirtFunc}{"PureVirt"});
            if(my $OverriddenMethod = findMethod($VirtFunc, $TName_Tid{$LibVersion}{$ClassName}, $LibVersion))
            { # both overridden virtual and implemented pure virtual functions
                $CompleteSignature{$LibVersion}{$VirtFunc}{"Override"} = $OverriddenMethod;
                $OverriddenMethods{$LibVersion}{$OverriddenMethod}{$VirtFunc} = 1;
                delete($VirtualTable{$LibVersion}{$ClassName}{$VirtFunc});
            }
        }
        if(not keys(%{$VirtualTable{$LibVersion}{$ClassName}})) {
            delete($VirtualTable{$LibVersion}{$ClassName});
        }
    }
}

sub setVirtFuncPositions($)
{
    my $LibVersion = $_[0];
    foreach my $ClassName (keys(%{$VirtualTable{$LibVersion}}))
    {
        my ($Num, $RelPos, $AbsNum) = (1, 0, 1);
        foreach my $VirtFunc (sort {int($CompleteSignature{$LibVersion}{$a}{"Line"}) <=> int($CompleteSignature{$LibVersion}{$b}{"Line"})}
        sort keys(%{$VirtualTable{$LibVersion}{$ClassName}}))
        {
            if(not $CompleteSignature{1}{$VirtFunc}{"Override"}
            and not $CompleteSignature{2}{$VirtFunc}{"Override"})
            {
                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
                    $CompleteSignature{$LibVersion}{$VirtFunc}{"RelPos"} = $RelPos++;
                }
                $VirtualTable{$LibVersion}{$ClassName}{$VirtFunc}=$Num++;
            }
            
        }
    }
    foreach my $ClassName (keys(%{$ClassToId{$LibVersion}}))
    {
        my $AbsNum = 1;
        foreach my $VirtFunc (getVTable($ClassToId{$LibVersion}{$ClassName}, $LibVersion)) {
            $VirtualTable_Full{$LibVersion}{$ClassName}{$VirtFunc}=$AbsNum++;
        }
    }
}

sub get_sub_classes($$$)
{
    my ($ClassId, $LibVersion, $Recursive) = @_;
    return () if(not defined $Class_SubClasses{$LibVersion}{$ClassId});
    my @Subs = ();
    foreach my $SubId (keys(%{$Class_SubClasses{$LibVersion}{$ClassId}}))
    {
        if($Recursive) {
            foreach my $SubSubId (get_sub_classes($SubId, $LibVersion, $Recursive)) {
                push(@Subs, $SubSubId);
            }
        }
        push(@Subs, $SubId);
    }
    return @Subs;
}

sub get_base_classes($$$)
{
    my ($ClassId, $LibVersion, $Recursive) = @_;
    my %ClassType = get_Type($Tid_TDid{$LibVersion}{$ClassId}, $ClassId, $LibVersion);
    return () if(not defined $ClassType{"Base"});
    my @Bases = ();
    foreach my $BaseId (sort {int($ClassType{"Base"}{$a}{"pos"})<=>int($ClassType{"Base"}{$b}{"pos"})}
    keys(%{$ClassType{"Base"}}))
    {
        if($Recursive) {
            foreach my $SubBaseId (get_base_classes($BaseId, $LibVersion, $Recursive)) {
                push(@Bases, $SubBaseId);
            }
        }
        push(@Bases, $BaseId);
    }
    return @Bases;
}

sub getVTable($$)
{# return list of v-table elements
    my ($ClassId, $LibVersion) = @_;
    my @Bases = get_base_classes($ClassId, $LibVersion, 1);
    my @Elements = ();
    foreach my $BaseId (@Bases, $ClassId)
    {
        my $BName = get_TypeName($BaseId, $LibVersion);
        my @VFunctions = keys(%{$VirtualTable{$LibVersion}{$BName}});
        @VFunctions = sort {int($CompleteSignature{$LibVersion}{$a}{"Line"}) <=> int($CompleteSignature{$LibVersion}{$b}{"Line"})} @VFunctions;
        foreach my $VFunc (@VFunctions)
        {
            push(@Elements, $VFunc);
        }
    }
    return @Elements;
}

sub getVShift($$)
{
    my ($ClassId, $LibVersion) = @_;
    my @Bases = get_base_classes($ClassId, $LibVersion, 1);
    my $VShift = 0;
    foreach my $BaseId (@Bases)
    {
        my $BName = get_TypeName($BaseId, $LibVersion);
        if(defined $VirtualTable{$LibVersion}{$BName}) {
            $VShift+=keys(%{$VirtualTable{$LibVersion}{$BName}});
        }
    }
    return $VShift;
}

sub getShift($$)
{
    my ($ClassId, $LibVersion) = @_;
    my @Bases = get_base_classes($ClassId, $LibVersion, 0);
    my $Shift = 0;
    foreach my $BaseId (@Bases)
    {
        my $Size = get_TypeSize($BaseId, $LibVersion);
        if($Size!=1) {
            # empty base class
            $Shift+=get_TypeSize($BaseId, $LibVersion);
        }
    }
    return $Shift;
}

sub getVSize($$)
{
    my ($ClassName, $LibVersion) = @_;
    if(defined $VirtualTable{$LibVersion}{$ClassName})  {
        return keys(%{$VirtualTable{$LibVersion}{$ClassName}});
    }
    else {
        return 0;
    }
}

sub isCopyingClass($$)
{
    my ($TypeId, $LibVersion) = @_;
    return $TypeInfo{$LibVersion}{$Tid_TDid{$LibVersion}{$TypeId}}{$TypeId}{"Copied"};
}

sub isLeafClass($$)
{
    my ($ClassId, $LibVersion) = @_;
    return (not keys(%{$Class_SubClasses{$LibVersion}{$ClassId}}));
}

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) = @_;
    return 0 if(not $TypePtr);
    if($End==-1) {
        $End = keys(%{$TypePtr->{"Memb"}})-1;
    }
    foreach my $MemPos (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 getAlignment($$$)
{
    my ($Pos, $TypePtr, $LibVersion) = @_;
    my $Tid = $TypePtr->{"Memb"}{$Pos}{"type"};
    my %Type = get_PureType($Tid_TDid{$LibVersion}{$Tid}, $Tid, $LibVersion);
    my $TSize = $Type{"Size"}*$BYTE_SIZE;
    my $MSize = $Type{"Size"}*$BYTE_SIZE;
    if(my $BSize = $TypePtr->{"Memb"}{$Pos}{"bitfield"})
    { # bitfields
        ($TSize, $MSize) = ($WORD_SIZE{$LibVersion}*$BYTE_SIZE, $BSize);
    }
    elsif($Type{"Type"} eq "Array")
    { # in the context of function parameter
      # it's passed through the pointer
    }
    # alignment
    my $Alignment = $WORD_SIZE{$LibVersion}*$BYTE_SIZE; # default
    if(my $Computed = $TypePtr->{"Memb"}{$Pos}{"algn"})
    { # computed by GCC
        $Alignment = $Computed*$BYTE_SIZE;
    }
    elsif($TypePtr->{"Memb"}{$Pos}{"bitfield"})
    { # bitfields are 1 bit aligned
        $Alignment = 1;
    }
    elsif($TSize and $TSize<$WORD_SIZE{$LibVersion}*$BYTE_SIZE)
    { # model
        $Alignment = $TSize;
    }
    return ($Alignment, $MSize);
}

sub getOffset($$$)
{ # offset of the field including padding
    my ($FieldPos, $TypePtr, $LibVersion) = @_;
    my $Offset = 0;
    foreach my $Pos (0 .. keys(%{$TypePtr->{"Memb"}})-1)
    {
        my ($Alignment, $MSize) = getAlignment($Pos, $TypePtr, $LibVersion);
        # padding
        my $Padding = 0;
        if($Offset % $Alignment!=0)
        { # not aligned, add padding
            $Padding = $Alignment - $Offset % $Alignment;
        }
        $Offset += $Padding;
        if($Pos==$FieldPos)
        { # after the padding
          # before the field
            return $Offset;
        }
        $Offset += $MSize;
    }
    return $FieldPos;# if something is going wrong
}

sub isMemPadded($$$$$)
{ # check if the target field can be added/removed/changed
  # without shifting other fields because of padding bits
    my ($FieldPos, $Size, $TypePtr, $Skip, $LibVersion) = @_;
    return 0 if($FieldPos==0);
    if(defined $TypePtr->{"Memb"}{""})
    {
        delete($TypePtr->{"Memb"}{""});
        if($Debug) {
            printMsg("WARNING", "internal error detected");
        }
    }
    my $Offset = 0;
    my (%Alignment, %MSize) = ();
    my $MaxAlgn = 0;
    my $End = keys(%{$TypePtr->{"Memb"}})-1;
    my $NextField = $FieldPos+1;
    foreach my $Pos (0 .. $End)
    {
        if($Skip and $Skip->{$Pos})
        { # skip removed/added fields
            if($Pos > $FieldPos)
            { # after the target
                $NextField += 1;
                next;
            }
        }
        ($Alignment{$Pos}, $MSize{$Pos}) = getAlignment($Pos, $TypePtr, $LibVersion);
        if($Alignment{$Pos}>$MaxAlgn) {
            $MaxAlgn = $Alignment{$Pos};
        }
        if($Pos==$FieldPos)
        {
            if($Size==-1)
            { # added/removed fields
                if($Pos!=$End)
                { # skip target field and see
                  # if enough padding will be
                  # created on the next step
                  # to include this field
                    next;
                }
            }
        }
        # padding
        my $Padding = 0;
        if($Offset % $Alignment{$Pos}!=0)
        { # not aligned, add padding
            $Padding = $Alignment{$Pos} - $Offset % $Alignment{$Pos};
        }
        if($Pos==$NextField)
        { # try to place target field in the padding
            if($Size==-1)
            { # added/removed fields
                my $TPadding = 0;
                if($Offset % $Alignment{$FieldPos}!=0)
                {# padding of the target field
                    $TPadding = $Alignment{$FieldPos} - $Offset % $Alignment{$FieldPos};
                }
                if($TPadding+$MSize{$FieldPos}<=$Padding)
                { # enough padding to place target field
                    return 1;
                }
                else {
                    return 0;
                }
            }
            else
            { # changed fields
                my $Delta = $Size-$MSize{$FieldPos};
                if($Delta>=0)
                { # increased
                    if($Size-$MSize{$FieldPos}<=$Padding)
                    { # enough padding to change target field
                        return 1;
                    }
                    else {
                        return 0;
                    }
                }
                else
                { # decreased
                    $Delta = abs($Delta);
                    if($Delta+$Padding>=$MSize{$Pos})
                    { # try to place the next field
                        if(($Offset-$Delta) % $Alignment{$Pos} != 0)
                        { # padding of the next field in new place
                            my $NPadding = $Alignment{$Pos} - ($Offset-$Delta) % $Alignment{$Pos};
                            if($NPadding+$MSize{$Pos}<=$Delta+$Padding)
                            { # enough delta+padding to store next field
                                return 0;
                            }
                        }
                        else
                        {
                            return 0;
                        }
                    }
                    return 1;
                }
            }
        }
        elsif($Pos==$End)
        { # target field is the last field
            if($Size==-1)
            { # added/removed fields
                if($Offset % $MaxAlgn!=0)
                { # tail padding
                    my $TailPadding = $MaxAlgn - $Offset % $MaxAlgn;
                    if($Padding+$MSize{$Pos}<=$TailPadding)
                    { # enough tail padding to place the last field
                        return 1;
                    }
                }
                return 0;
            }
            else
            { # changed fields
                # scenario #1
                my $Offset1 = $Offset+$Padding+$MSize{$Pos};
                if($Offset1 % $MaxAlgn != 0)
                { # tail padding
                    $Offset1 += $MaxAlgn - $Offset1 % $MaxAlgn;
                }
                # scenario #2
                my $Offset2 = $Offset+$Padding+$Size;
                if($Offset2 % $MaxAlgn != 0)
                { # tail padding
                    $Offset2 += $MaxAlgn - $Offset2 % $MaxAlgn;
                }
                if($Offset1!=$Offset2)
                { # different sizes of structure
                    return 0;
                }
                return 1;
            }
        }
        $Offset += $Padding+$MSize{$Pos};
    }
    return 0;
}

sub isReserved($)
{ # reserved fields == private
    my $MName = $_[0];
    if($MName=~/reserved|padding|f_spare/i) {
        return 1;
    }
    if($MName=~/\A[_]*(spare|pad|unused)[_]*\Z/i) {
        return 1;
    }
    if($MName=~/(pad\d+)/i) {
        return 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"});
    if(not $TypePtr->{"Memb"}{$FieldPos}{"access"})
    { # by name in C language
      # FIXME: add other methods to detect private members
        my $MName = $TypePtr->{"Memb"}{$FieldPos}{"name"};
        if($MName=~/priv|abidata|parent_object/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;
    }
    elsif($TypePtr->{"Memb"}{$FieldPos}{"access"} ne "private")
    { # by access in C++ language
        return 1;
    }
    return 0;
}

sub cmpVTables_Model($)
{
    my $ClassName = $_[0];
    foreach my $Symbol (keys(%{$VirtualTable_Full{1}{$ClassName}}))
    {
        if(not defined $VirtualTable_Full{2}{$ClassName}{$Symbol}) {
            return 1;
        }
    }
    return 0;
}

sub cmpVTables($$)
{
    my ($ClassName, $Strong) = @_;
    my $ClassId1 = $ClassToId{1}{$ClassName};
    my $ClassId2 = $ClassToId{2}{$ClassName};
    my %Type1 = get_Type($Tid_TDid{1}{$ClassId1}, $ClassId1, 1);
    my %Type2 = get_Type($Tid_TDid{2}{$ClassId2}, $ClassId2, 2);
    if(not defined $Type1{"VTable"}
    or not defined $Type2{"VTable"})
    { # old ABI dumps
        return 0;
    }
    my %Indexes = map {$_=>1} (keys(%{$Type1{"VTable"}}), keys(%{$Type2{"VTable"}}));
    foreach my $Offset (sort {int($a)<=>int($b)} keys(%Indexes))
    {
        if(not defined $Type1{"VTable"}{$Offset})
        { # v-table v.1 < v-table v.2
            return $Strong;
        }
        my $Entry1 = $Type1{"VTable"}{$Offset};
        if(not defined $Type2{"VTable"}{$Offset})
        { # v-table v.1 > v-table v.2
            return $Strong;
        }
        my $Entry2 = $Type2{"VTable"}{$Offset};
        $Entry1 = simpleVEntry($Entry1);
        $Entry2 = simpleVEntry($Entry2);
        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;
                    }
                }
            }
            return 1;
        }
    }
    return 0;
}

sub mergeVTables()
{ # merging v-tables without diagnostics
    foreach my $ClassName (keys(%{$VirtualTable{1}}))
    {
        if($VTableChanged{$ClassName})
        { # already registered
            next;
        }
        if(cmpVTables($ClassName, 0))
        {
            my @Affected = (keys(%{$ClassMethods{1}{$ClassName}}));
            foreach my $Symbol (@Affected)
            {
                %{$CompatProblems{$Symbol}{"Virtual_Table_Changed_Unknown"}{$ClassName}}=(
                    "Type_Name"=>$ClassName,
                    "Type_Type"=>"Class",
                    "Target"=>$ClassName);
            }
        }
    }
}

sub mergeBases()
{
    foreach my $ClassName (keys(%{$ClassToId{1}}))
    { # detect added and removed virtual functions
        my $ClassId = $ClassToId{1}{$ClassName};
        next if(not $ClassId);
        foreach my $VirtFunc (keys(%{$VirtualTable{2}{$ClassName}}))
        {
            if($ClassToId{1}{$ClassName}
            and not defined $VirtualTable{1}{$ClassName}{$VirtFunc})
            { # added to v-table
                if(not $CompleteSignature{2}{$VirtFunc}{"Override"}) {
                    $AddedInt_Virt{$ClassName}{$VirtFunc} = 1;
                }
            }
        }
        foreach my $VirtFunc (keys(%{$VirtualTable{1}{$ClassName}}))
        {
            if($ClassToId{2}{$ClassName}
            and not defined $VirtualTable{2}{$ClassName}{$VirtFunc})
            { # removed from v-table
                if(not $CompleteSignature{1}{$VirtFunc}{"Override"}) {
                    $RemovedInt_Virt{$ClassName}{$VirtFunc} = 1;
                }
            }
        }
        my %Class_Type = get_Type($Tid_TDid{1}{$ClassId}, $ClassId, 1);
        foreach my $AddedVFunc (keys(%{$AddedInt_Virt{$ClassName}}))
        { # check replacements, including pure virtual methods
            my $AddedPos = $VirtualTable{2}{$ClassName}{$AddedVFunc};
            foreach my $RemovedVFunc (keys(%{$RemovedInt_Virt{$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 = ($RemovedVFunc);
                if($CompleteSignature{1}{$RemovedVFunc}{"PureVirt"})
                { # pure methods
                    if(not isUsedClass($ClassId, 1))
                    { # not a parameter of some exported method
                        next;
                    }
                    $ProblemType = "Pure_Virtual_Replacement";
                    @Affected = (keys(%{$ClassMethods{1}{$ClassName}}))
                }
                foreach my $AffectedInt (@Affected)
                {
                    if($CompleteSignature{1}{$AffectedInt}{"PureVirt"})
                    { # affected exported methods only
                        next;
                    }
                    %{$CompatProblems{$AffectedInt}{$ProblemType}{$tr_name{$AddedVFunc}}}=(
                        "Type_Name"=>$Class_Type{"Name"},
                        "Type_Type"=>"Class",
                        "Target"=>get_Signature($AddedVFunc, 2),
                        "Old_Value"=>get_Signature($RemovedVFunc, 1));
                }
            }
        }
    }
    if($UsedDump{1}{"V"}
    and cmpVersions($UsedDump{1}{"V"}, "2.0")<0)
    { # support for old dumps
      # "Base" attribute introduced in ACC 1.22 (dump 2.0 format)
        return;
    }
    if($UsedDump{2}{"V"}
    and cmpVersions($UsedDump{2}{"V"}, "2.0")<0)
    { # support for old dumps
      # "Base" attribute introduced in ACC 1.22 (dump 2.0 format)
        return;
    }
    foreach my $ClassName (sort keys(%{$ClassToId{1}}))
    {
        my $ClassId_Old = $ClassToId{1}{$ClassName};
        next if(not $ClassId_Old);
        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;
        }
        if($ClassName=~/>/)
        { # skip affected template instances
            next;
        }
        my %Class_Old = get_Type($Tid_TDid{1}{$ClassId_Old}, $ClassId_Old, 1);
        my $ClassId_New = $ClassToId{2}{$ClassName};
        next if(not $ClassId_New);
        my %Class_New = get_Type($Tid_TDid{2}{$ClassId_New}, $ClassId_New, 2);
        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 ($BNum1, $BNum2) = (1, 1);
        my %BasePos_Old = map {get_TypeName($_, 1) => $BNum1++} @Bases_Old;
        my %BasePos_New = map {get_TypeName($_, 2) => $BNum2++} @Bases_New;
        my %ShortBase_Old = map {get_TypeShort($_, 1) => 1} @Bases_Old;
        my %ShortBase_New = map {get_TypeShort($_, 2) => 1} @Bases_New;
        my $Shift_Old = getShift($ClassId_Old, 1);
        my $Shift_New = getShift($ClassId_New, 2);
        my %BaseId_New = map {get_TypeName($_, 2) => $_} @Bases_New;
        my ($Added, $Removed) = (0, 0);
        my @StableBases_Old = ();
        foreach my $BaseId (@Bases_Old)
        {
            my $BaseName = get_TypeName($BaseId, 1);
            if($BasePos_New{$BaseName}) {
                push(@StableBases_Old, $BaseId);
            }
            elsif(not $ShortBase_New{$BaseName}
            and not $ShortBase_New{get_TypeShort($BaseId, 1)})
            { # removed base
              # excluding namespace::SomeClass to SomeClass renaming
                my $ProblemKind = "Removed_Base_Class";
                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_Full{1}{$BaseName}})
                and (cmpVTables($ClassName, 1) or cmpVTables_Model($ClassName)))
                { # affected v-table
                    $ProblemKind .= "_And_VTable";
                    $VTableChanged{$ClassName}=1;
                }
                my @Affected = keys(%{$ClassMethods{1}{$ClassName}});
                foreach my $SubId (get_sub_classes($ClassId_Old, 1, 1))
                {
                    my $SubName = get_TypeName($SubId, 1);
                    push(@Affected, keys(%{$ClassMethods{1}{$SubName}}));
                    if($ProblemKind=~/VTable/) {
                        $VTableChanged{$SubName}=1;
                    }
                }
                foreach my $Interface (@Affected)
                {
                    %{$CompatProblems{$Interface}{$ProblemKind}{"this"}}=(
                        "Type_Name"=>$ClassName,
                        "Type_Type"=>"Class",
                        "Target"=>$BaseName,
                        "Old_Size"=>$Class_Old{"Size"}*$BYTE_SIZE,
                        "New_Size"=>$Class_New{"Size"}*$BYTE_SIZE,
                        "Shift"=>abs($Shift_New-$Shift_Old)  );
                }
                $Removed+=1;
            }
        }
        my @StableBases_New = ();
        foreach my $BaseId (@Bases_New)
        {
            my $BaseName = get_TypeName($BaseId, 2);
            if($BasePos_Old{$BaseName}) {
                push(@StableBases_New, $BaseId);
            }
            elsif(not $ShortBase_Old{$BaseName}
            and not $ShortBase_Old{get_TypeShort($BaseId, 2)})
            { # added base
              # excluding namespace::SomeClass to SomeClass renaming
                my $ProblemKind = "Added_Base_Class";
                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_Full{2}{$BaseName}})
                and (cmpVTables($ClassName, 1) or cmpVTables_Model($ClassName)))
                { # affected v-table
                    $ProblemKind .= "_And_VTable";
                    $VTableChanged{$ClassName}=1;
                }
                my @Affected = keys(%{$ClassMethods{1}{$ClassName}});
                foreach my $SubId (get_sub_classes($ClassId_Old, 1, 1))
                {
                    my $SubName = get_TypeName($SubId, 1);
                    push(@Affected, keys(%{$ClassMethods{1}{$SubName}}));
                    if($ProblemKind=~/VTable/) {
                        $VTableChanged{$SubName}=1;
                    }
                }
                foreach my $Interface (@Affected)
                {
                    %{$CompatProblems{$Interface}{$ProblemKind}{"this"}}=(
                        "Type_Name"=>$ClassName,
                        "Type_Type"=>"Class",
                        "Target"=>$BaseName,
                        "Old_Size"=>$Class_Old{"Size"}*$BYTE_SIZE,
                        "New_Size"=>$Class_New{"Size"}*$BYTE_SIZE,
                        "Shift"=>abs($Shift_New-$Shift_Old)  );
                }
                $Added+=1;
            }
        }
        ($BNum1, $BNum2) = (1, 1);
        my %BaseRelPos_Old = map {get_TypeName($_, 1) => $BNum1++} @StableBases_Old;
        my %BaseRelPos_New = map {get_TypeName($_, 2) => $BNum2++} @StableBases_New;
        foreach my $BaseId (@Bases_Old)
        {
            my $BaseName = get_TypeName($BaseId, 1);
            if(my $NewPos = $BaseRelPos_New{$BaseName})
            {
                my $BaseNewId = $BaseId_New{$BaseName};
                my $OldPos = $BaseRelPos_Old{$BaseName};
                if($NewPos!=$OldPos)
                { # changed position of the base class
                    foreach my $Interface (keys(%{$ClassMethods{1}{$ClassName}}))
                    {
                        %{$CompatProblems{$Interface}{"Base_Class_Position"}{"this"}}=(
                            "Type_Name"=>$ClassName,
                            "Type_Type"=>"Class",
                            "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{1}{$ClassName}}))
                    {
                        %{$CompatProblems{$Interface}{"Base_Class_Became_Non_Virtually_Inherited"}{"this->".$BaseName}}=(
                            "Type_Name"=>$ClassName,
                            "Type_Type"=>"Class",
                            "Target"=>$BaseName  );
                    }
                }
                elsif(not $Class_Old{"Base"}{$BaseId}{"virtual"}
                and $Class_New{"Base"}{$BaseNewId}{"virtual"})
                { # became virtual base
                    foreach my $Interface (keys(%{$ClassMethods{1}{$ClassName}}))
                    {
                        %{$CompatProblems{$Interface}{"Base_Class_Became_Virtually_Inherited"}{"this->".$BaseName}}=(
                            "Type_Name"=>$ClassName,
                            "Type_Type"=>"Class",
                            "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 = get_Type($Tid_TDid{1}{$BaseId}, $BaseId, 1);
                my $Size_Old = get_TypeSize($BaseId, 1);
                my $Size_New = get_TypeSize($BaseId_New{$BaseType{"Name"}}, 2);
                if($Size_Old ne $Size_New
                and $Size_Old and $Size_New)
                {
                    my $ProblemType = "";
                    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{1}{$ClassName}}))
                    { # base class size changes affecting current class
                        %{$CompatProblems{$Interface}{$ProblemType}{"this->".$BaseType{"Name"}}}=(
                            "Type_Name"=>$BaseType{"Name"},
                            "Type_Type"=>"Class",
                            "Target"=>$BaseType{"Name"},
                            "Old_Size"=>$Size_Old*$BYTE_SIZE,
                            "New_Size"=>$Size_New*$BYTE_SIZE  );
                    }
                }
            }
        }
        if(my @VFunctions = keys(%{$VirtualTable{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 = get_base_classes($ClassId_Old, 1, 1);
                my @AllBases_New = get_base_classes($ClassId_New, 2, 1);
                ($BNum1, $BNum2) = (1, 1);
                my %StableBase = map {get_TypeName($_, 2) => $_} @AllBases_New;
                foreach my $BaseId (@AllBases_Old)
                {
                    my %BaseType = get_Type($Tid_TDid{1}{$BaseId}, $BaseId, 1);
                    if(not $StableBase{$BaseType{"Name"}})
                    { # lost base
                        next;
                    }
                    my $VSize_Old = getVSize($BaseType{"Name"}, 1);
                    my $VSize_New = getVSize($BaseType{"Name"}, 2);
                    if($VSize_Old!=$VSize_New)
                    {
                        my $VRealSize_Old = get_VTableSymbolSize($BaseType{"Name"}, 1);
                        my $VRealSize_New = get_VTableSymbolSize($BaseType{"Name"}, 2);
                        if(not $VRealSize_Old or not $VRealSize_New)
                        { # try to compute a model v-table size
                            $VRealSize_Old = ($VSize_Old+2+getVShift($BaseId, 1))*$WORD_SIZE{1};
                            $VRealSize_New = ($VSize_New+2+getVShift($StableBase{$BaseType{"Name"}}, 2))*$WORD_SIZE{2};
                        }
                        foreach my $Interface (@VFunctions)
                        {
                            if(not defined $VirtualTable{2}{$ClassName}{$Interface})
                            { # Removed_Virtual_Method, will be registered in mergeVirtualTables()
                                next;
                            }
                            if($VirtualTable{2}{$ClassName}{$Interface}-$VirtualTable{1}{$ClassName}{$Interface}+$VSize_New-$VSize_Old==0)
                            { # skip interfaces that have not changed the absolute virtual position
                                next;
                            }
                            if(not link_symbol($Interface, 1, "-Deps")
                            and not $CheckHeadersOnly)
                            { # affected symbols in shared library
                                next;
                            }
                            if($LIB_ARCH{1} eq $LIB_ARCH{2}
                            or not $LIB_ARCH{1} or not $LIB_ARCH{2})
                            {
                                %{$CompatProblems{$Interface}{"Virtual_Table_Size"}{$BaseType{"Name"}}}=(
                                    "Type_Name"=>$BaseType{"Name"},
                                    "Type_Type"=>"Class",
                                    "Target"=>get_Signature($Interface, 1),
                                    "Old_Size"=>$VRealSize_Old*$BYTE_SIZE,
                                    "New_Size"=>$VRealSize_New*$BYTE_SIZE  );
                            }
                            $VTableChanged{$BaseType{"Name"}} = 1;
                            $VTableChanged{$ClassName} = 1;
                            foreach my $VirtFunc (keys(%{$AddedInt_Virt{$BaseType{"Name"}}}))
                            { # the reason of the layout change: added virtual functions
                                next if($VirtualReplacement{$VirtFunc});
                                my $ProblemType = "Added_Virtual_Method";
                                if($CompleteSignature{2}{$VirtFunc}{"PureVirt"}) {
                                    $ProblemType = "Added_Pure_Virtual_Method";
                                }
                                %{$CompatProblems{$Interface}{$ProblemType}{get_Signature($VirtFunc, 2)}}=(
                                    "Type_Name"=>$BaseType{"Name"},
                                    "Type_Type"=>"Class",
                                    "Target"=>get_Signature($VirtFunc, 2)  );
                            }
                            foreach my $VirtFunc (keys(%{$RemovedInt_Virt{$BaseType{"Name"}}}))
                            { # the reason of the layout change: removed virtual functions
                                next if($VirtualReplacement{$VirtFunc});
                                my $ProblemType = "Removed_Virtual_Method";
                                if($CompleteSignature{1}{$VirtFunc}{"PureVirt"}) {
                                    $ProblemType = "Removed_Pure_Virtual_Method";
                                }
                                %{$CompatProblems{$Interface}{$ProblemType}{get_Signature($VirtFunc, 1)}}=(
                                    "Type_Name"=>$BaseType{"Name"},
                                    "Type_Type"=>"Class",
                                    "Target"=>get_Signature($VirtFunc, 1)  );
                            }
                        }
                    }
                }
            }
        }
    }
}

sub isCreatable($$)
{
    my ($ClassId, $LibVersion) = @_;
    if($AllocableClass{$LibVersion}{get_TypeName($ClassId, $LibVersion)}
    or isCopyingClass($ClassId, $LibVersion)) {
        return 1;
    }
    if(keys(%{$Class_SubClasses{$LibVersion}{$ClassId}}))
    { # Fix for incomplete data: if this class has
      # a base class then it should also has a constructor 
        return 1;
    }
    if($ReturnedClass{$LibVersion}{$ClassId})
    { # returned by some method of this class
      # or any other class
        return 1;
    }
    return 0;
}

sub isUsedClass($$)
{
    my ($ClassId, $LibVersion) = @_;
    if(keys(%{$ParamClass{$LibVersion}{$ClassId}}))
    { # parameter of some exported method
        return 1;
    }
    my $CName = get_TypeName($ClassId, 1);
    if(keys(%{$ClassMethods{1}{$CName}}))
    { # method from target class
        return 1;
    }
    return 0;
}

sub mergeVirtualTables($)
{ # check for changes in the virtual table
    my $Interface = $_[0];
    # affected method:
    #  - virtual
    #  - pure-virtual
    #  - non-virtual
    if($CompleteSignature{1}{$Interface}{"Data"})
    { # global data is not affected
        return;
    }
    my $Class_Id = $CompleteSignature{1}{$Interface}{"Class"};
    my $CName = get_TypeName($Class_Id, 1);
    if($CompleteSignature{1}{$Interface}{"PureVirt"}
    and not isUsedClass($Class_Id, 1))
    { # pure virtuals should not be affected
      # if there are no exported methods using this class
        return;
    }
    $CheckedTypes{$CName} = 1;
    # check virtual table structure
    foreach my $AddedVFunc (keys(%{$AddedInt_Virt{$CName}}))
    {
        next if($Interface eq $AddedVFunc);
        next if($VirtualReplacement{$AddedVFunc});
        my $VPos_Added = $VirtualTable{2}{$CName}{$AddedVFunc};
        if($CompleteSignature{2}{$AddedVFunc}{"PureVirt"})
        { # pure virtual methods affect all others (virtual and non-virtual)
            %{$CompatProblems{$Interface}{"Added_Pure_Virtual_Method"}{$tr_name{$AddedVFunc}}}=(
                "Type_Name"=>$CName,
                "Type_Type"=>"Class",
                "Target"=>get_Signature($AddedVFunc, 2)  );
            $VTableChanged{$CName} = 1;
        }
        elsif($VPos_Added>keys(%{$VirtualTable{1}{$CName}}))
        { # added virtual function at the end of v-table
            if(not keys(%{$VirtualTable_Full{1}{$CName}}))
            { # became polymorphous class, added v-table pointer
                %{$CompatProblems{$Interface}{"Added_First_Virtual_Method"}{$tr_name{$AddedVFunc}}}=(
                    "Type_Name"=>$CName,
                    "Type_Type"=>"Class",
                    "Target"=>get_Signature($AddedVFunc, 2)  );
                $VTableChanged{$CName} = 1;
            }
            else
            {
                my $VSize_Old = getVSize($CName, 1);
                my $VSize_New = getVSize($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{$Interface}{$ProblemType}{$tr_name{$AddedVFunc}}}=(
                        "Type_Name"=>$CName,
                        "Type_Type"=>"Class",
                        "Target"=>get_Signature($AddedVFunc, 2)  );
                    $VTableChanged{$CName} = 1;
                }
                else
                {
                    my $ProblemType = "Added_Virtual_Method";
                    if(isLeafClass($Class_Id, 1)) {
                        $ProblemType = "Added_Virtual_Method_At_End_Of_Leaf_Allocable_Class";
                    }
                    %{$CompatProblems{$Interface}{$ProblemType}{$tr_name{$AddedVFunc}}}=(
                        "Type_Name"=>$CName,
                        "Type_Type"=>"Class",
                        "Target"=>get_Signature($AddedVFunc, 2)  );
                    $VTableChanged{$CName} = 1;
                }
            }
        }
        elsif($CompleteSignature{1}{$Interface}{"Virt"}
        or $CompleteSignature{1}{$Interface}{"PureVirt"})
        {
            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 $CompleteSignature{1}{$ASymbol}{"PureVirt"}
                    and not link_symbol($ASymbol, 1, "-Deps")) {
                        next;
                    }
                    $CheckedSymbols{$ASymbol} = 1;
                    %{$CompatProblems{$ASymbol}{"Added_Virtual_Method"}{$tr_name{$AddedVFunc}}}=(
                        "Type_Name"=>$CName,
                        "Type_Type"=>"Class",
                        "Target"=>get_Signature($AddedVFunc, 2)  );
                    $VTableChanged{get_TypeName($CompleteSignature{1}{$ASymbol}{"Class"}, 1)} = 1;
                }
            }
        }
        else {
            # safe
        }
    }
    foreach my $RemovedVFunc (keys(%{$RemovedInt_Virt{$CName}}))
    {
        next if($VirtualReplacement{$RemovedVFunc});
        if($RemovedVFunc eq $Interface
        and $CompleteSignature{1}{$RemovedVFunc}{"PureVirt"})
        { # This case is for removed virtual methods
          # implemented in both versions of a library
            next;
        }
        if(not keys(%{$VirtualTable_Full{2}{$CName}}))
        { # became non-polymorphous class, removed v-table pointer
            %{$CompatProblems{$Interface}{"Removed_Last_Virtual_Method"}{$tr_name{$RemovedVFunc}}}=(
                "Type_Name"=>$CName,
                "Type_Type"=>"Class",
                "Target"=>get_Signature($RemovedVFunc, 1)  );
            $VTableChanged{$CName} = 1;
        }
        elsif($CompleteSignature{1}{$Interface}{"Virt"}
        or $CompleteSignature{1}{$Interface}{"PureVirt"})
        {
            my $VPos_Removed = $VirtualTable{1}{$CName}{$RemovedVFunc};
            my $VPos_Old = $VirtualTable{1}{$CName}{$Interface};
            my $VPos_New = $VirtualTable{2}{$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 $CompleteSignature{1}{$ASymbol}{"PureVirt"}
                    and not link_symbol($ASymbol, 1, "-Deps")) {
                        next;
                    }
                    my $ProblemType = "Removed_Virtual_Method";
                    if($CompleteSignature{1}{$RemovedVFunc}{"PureVirt"}) {
                        $ProblemType = "Removed_Pure_Virtual_Method";
                    }
                    $CheckedSymbols{$ASymbol} = 1;
                    %{$CompatProblems{$ASymbol}{$ProblemType}{$tr_name{$RemovedVFunc}}}=(
                        "Type_Name"=>$CName,
                        "Type_Type"=>"Class",
                        "Target"=>get_Signature($RemovedVFunc, 1)  );
                    $VTableChanged{get_TypeName($CompleteSignature{1}{$ASymbol}{"Class"}, 1)} = 1;
                }
            }
        }
    }
}

sub find_MemberPair_Pos_byName($$)
{
    my ($Member_Name, $Pair_Type) = @_;
    $Member_Name=~s/\A[_]+|[_]+\Z//g;
    foreach my $MemberPair_Pos (sort {int($a)<=>int($b)} keys(%{$Pair_Type->{"Memb"}}))
    {
        if(defined $Pair_Type->{"Memb"}{$MemberPair_Pos})
        {
            my $Name = $Pair_Type->{"Memb"}{$MemberPair_Pos}{"name"};
            $Name=~s/\A[_]+|[_]+\Z//g;
            if($Name eq $Member_Name) {
                return $MemberPair_Pos;
            }
        }
    }
    return "lost";
}

sub find_MemberPair_Pos_byVal($$)
{
    my ($Member_Value, $Pair_Type) = @_;
    foreach my $MemberPair_Pos (sort {int($a)<=>int($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";
}

my %Priority_Value=(
    "High"=>3,
    "Medium"=>2,
    "Low"=>1);

sub max_priority($$)
{
    my ($Pr1, $Pr2) = @_;
    if(cmp_priority($Pr1, $Pr2)) {
        return $Pr1;
    }
    else {
        return $Pr2;
    }
}

sub cmp_priority($$)
{
    my ($Pr1, $Pr2) = @_;
    return ($Priority_Value{$Pr1}>$Priority_Value{$Pr2});
}

sub getProblemSeverity($$)
{
    my ($Level, $Kind) = @_;
    return $CompatRules{$Level}{$Kind}{"Severity"};
}

sub isRecurType($$$$)
{
    foreach (@RecurTypes)
    {
        if($_->{"Tid1"} eq $_[0]
        and $_->{"TDid1"} eq $_[1]
        and $_->{"Tid2"} eq $_[2]
        and $_->{"TDid2"} eq $_[3])
        {
            return 1;
        }
    }
    return 0;
}

sub pushType($$$$)
{
    my %TypeIDs=(
        "Tid1"  => $_[0],
        "TDid1" => $_[1],
        "Tid2"  => $_[2],
        "TDid2" => $_[3]  );
    push(@RecurTypes, \%TypeIDs);
}

sub isRenamed($$$$$)
{
    my ($MemPos, $Type1, $LVersion1, $Type2, $LVersion2) = @_;
    my $Member_Name = $Type1->{"Memb"}{$MemPos}{"name"};
    my $MemberType_Id = $Type1->{"Memb"}{$MemPos}{"type"};
    my %MemberType_Pure = get_PureType($Tid_TDid{$LVersion1}{$MemberType_Id}, $MemberType_Id, $LVersion1);
    if(not defined $Type2->{"Memb"}{$MemPos}) {
        return "";
    }
    my $StraightPairType_Id = $Type2->{"Memb"}{$MemPos}{"type"};
    my %StraightPairType_Pure = get_PureType($Tid_TDid{$LVersion2}{$StraightPairType_Id}, $StraightPairType_Id, $LVersion2);
    
    my $StraightPair_Name = $Type2->{"Memb"}{$MemPos}{"name"};
    my $MemberPair_Pos_Rev = ($Member_Name eq $StraightPair_Name)?$MemPos:find_MemberPair_Pos_byName($StraightPair_Name, $Type1);
    if($MemberPair_Pos_Rev eq "lost")
    {
        if($MemberType_Pure{"Name"} eq $StraightPairType_Pure{"Name"})
        {# base type match
            return $StraightPair_Name;
        }
        if(get_TypeName($MemberType_Id, $LVersion1) eq get_TypeName($StraightPairType_Id, $LVersion2))
        {# exact type match
            return $StraightPair_Name;
        }
        if($MemberType_Pure{"Size"} eq $StraightPairType_Pure{"Size"})
        {# size match
            return $StraightPair_Name;
        }
        if(isReserved($StraightPair_Name))
        {# reserved fields
            return $StraightPair_Name;
        }
    }
    return "";
}

sub isLastElem($$)
{
    my ($Pos, $TypeRef) = @_;
    my $Name = $TypeRef->{"Memb"}{$Pos}{"name"};
    if($Name=~/last|count|max|total/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) = @_;
    if($T1->{"Name"} ne $T2->{"Name"}
    and not isAnon($T1->{"Name"})
    and not isAnon($T2->{"Name"}))
    { # different names
        if($T1->{"Type"} ne "Pointer"
        or $T2->{"Type"} ne "Pointer")
        { # compare base types
            return 1;
        }
        if($T1->{"Name"}!~/\Avoid\s*\*/
        and $T2->{"Name"}=~/\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, $Type1_DId, $Type2_Id, $Type2_DId) = @_;
    return () if((not $Type1_Id and not $Type1_DId) or (not $Type2_Id and not $Type2_DId));
    my (%Sub_SubProblems, %SubProblems) = ();
    if($Cache{"mergeTypes"}{$Type1_Id}{$Type1_DId}{$Type2_Id}{$Type2_DId})
    { # already merged
        return %{$Cache{"mergeTypes"}{$Type1_Id}{$Type1_DId}{$Type2_Id}{$Type2_DId}};
    }
    my %Type1 = get_Type($Type1_DId, $Type1_Id, 1);
    my %Type2 = get_Type($Type2_DId, $Type2_Id, 2);
    my %Type1_Pure = get_PureType($Type1_DId, $Type1_Id, 1);
    my %Type2_Pure = get_PureType($Type2_DId, $Type2_Id, 2);
    $CheckedTypes{$Type1{"Name"}}=1;
    $CheckedTypes{$Type1_Pure{"Name"}}=1;
    return () if(not $Type1_Pure{"Size"} or not $Type2_Pure{"Size"});
    if(isRecurType($Type1_Pure{"Tid"}, $Type1_Pure{"TDid"}, $Type2_Pure{"Tid"}, $Type2_Pure{"TDid"}))
    { # skip recursive declarations
        return ();
    }
    return () if(not $Type1_Pure{"Name"} or not $Type2_Pure{"Name"});
    return () if($SkipTypes{1}{$Type1_Pure{"Name"}});
    return () if($SkipTypes{1}{$Type1{"Name"}});
    
    my %Typedef_1 = goToFirst($Type1{"TDid"}, $Type1{"Tid"}, 1, "Typedef");
    my %Typedef_2 = goToFirst($Type2{"TDid"}, $Type2{"Tid"}, 2, "Typedef");
    if($Typedef_1{"Type"} eq "Typedef" and $Typedef_2{"Type"} eq "Typedef"
    and $Typedef_1{"Name"} eq $Typedef_2{"Name"} and not $UseOldDumps)
    {
        my %Base_1 = get_OneStep_BaseType($Typedef_1{"TDid"}, $Typedef_1{"Tid"}, 1);
        my %Base_2 = get_OneStep_BaseType($Typedef_2{"TDid"}, $Typedef_2{"Tid"}, 2);
        if(differentFmts())
        { # different GCC versions or different dumps
            $Base_1{"Name"} = uncover_typedefs($Base_1{"Name"}, 1);
            $Base_2{"Name"} = uncover_typedefs($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"});
            $Base_2{"Name"} = formatName($Base_2{"Name"});
        }
        if($Base_1{"Name"}!~/anon\-/ and $Base_2{"Name"}!~/anon\-/
        and $Base_1{"Name"} ne $Base_2{"Name"})
        {
            if($Type1{"Size"} ne $Type2{"Size"})
            {
                %{$SubProblems{"DataType_Size"}{$Typedef_1{"Name"}}}=(
                    "Target"=>$Typedef_1{"Name"},
                    "Type_Name"=>$Typedef_1{"Name"},
                    "Type_Type"=>"Typedef",
                    "Old_Size"=>$Type1{"Size"}*$BYTE_SIZE,
                    "New_Size"=>$Type2{"Size"}*$BYTE_SIZE  );
            }
            %{$SubProblems{"Typedef_BaseType"}{$Typedef_1{"Name"}}}=(
                "Target"=>$Typedef_1{"Name"},
                "Type_Name"=>$Typedef_1{"Name"},
                "Type_Type"=>"Typedef",
                "Old_Value"=>$Base_1{"Name"},
                "New_Value"=>$Base_2{"Name"}  );
        }
    }
    if(nonComparable(\%Type1_Pure, \%Type2_Pure))
    { # different types (reported in detectTypeChange(...))
        if($Type1_Pure{"Name"} eq $Type2_Pure{"Name"}
        and $Type1_Pure{"Type"} ne $Type2_Pure{"Type"}
        and $Type1_Pure{"Type"}!~/Intrinsic|Pointer|Ref|Typedef/)
        { # different type of the type
            %{$SubProblems{"DataType_Type"}{$Type1_Pure{"Name"}}}=(
                "Target"=>$Type1_Pure{"Name"},
                "Type_Name"=>$Type1_Pure{"Name"},
                "Type_Type"=>$Type1_Pure{"Type"},
                "Old_Value"=>lc($Type1_Pure{"Type"}),
                "New_Value"=>lc($Type2_Pure{"Type"})  );
        }
        %{$Cache{"mergeTypes"}{$Type1_Id}{$Type1_DId}{$Type2_Id}{$Type2_DId}} = %SubProblems;
        return %SubProblems;
    }
    pushType($Type1_Pure{"Tid"}, $Type1_Pure{"TDid"},
             $Type2_Pure{"Tid"}, $Type2_Pure{"TDid"});
    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($Type1_Pure{"Size"} ne $Type2_Pure{"Size"})
        {
            my $ProblemKind = "DataType_Size";
            if($Type1_Pure{"Type"} eq "Class"
            and keys(%{$ClassMethods{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"},
                "Type_Type"=>$Type1_Pure{"Type"},
                "Old_Size"=>$Type1_Pure{"Size"}*$BYTE_SIZE,
                "New_Size"=>$Type2_Pure{"Size"}*$BYTE_SIZE,
                "InitialType_Type"=>$Type1_Pure{"Type"}  );
        }
    }
    if($Type1_Pure{"BaseType"}{"Tid"} and $Type2_Pure{"BaseType"}{"Tid"})
    {# checking base types
        %Sub_SubProblems = mergeTypes($Type1_Pure{"BaseType"}{"Tid"}, $Type1_Pure{"BaseType"}{"TDid"},
                                       $Type2_Pure{"BaseType"}{"Tid"}, $Type2_Pure{"BaseType"}{"TDid"});
        foreach my $Sub_SubProblemType (keys(%Sub_SubProblems))
        {
            foreach my $Sub_SubLocation (keys(%{$Sub_SubProblems{$Sub_SubProblemType}}))
            {
                foreach my $Attr (keys(%{$Sub_SubProblems{$Sub_SubProblemType}{$Sub_SubLocation}})) {
                    $SubProblems{$Sub_SubProblemType}{$Sub_SubLocation}{$Attr} = $Sub_SubProblems{$Sub_SubProblemType}{$Sub_SubLocation}{$Attr};
                }
                $SubProblems{$Sub_SubProblemType}{$Sub_SubLocation}{"InitialType_Type"} = $Type1_Pure{"Type"};
            }
        }
    }
    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 {int($a) <=> int($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:find_MemberPair_Pos_byName($Member_Name, \%Type2_Pure);
        if($MemberPair_Pos eq "lost")
        {
            if($Type2_Pure{"Type"}=~/\A(Struct|Class|Union)\Z/)
            {
                if(isUnnamed($Member_Name))
                { # support for old-version dumps
                  # unnamed fields have been introduced in the ACC 1.23 (dump 2.1 format)
                    if($UsedDump{2}{"V"}
                    and cmpVersions($UsedDump{2}{"V"}, "2.1")<0) {
                        next;
                    }
                }
                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 = find_MemberPair_Pos_byVal($Member_Value1, \%Type2_Pure);
                if($MemberPair_Pos ne "lost")
                { # renamed
                    my $RenamedTo = $Type2_Pure{"Memb"}{$MemberPair_Pos}{"name"};
                    my $MemberPair_Pos_Rev = find_MemberPair_Pos_byName($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 {int($a) <=> int($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:find_MemberPair_Pos_byName($Member_Name, \%Type1_Pure);
        if($MemberPair_Pos eq "lost")
        {
            if(isUnnamed($Member_Name))
            { # support for old-version dumps
              # unnamed fields have been introduced in the ACC 1.23 (dump 2.1 format)
                if($UsedDump{1}{"V"}
                and cmpVersions($UsedDump{1}{"V"}, "2.1")<0) {
                    next;
                }
            }
            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 {int($a) <=> int($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 {int($a) <=> int($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
                        $ProblemType = "Moved_Private_Field";
                    }
                    if($Type1_Pure{"Size"} ne $Type2_Pure{"Size"})
                    { # affected size
                        my $MemSize1 = get_TypeSize($Type1_Pure{"Memb"}{$AbsPos1}{"type"}, 1);
                        my $MovedAbsPos = $AbsPos{1}{$RPos2};
                        my $MemSize2 = get_TypeSize($Type1_Pure{"Memb"}{$MovedAbsPos}{"type"}, 1);
                        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"},
                        "Type_Type"=>$Type1_Pure{"Type"},
                        "Old_Value"=>$RPos1,
                        "New_Value"=>$RPos2 );
                }
            }
        }
    }
    foreach my $Member_Pos (sort {int($a) <=> int($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);
        if(my $RenamedTo = $RenamedField{$Member_Pos})
        { # renamed
            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"},
                        "Type_Type"=>$Type1_Pure{"Type"},
                        "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"},
                    "Type_Type"=>$Type1_Pure{"Type"},
                    "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)) {
                    $ProblemType = "Removed_Private_Field";
                }
                if(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"},
                    "Type_Type"=>$Type1_Pure{"Type"}  );
            }
            elsif($Type2_Pure{"Type"} eq "Union")
            {
                if($Type1_Pure{"Size"} ne $Type2_Pure{"Size"})
                {
                    %{$SubProblems{"Removed_Union_Field_And_Size"}{$Member_Name}}=(
                        "Target"=>$Member_Name,
                        "Type_Name"=>$Type1_Pure{"Name"},
                        "Type_Type"=>$Type1_Pure{"Type"}  );
                }
                else
                {
                    %{$SubProblems{"Removed_Union_Field"}{$Member_Name}}=(
                        "Target"=>$Member_Name,
                        "Type_Name"=>$Type1_Pure{"Name"},
                        "Type_Type"=>$Type1_Pure{"Type"}  );
                }
            }
            elsif($Type1_Pure{"Type"} eq "Enum")
            {
                %{$SubProblems{"Enum_Member_Removed"}{$Member_Name}}=(
                    "Target"=>$Member_Name,
                    "Type_Name"=>$Type1_Pure{"Name"},
                    "Type_Type"=>$Type1_Pure{"Type"},
                    "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";
                    }
                    %{$SubProblems{$ProblemType}{$Member_Name}}=(
                        "Target"=>$Member_Name,
                        "Type_Name"=>$Type1_Pure{"Name"},
                        "Type_Type"=>$Type1_Pure{"Type"},
                        "Old_Value"=>$Member_Value1,
                        "New_Value"=>$Member_Value2  );
                }
            }
            elsif($Type2_Pure{"Type"}=~/\A(Struct|Class|Union)\Z/)
            {
                my $MemberType1_Id = $Type1_Pure{"Memb"}{$Member_Pos}{"type"};
                my $MemberType2_Id = $Type2_Pure{"Memb"}{$MemberPair_Pos}{"type"};
                my $SizeV1 = get_TypeSize($MemberType1_Id, 1)*$BYTE_SIZE;
                if(my $BSize1 = $Type1_Pure{"Memb"}{$Member_Pos}{"bitfield"}) {
                    $SizeV1 = $BSize1;
                }
                my $SizeV2 = get_TypeSize($MemberType2_Id, 2)*$BYTE_SIZE;
                if(my $BSize2 = $Type2_Pure{"Memb"}{$MemberPair_Pos}{"bitfield"}) {
                    $SizeV2 = $BSize2;
                }
                my $MemberType1_Name = get_TypeName($MemberType1_Id, 1);
                my $MemberType2_Name = get_TypeName($MemberType2_Id, 2);
                if($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, get_TypeSize($MemberType2_Id, 2)*$BYTE_SIZE, \%Type1_Pure, \%RemovedField, 1))
                        { # check an effect
                            if(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
                            $ProblemType = "";
                        }
                        if($ProblemType)
                        { # register a problem
                            %{$SubProblems{$ProblemType}{$Member_Name}}=(
                                "Target"=>$Member_Name,
                                "Type_Name"=>$Type1_Pure{"Name"},
                                "Type_Type"=>$Type1_Pure{"Type"},
                                "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;
                }
                %Sub_SubProblems = detectTypeChange($MemberType1_Id, $MemberType2_Id, "Field");
                foreach my $ProblemType (keys(%Sub_SubProblems))
                {
                    my $Old_Value = $Sub_SubProblems{$ProblemType}{"Old_Value"};
                    my $New_Value = $Sub_SubProblems{$ProblemType}{"New_Value"};
                    if($ProblemType eq "Field_Type"
                    or $ProblemType eq "Field_Type_And_Size")
                    {
                        if((not $UsedDump{1}{"V"} or cmpVersions($UsedDump{1}{"V"}, "2.6")>=0)
                        and (not $UsedDump{2}{"V"} or cmpVersions($UsedDump{2}{"V"}, "2.6")>=0))
                        {
                            if($Old_Value!~/(\A|\W)volatile(\W|\Z)/
                            and $New_Value=~/(\A|\W)volatile(\W|\Z)/)
                            { # non-"volatile" to "volatile"
                                %{$Sub_SubProblems{"Field_Became_Volatile"}} = %{$Sub_SubProblems{$ProblemType}};
                            }
                        }
                    }
                }
                foreach my $ProblemType (keys(%Sub_SubProblems))
                {
                    my $ProblemType_Init = $ProblemType;
                    if($ProblemType eq "Field_Type_And_Size")
                    {
                        if(not isPublic(\%Type1_Pure, $Member_Pos)
                        or isUnnamed($Member_Name)) {
                            $ProblemType = "Private_".$ProblemType;
                        }
                        if(not isMemPadded($Member_Pos, get_TypeSize($MemberType2_Id, 2)*$BYTE_SIZE, \%Type1_Pure, \%RemovedField, 1))
                        { # check an effect
                            if(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
                    {
                        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
                        next;
                    }
                    %{$SubProblems{$ProblemType}{$Member_Name}}=(
                        "Target"=>$Member_Name,
                        "Type_Name"=>$Type1_Pure{"Name"},
                        "Type_Type"=>$Type1_Pure{"Type"}  );
                    foreach my $Attr (keys(%{$Sub_SubProblems{$ProblemType_Init}}))
                    { # other properties
                        $SubProblems{$ProblemType}{$Member_Name}{$Attr} = $Sub_SubProblems{$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 (replace)
                    %Sub_SubProblems = mergeTypes($MemberType1_Id, $Tid_TDid{1}{$MemberType1_Id},
                                                  $MemberType2_Id, $Tid_TDid{2}{$MemberType2_Id});
                    foreach my $Sub_SubProblemType (keys(%Sub_SubProblems))
                    {
                        foreach my $Sub_SubLocation (keys(%{$Sub_SubProblems{$Sub_SubProblemType}}))
                        {
                            my $NewLocation = ($Sub_SubLocation)?$Member_Name."->".$Sub_SubLocation:$Member_Name;
                            $SubProblems{$Sub_SubProblemType}{$NewLocation}{"IsInTypeInternals"}=1;
                            foreach my $Attr (keys(%{$Sub_SubProblems{$Sub_SubProblemType}{$Sub_SubLocation}})) {
                                $SubProblems{$Sub_SubProblemType}{$NewLocation}{$Attr} = $Sub_SubProblems{$Sub_SubProblemType}{$Sub_SubLocation}{$Attr};
                            }
                            if($Sub_SubLocation!~/\-\>/) {
                                $SubProblems{$Sub_SubProblemType}{$NewLocation}{"Start_Type_Name"} = $MemberType1_Name;
                            }
                        }
                    }
                }
            }
        }
    }
    foreach my $Member_Pos (sort {int($a) <=> int($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);
        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)) {
                    $ProblemType = "Added_Private_Field";
                }
                if(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"},
                    "Type_Type"=>$Type1_Pure{"Type"}  );
            }
            elsif($Type2_Pure{"Type"} eq "Union")
            {
                if($Type1_Pure{"Size"} ne $Type2_Pure{"Size"})
                {
                    %{$SubProblems{"Added_Union_Field_And_Size"}{$Member_Name}}=(
                        "Target"=>$Member_Name,
                        "Type_Name"=>$Type1_Pure{"Name"},
                        "Type_Type"=>$Type1_Pure{"Type"}  );
                }
                else
                {
                    %{$SubProblems{"Added_Union_Field"}{$Member_Name}}=(
                        "Target"=>$Member_Name,
                        "Type_Name"=>$Type1_Pure{"Name"},
                        "Type_Type"=>$Type1_Pure{"Type"}  );
                }
            }
            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"},
                    "Type_Type"=>$Type2_Pure{"Type"},
                    "New_Value"=>$Member_Value  );
            }
        }
    }
    %{$Cache{"mergeTypes"}{$Type1_Id}{$Type1_DId}{$Type2_Id}{$Type2_DId}} = %SubProblems;
    pop(@RecurTypes);
    return %SubProblems;
}

sub isUnnamed($) {
    return $_[0]=~/\Aunnamed\d+\Z/;
}

sub get_TypeName($$)
{
    my ($TypeId, $LibVersion) = @_;
    return $TypeInfo{$LibVersion}{$Tid_TDid{$LibVersion}{$TypeId}}{$TypeId}{"Name"};
}

sub get_TypeShort($$)
{
    my ($TypeId, $LibVersion) = @_;
    my $TypeName = $TypeInfo{$LibVersion}{$Tid_TDid{$LibVersion}{$TypeId}}{$TypeId}{"Name"};
    my $NameSpace = $TypeInfo{$LibVersion}{$Tid_TDid{$LibVersion}{$TypeId}}{$TypeId}{"NameSpace"};
    $TypeName=~s/\A$NameSpace\:\://g;
    return $TypeName;
}

sub get_TypeSize($$)
{
    my ($TypeId, $LibVersion) = @_;
    return $TypeInfo{$LibVersion}{$Tid_TDid{$LibVersion}{$TypeId}}{$TypeId}{"Size"};
}

sub get_TypeAttr($$$)
{
    my ($TypeId, $LibVersion, $Attr) = @_;
    return $TypeInfo{$LibVersion}{$Tid_TDid{$LibVersion}{$TypeId}}{$TypeId}{$Attr};
}

sub goToFirst($$$$)
{
    my ($TypeDId, $TypeId, $LibVersion, $Type_Type) = @_;
    if(defined $Cache{"goToFirst"}{$TypeDId}{$TypeId}{$LibVersion}{$Type_Type}) {
        return %{$Cache{"goToFirst"}{$TypeDId}{$TypeId}{$LibVersion}{$Type_Type}};
    }
    return () if(not $TypeInfo{$LibVersion}{$TypeDId}{$TypeId});
    my %Type = %{$TypeInfo{$LibVersion}{$TypeDId}{$TypeId}};
    return () if(not $Type{"Type"});
    if($Type{"Type"} ne $Type_Type)
    {
        return () if(not $Type{"BaseType"}{"TDid"} and not $Type{"BaseType"}{"Tid"});
        %Type = goToFirst($Type{"BaseType"}{"TDid"}, $Type{"BaseType"}{"Tid"}, $LibVersion, $Type_Type);
    }
    $Cache{"goToFirst"}{$TypeDId}{$TypeId}{$LibVersion}{$Type_Type} = \%Type;
    return %Type;
}

my %TypeSpecAttributes = (
    "Const" => 1,
    "Volatile" => 1,
    "ConstVolatile" => 1,
    "Restrict" => 1,
    "Typedef" => 1
);

sub get_PureType($$$)
{
    my ($TypeDId, $TypeId, $LibVersion) = @_;
    return "" if(not $TypeId);
    if(defined $Cache{"get_PureType"}{$TypeDId}{$TypeId}{$LibVersion}) {
        return %{$Cache{"get_PureType"}{$TypeDId}{$TypeId}{$LibVersion}};
    }
    return "" if(not $TypeInfo{$LibVersion}{$TypeDId}{$TypeId});
    my %Type = %{$TypeInfo{$LibVersion}{$TypeDId}{$TypeId}};
    return %Type if(not $Type{"BaseType"}{"TDid"} and not $Type{"BaseType"}{"Tid"});
    if($TypeSpecAttributes{$Type{"Type"}}) {
        %Type = get_PureType($Type{"BaseType"}{"TDid"}, $Type{"BaseType"}{"Tid"}, $LibVersion);
    }
    $Cache{"get_PureType"}{$TypeDId}{$TypeId}{$LibVersion} = \%Type;
    return %Type;
}

sub get_PointerLevel($$$)
{
    my ($TypeDId, $TypeId, $LibVersion) = @_;
    return 0 if(not $TypeId);
    if(defined $Cache{"get_PointerLevel"}{$TypeDId}{$TypeId}{$LibVersion}) {
        return $Cache{"get_PointerLevel"}{$TypeDId}{$TypeId}{$LibVersion};
    }
    return "" if(not $TypeInfo{$LibVersion}{$TypeDId}{$TypeId});
    my %Type = %{$TypeInfo{$LibVersion}{$TypeDId}{$TypeId}};
    return 1 if($Type{"Type"}=~/FuncPtr|MethodPtr|FieldPtr/);
    return 0 if(not $Type{"BaseType"}{"TDid"} and not $Type{"BaseType"}{"Tid"});
    my $PointerLevel = 0;
    if($Type{"Type"} =~/Pointer|Ref|FuncPtr|MethodPtr|FieldPtr/) {
        $PointerLevel += 1;
    }
    $PointerLevel += get_PointerLevel($Type{"BaseType"}{"TDid"}, $Type{"BaseType"}{"Tid"}, $LibVersion);
    $Cache{"get_PointerLevel"}{$TypeDId}{$TypeId}{$LibVersion} = $PointerLevel;
    return $PointerLevel;
}

sub get_BaseType($$$)
{
    my ($TypeDId, $TypeId, $LibVersion) = @_;
    return () if(not $TypeId);
    if(defined $Cache{"get_BaseType"}{$TypeDId}{$TypeId}{$LibVersion}) {
        return %{$Cache{"get_BaseType"}{$TypeDId}{$TypeId}{$LibVersion}};
    }
    return () if(not $TypeInfo{$LibVersion}{$TypeDId}{$TypeId});
    my %Type = %{$TypeInfo{$LibVersion}{$TypeDId}{$TypeId}};
    return %Type if(not $Type{"BaseType"}{"TDid"} and not $Type{"BaseType"}{"Tid"});
    %Type = get_BaseType($Type{"BaseType"}{"TDid"}, $Type{"BaseType"}{"Tid"}, $LibVersion);
    $Cache{"get_BaseType"}{$TypeDId}{$TypeId}{$LibVersion} = \%Type;
    return %Type;
}

sub get_BaseTypeQual($$$)
{
    my ($TypeDId, $TypeId, $LibVersion) = @_;
    return "" if(not $TypeId);
    return "" if(not $TypeInfo{$LibVersion}{$TypeDId}{$TypeId});
    my %Type = %{$TypeInfo{$LibVersion}{$TypeDId}{$TypeId}};
    return "" if(not $Type{"BaseType"}{"TDid"} and not $Type{"BaseType"}{"Tid"});
    my $Qual = "";
    if($Type{"Type"} eq "Pointer") {
        $Qual .= "*";
    }
    elsif($Type{"Type"} eq "Ref") {
        $Qual .= "&";
    }
    elsif($Type{"Type"} eq "ConstVolatile") {
        $Qual .= "const volatile";
    }
    elsif($Type{"Type"} eq "Const"
    or $Type{"Type"} eq "Volatile"
    or $Type{"Type"} eq "Restrict") {
        $Qual .= lc($Type{"Type"});
    }
    my $BQual = get_BaseTypeQual($Type{"BaseType"}{"TDid"}, $Type{"BaseType"}{"Tid"}, $LibVersion);
    return $BQual.$Qual;
}

sub get_OneStep_BaseType($$$)
{
    my ($TypeDId, $TypeId, $LibVersion) = @_;
    return () if(not $TypeId);
    return () if(not $TypeInfo{$LibVersion}{$TypeDId}{$TypeId});
    my %Type = %{$TypeInfo{$LibVersion}{$TypeDId}{$TypeId}};
    if(not $Type{"BaseType"}{"TDid"}
    and not $Type{"BaseType"}{"Tid"}) {
        return %Type;
    }
    return get_Type($Type{"BaseType"}{"TDid"}, $Type{"BaseType"}{"Tid"}, $LibVersion);
}

sub get_Type($$$)
{
    my ($TypeDId, $TypeId, $LibVersion) = @_;
    return "" if(not $TypeId);
    return "" if(not $TypeInfo{$LibVersion}{$TypeDId}{$TypeId});
    return %{$TypeInfo{$LibVersion}{$TypeDId}{$TypeId}};
}

sub skipGlobalData($)
{
    my $Symbol = $_[0];
    return ($Symbol=~/\A(_ZGV|_ZTI|_ZTS|_ZTT|_ZTV|_ZTC|_ZThn|_ZTv0_n)/);
}

sub isTemplateInstance($)
{
    my $Symbol = $_[0];
    return 0 if($Symbol!~/\A(_Z|\?)/);
    my $Signature = $tr_name{$Symbol};
    return 0 if($Signature!~/>/);
    my $ShortName = substr($Signature, 0, detect_center($Signature, "("));
    $ShortName=~s/::operator .*//;# class::operator template<instance>
    return ($ShortName=~/<.+>/);
}

sub isTemplateSpec($$)
{
    my ($Symbol, $LibVersion) = @_;
    if(my $ClassId = $CompleteSignature{$LibVersion}{$Symbol}{"Class"})
    {
        if(get_TypeAttr($ClassId, $LibVersion, "Spec"))
        { # class specialization
            return 1;
        }
        elsif($CompleteSignature{$LibVersion}{$Symbol}{"Spec"})
        { # method specialization
            return 1;
        }
    }
    return 0;
}

sub symbolFilter($$$)
{ # some special cases when the symbol cannot be imported
    my ($Symbol, $LibVersion, $Type) = @_;
    if(skipGlobalData($Symbol))
    { # non-public global data
        return 0;
    }
    if($CheckObjectsOnly) {
        return 0 if($Symbol=~/\A(_init|_fini)\Z/);
    }
    if($CheckHeadersOnly and $UsedDump{$LibVersion}{"V"}
    and cmpVersions($UsedDump{$LibVersion}{"V"}, "2.7")<0)
    { # support for old ABI dumps in --headers-only mode
        foreach my $Pos (keys(%{$CompleteSignature{$LibVersion}{$Symbol}{"Param"}}))
        {
            if(my $Pid = $CompleteSignature{$LibVersion}{$Symbol}{"Param"}{$Pos}{"type"})
            {
                my $PType = get_TypeAttr($Pid, $LibVersion, "Type");
                if(not $PType or $PType eq "Unknown") {
                    return 0;
                }
            }
        }
    }
    if($Type=~/Imported/)
    {
        my $ClassId = $CompleteSignature{$LibVersion}{$Symbol}{"Class"};
        if(not $STDCXX_TESTING and $Symbol=~/\A(_ZS|_ZNS|_ZNKS)/)
        { # stdc++ interfaces
            return 0;
        }
        if($SkipSymbols{$LibVersion}{$Symbol})
        { # user defined symbols to ignore
            return 0;
        }
        my $NameSpace = $CompleteSignature{$LibVersion}{$Symbol}{"NameSpace"};
        if(not $NameSpace and $ClassId)
        { # class methods have no "NameSpace" attribute
            $NameSpace = get_TypeAttr($ClassId, $LibVersion, "NameSpace");
        }
        if($NameSpace)
        { # user defined namespaces to ignore
            if($SkipNameSpaces{$LibVersion}{$NameSpace}) {
                return 0;
            }
            foreach my $NS (keys(%{$SkipNameSpaces{$LibVersion}}))
            { # nested namespaces
                if($NameSpace=~/\A\Q$NS\E(\:\:|\Z)/) { 
                    return 0;
                }
            }
        }
        if(my $Header = $CompleteSignature{$LibVersion}{$Symbol}{"Header"})
        {
            if(my $Skip = skip_header($Header, $LibVersion))
            { # --skip-headers or <skip_headers> (not <skip_including>)
                if($Skip==1) {
                    return 0;
                }
            }
            if(not is_target_header($Header))
            { # --header, --headers-list
                return 0;
            }
        }
        if($SymbolsListPath and not $SymbolsList{$Symbol})
        { # user defined symbols
            return 0;
        }
        if($AppPath and not $SymbolsList_App{$Symbol})
        { # user defined symbols (in application)
            return 0;
        }
        if($CompleteSignature{$LibVersion}{$Symbol}{"InLine"}
        or (isTemplateInstance($Symbol) and not isTemplateSpec($Symbol, $LibVersion)))
        {
            if($ClassId and $CompleteSignature{$LibVersion}{$Symbol}{"Virt"})
            { # inline virtual methods
                if($Type=~/InlineVirtual/) {
                    return 1;
                }
                my $Allocable = (not isCopyingClass($ClassId, $LibVersion));
                if(not $Allocable)
                { # check bases
                    foreach my $DCId (get_sub_classes($ClassId, $LibVersion, 1))
                    {
                        if(not isCopyingClass($DCId, $LibVersion))
                        { # exists a derived class without default c-tor
                            $Allocable=1;
                            last;
                        }
                    }
                }
                if(not $Allocable) {
                    return 0;
                }
            }
            else
            { # inline non-virtual methods
                return 0;
            }
        }
    }
    return 1;
}

sub mergeImpl()
{
    my $DiffCmd = get_CmdPath("diff");
    if(not $DiffCmd) {
        exitStatus("Not_Found", "can't find \"diff\"");
    }
    foreach my $Interface (sort keys(%{$Symbol_Library{1}}))
    { # implementation changes
        next if($CompleteSignature{1}{$Interface}{"Private"});
        next if(not $CompleteSignature{1}{$Interface}{"Header"} and not $CheckObjectsOnly);
        next if(not $Symbol_Library{2}{$Interface} and not $Symbol_Library{2}{$SymVer{2}{$Interface}});
        next if(not symbolFilter($Interface, 1, "Imported"));
        my $Impl1 = canonify_implementation($Interface_Impl{1}{$Interface});
        next if(not $Impl1);
        my $Impl2 = canonify_implementation($Interface_Impl{2}{$Interface});
        next if(not $Impl2);
        if($Impl1 ne $Impl2)
        {
            writeFile("$TMP_DIR/impl1", $Impl1);
            writeFile("$TMP_DIR/impl2", $Impl2);
            my $Diff = `$DiffCmd -rNau $TMP_DIR/impl1 $TMP_DIR/impl2`;
            $Diff=~s/(---|\+\+\+).+\n//g;
            $Diff=~s/[ ]{3,}/ /g;
            $Diff=~s/\n\@\@/\n \n\@\@/g;
            unlink("$TMP_DIR/impl1", "$TMP_DIR/impl2");
            %{$ImplProblems{$Interface}}=(
                "Diff" => get_CodeView($Diff)  );
        }
    }
}

sub canonify_implementation($)
{
    my $FuncBody=  $_[0];
    return "" if(not $FuncBody);
    $FuncBody=~s/0x[a-f\d]+/0x?/g;# addr
    $FuncBody=~s/((\A|\n)[a-z]+[\t ]+)[a-f\d]+([^x]|\Z)/$1?$3/g;# call, jump
    $FuncBody=~s/# [a-f\d]+ /# ? /g;# call, jump
    $FuncBody=~s/%([a-z]+[a-f\d]*)/\%reg/g;# registers
    while($FuncBody=~s/\nnop[ \t]*(\n|\Z)/$1/g){};# empty op
    $FuncBody=~s/<.+?\.cpp.+?>/<name.cpp>/g;
    $FuncBody=~s/(\A|\n)[a-f\d]+ </$1? </g;# 5e74 <_ZN...
    $FuncBody=~s/\.L\d+/.L/g;
    $FuncBody=~s/#(-?)\d+/#$1?/g;# r3, [r3, #120]
    $FuncBody=~s/[\n]{2,}/\n/g;
    return $FuncBody;
}

sub get_CodeView($)
{
    my $Code = $_[0];
    my $View = "";
    foreach my $Line (split(/\n/, $Code))
    {
        if($Line=~s/\A(\+|-)/$1 /g)
        {# bold line
            $View .= "<tr><td><b>".htmlSpecChars($Line)."</b></td></tr>\n";
        }
        else {
            $View .= "<tr><td>".htmlSpecChars($Line)."</td></tr>\n";
        }
    }
    return "<table class='code_view'>$View</table>\n";
}

sub getImplementations($$)
{
    my ($LibVersion, $Path) = @_;
    return if(not $LibVersion or not -e $Path);
    if($OSgroup eq "macos")
    {
        my $OtoolCmd = get_CmdPath("otool");
        if(not $OtoolCmd) {
            exitStatus("Not_Found", "can't find \"otool\"");
        }
        my $CurInterface = "";
        foreach my $Line (split(/\n/, `$OtoolCmd -tv $Path 2>$TMP_DIR/null`))
        {
            if($Line=~/\A\s*_(\w+)\s*:/i) {
                $CurInterface = $1;
            }
            elsif($Line=~/\A\s*[\da-z]+\s+(.+?)\Z/i) {
                $Interface_Impl{$LibVersion}{$CurInterface} .= "$1\n";
            }
        }
    }
    else
    {
        my $ObjdumpCmd = get_CmdPath("objdump");
        if(not $ObjdumpCmd) {
            exitStatus("Not_Found", "can't find \"objdump\"");
        }
        my $CurInterface = "";
        foreach my $Line (split(/\n/, `$ObjdumpCmd -d $Path 2>$TMP_DIR/null`))
        {
            if($Line=~/\A[\da-z]+\s+<(\w+)>/i) {
                $CurInterface = $1;
            }
            else
            { # x86:    51fa:(\t)89 e5               (\t)mov    %esp,%ebp
              # arm:    5020:(\t)e24cb004(\t)sub(\t)fp, ip, #4(\t); 0x4
                if($Line=~/\A\s*[a-f\d]+:\s+([a-f\d]+\s+)+([a-z]+\s+.*?)\s*(;.*|)\Z/i) {
                    $Interface_Impl{$LibVersion}{$CurInterface} .= "$2\n";
                }
            }
        }
    }
}

sub detectAdded()
{
    foreach my $Symbol (keys(%{$Symbol_Library{2}}))
    {
        if(link_symbol($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 = $SymVer{2}{$Symbol}
        and $Symbol!~/\@/) {
            next;
        }
        $AddedInt{$Symbol} = 1;
    }
}

sub detectRemoved()
{
    foreach my $Symbol (keys(%{$Symbol_Library{1}}))
    {
        if($CheckObjectsOnly) {
            $CheckedSymbols{$Symbol} = 1;
        }
        if(link_symbol($Symbol, 2, "+Deps"))
        { # linker can find an old symbol
          # in the new-version library
            next;
        }
        if(my $VSym = $SymVer{1}{$Symbol}
        and $Symbol!~/\@/) {
            next;
        }
        $RemovedInt{$Symbol} = 1;
    }
}

sub mergeLibs()
{
    foreach my $Symbol (sort keys(%AddedInt))
    { # checking added symbols
        next if($CompleteSignature{2}{$Symbol}{"Private"});
        next if(not $CompleteSignature{2}{$Symbol}{"Header"} and not $CheckObjectsOnly);
        next if(not symbolFilter($Symbol, 2, "Imported"));
        %{$CompatProblems{$Symbol}{"Added_Interface"}{""}}=();
    }
    foreach my $Symbol (sort keys(%RemovedInt))
    { # checking removed symbols
        next if($CompleteSignature{1}{$Symbol}{"Private"});
        next if(not $CompleteSignature{1}{$Symbol}{"Header"} and not $CheckObjectsOnly);
        if($Symbol=~/\A_ZTV/)
        { # skip v-tables for templates, that should not be imported by applications
            next if($tr_name{$Symbol}=~/</);
            if(not keys(%{$ClassMethods{1}{$VTableClass{1}{$Symbol}}}))
            { # vtables for "private" classes
              # use case: vtable for QDragManager (Qt 4.5.3 to 4.6.0) became HIDDEN symbol
                next;
            }
        }
        else {
            next if(not symbolFilter($Symbol, 1, "Imported"));
        }
        if($CompleteSignature{1}{$Symbol}{"PureVirt"})
        { # symbols for pure virtual methods cannot be called by clients
            next;
        }
        %{$CompatProblems{$Symbol}{"Removed_Interface"}{""}}=();
    }
}

sub detectAdded_H()
{
    foreach my $Symbol (sort keys(%{$CompleteSignature{2}}))
    {
        if($GeneratedSymbols{$Symbol}) {
            next;
        }
        if(not $CompleteSignature{1}{$Symbol}) {
            $AddedInt{$Symbol} = 1;
        }
    }
}

sub detectRemoved_H()
{
    foreach my $Symbol (sort keys(%{$CompleteSignature{1}}))
    {
        if($GeneratedSymbols{$Symbol}) {
            next;
        }
        if(not $CompleteSignature{2}{$Symbol}) {
            $RemovedInt{$Symbol} = 1;
        }
    }
}

sub mergeHeaders()
{
    foreach my $Symbol (sort keys(%AddedInt))
    { # checking added symbols
        next if($CompleteSignature{2}{$Symbol}{"PureVirt"});
        next if($CompleteSignature{2}{$Symbol}{"InLine"});
        next if($CompleteSignature{2}{$Symbol}{"Private"});
        next if(not symbolFilter($Symbol, 2, "Imported"));
        %{$CompatProblems{$Symbol}{"Added_Interface"}{""}}=();
    }
    foreach my $Symbol (sort keys(%RemovedInt))
    { # checking removed symbols
        next if($CompleteSignature{1}{$Symbol}{"PureVirt"});
        next if($CompleteSignature{1}{$Symbol}{"InLine"});
        next if($CompleteSignature{1}{$Symbol}{"Private"});
        next if(not symbolFilter($Symbol, 1, "Imported"));
        %{$CompatProblems{$Symbol}{"Removed_Interface"}{""}}=();
    }
}

sub addParamNames($)
{
    my $LibraryVersion = $_[0];
    return if(not keys(%AddIntParams));
    my $SecondVersion = $LibraryVersion==1?2:1;
    foreach my $Interface (sort keys(%{$CompleteSignature{$LibraryVersion}}))
    {
        next if(not keys(%{$AddIntParams{$Interface}}));
        foreach my $ParamPos (sort {int($a)<=>int($b)} keys(%{$CompleteSignature{$LibraryVersion}{$Interface}{"Param"}}))
        {# add absent parameter names
            my $ParamName = $CompleteSignature{$LibraryVersion}{$Interface}{"Param"}{$ParamPos}{"name"};
            if($ParamName=~/\Ap\d+\Z/ and my $NewParamName = $AddIntParams{$Interface}{$ParamPos})
            {# names from the external file
                if(defined $CompleteSignature{$SecondVersion}{$Interface}
                and defined $CompleteSignature{$SecondVersion}{$Interface}{"Param"}{$ParamPos})
                {
                    if($CompleteSignature{$SecondVersion}{$Interface}{"Param"}{$ParamPos}{"name"}=~/\Ap\d+\Z/) {
                        $CompleteSignature{$LibraryVersion}{$Interface}{"Param"}{$ParamPos}{"name"} = $NewParamName;
                    }
                }
                else {
                    $CompleteSignature{$LibraryVersion}{$Interface}{"Param"}{$ParamPos}{"name"} = $NewParamName;
                }
            }
        }
    }
}

sub detectChangedTypedefs()
{ # detect changed typedefs to create correct function signatures
    foreach my $Typedef (keys(%{$Typedef_BaseName{1}}))
    {
        next if(not $Typedef);
        next if(isAnon($Typedef_BaseName{1}{$Typedef}));
        next if(isAnon($Typedef_BaseName{2}{$Typedef}));
        next if(not $Typedef_BaseName{1}{$Typedef});
        next if(not $Typedef_BaseName{2}{$Typedef});# exclude added/removed
        if($Typedef_BaseName{1}{$Typedef} ne $Typedef_BaseName{2}{$Typedef}) {
            $ChangedTypedef{$Typedef} = 1;
        }
    }
}

sub get_symbol_suffix($$)
{
    my ($Interface, $Full) = @_;
    $Interface=~s/\A([^\@\$\?]+)[\@\$]+/$1/g;# remove version
    my $Signature = $tr_name{$Interface};
    my $Suffix = substr($Signature, detect_center($Signature, "("));
    if(not $Full) {
        $Suffix=~s/(\))\s*(const volatile|volatile const|const|volatile)\Z/$1/g;
    }
    return $Suffix;
}

sub get_symbol_prefix($$)
{
    my ($Symbol, $LibVersion) = @_;
    my $ShortName = $CompleteSignature{$LibVersion}{$Symbol}{"ShortName"};
    if(my $ClassId = $CompleteSignature{$LibVersion}{$Symbol}{"Class"})
    { # methods
        $ShortName = get_TypeName($ClassId, $LibVersion)."::".$ShortName;
    }
    return $ShortName;
}

sub mergeSignatures()
{
    my %SubProblems = ();
    
    registerVirtualTable(1);
    registerVirtualTable(2);

    if($UsedDump{1}{"V"} and cmpVersions($UsedDump{1}{"V"}, "1.22")<0
    and (not $UsedDump{2}{"V"} or cmpVersions($UsedDump{2}{"V"}, "1.22")>=0))
    { # support for old ABI dumps
        foreach my $ClassName (keys(%{$VirtualTable{2}}))
        {
            if($ClassName=~/</)
            { # templates
                if(not defined $VirtualTable{1}{$ClassName})
                { # synchronize
                    delete($VirtualTable{2}{$ClassName});
                }
            }
        }
    }
    
    registerOverriding(1);
    registerOverriding(2);
    
    setVirtFuncPositions(1);
    setVirtFuncPositions(2);
    
    addParamNames(1);
    addParamNames(2);
    
    detectChangedTypedefs();
    mergeBases();
    my %AddedOverloads = ();
    foreach my $Interface (sort keys(%AddedInt))
    { # check all added exported symbols
        next if(not $CompleteSignature{2}{$Interface}{"Header"});
        if(defined $CompleteSignature{1}{$Interface}
        and $CompleteSignature{1}{$Interface}{"Header"})
        { # double-check added symbol
            next;
        }
        next if(not symbolFilter($Interface, 2, "Imported"));
        if($Interface=~/\A(_Z|\?)/)
        { # C++
            $AddedOverloads{get_symbol_prefix($Interface, 2)}{get_symbol_suffix($Interface, 1)} = $Interface;
        }
        if(my $OverriddenMethod = $CompleteSignature{2}{$Interface}{"Override"})
        { # register virtual redefinition
            my $AffectedClass_Name = get_TypeName($CompleteSignature{2}{$Interface}{"Class"}, 2);
            if(defined $CompleteSignature{1}{$OverriddenMethod}
            and $CompleteSignature{1}{$OverriddenMethod}{"Virt"} and $ClassToId{1}{$AffectedClass_Name}
            and not $CompleteSignature{1}{$OverriddenMethod}{"Private"})
            { # public virtual methods, virtual destructors: class should exist in previous version
                if(isCopyingClass($ClassToId{1}{$AffectedClass_Name}, 1))
                { # old v-table (copied) will be used by applications
                    next;
                }
                if(defined $CompleteSignature{1}{$Interface}
                and $CompleteSignature{1}{$Interface}{"InLine"})
                { # auto-generated virtual destructors stay in the header (and v-table), added to library
                  # use case: Ice 3.3.1 -> 3.4.0
                    next;
                }
                %{$CompatProblems{$OverriddenMethod}{"Overridden_Virtual_Method"}{$tr_name{$Interface}}}=(
                    "Type_Name"=>$AffectedClass_Name,
                    "Type_Type"=>"Class",
                    "Target"=>get_Signature($Interface, 2),
                    "Old_Value"=>get_Signature($OverriddenMethod, 2),
                    "New_Value"=>get_Signature($Interface, 2)  );
            }
        }
    }
    foreach my $Interface (sort keys(%RemovedInt))
    {
        next if(not $CompleteSignature{1}{$Interface}{"Header"});
        if(defined $CompleteSignature{2}{$Interface}
        and $CompleteSignature{2}{$Interface}{"Header"})
        { # double-check removed symbol
            next;
        }
        next if($CompleteSignature{1}{$Interface}{"Private"}); # skip private methods
        next if(not symbolFilter($Interface, 1, "Imported"));
        $CheckedSymbols{$Interface} = 1;
        if(my $OverriddenMethod = $CompleteSignature{1}{$Interface}{"Override"})
        { # register virtual redefinition
            my $AffectedClass_Name = get_TypeName($CompleteSignature{1}{$Interface}{"Class"}, 1);
            if(defined $CompleteSignature{2}{$OverriddenMethod}
            and $CompleteSignature{2}{$OverriddenMethod}{"Virt"} and $ClassToId{2}{$AffectedClass_Name})
            { # virtual methods, virtual destructors: class should exist in newer version
                if(isCopyingClass($CompleteSignature{1}{$Interface}{"Class"}, 1))
                { # old v-table (copied) will be used by applications
                    next;
                }
                if(defined $CompleteSignature{2}{$Interface}
                and $CompleteSignature{2}{$Interface}{"InLine"})
                { # auto-generated virtual destructors stay in the header (and v-table), removed from library
                  # use case: Ice 3.3.1 -> 3.4.0
                    next;
                }
                %{$CompatProblems{$Interface}{"Overridden_Virtual_Method_B"}{$tr_name{$OverriddenMethod}}}=(
                    "Type_Name"=>$AffectedClass_Name,
                    "Type_Type"=>"Class",
                    "Target"=>get_Signature($OverriddenMethod, 1),
                    "Old_Value"=>get_Signature($Interface, 1),
                    "New_Value"=>get_Signature($OverriddenMethod, 1)  );
            }
        }
        if($OSgroup eq "windows")
        { # register the reason of symbol name change
            if(my $NewSymbol = $mangled_name{2}{$tr_name{$Interface}})
            {
                if($AddedInt{$NewSymbol})
                {
                    if($CompleteSignature{1}{$Interface}{"Static"} ne $CompleteSignature{2}{$NewSymbol}{"Static"})
                    {
                        if($CompleteSignature{2}{$NewSymbol}{"Static"}) {
                            %{$CompatProblems{$Interface}{"Symbol_Changed_Static"}{$tr_name{$Interface}}}=(
                                "Target"=>$tr_name{$Interface},
                                "Old_Value"=>$Interface,
                                "New_Value"=>$NewSymbol  );
                        }
                        else {
                            %{$CompatProblems{$Interface}{"Symbol_Changed_NonStatic"}{$tr_name{$Interface}}}=(
                                "Target"=>$tr_name{$Interface},
                                "Old_Value"=>$Interface,
                                "New_Value"=>$NewSymbol  );
                        }
                    }
                    if($CompleteSignature{1}{$Interface}{"Virt"} ne $CompleteSignature{2}{$NewSymbol}{"Virt"})
                    {
                        if($CompleteSignature{2}{$NewSymbol}{"Virt"}) {
                            %{$CompatProblems{$Interface}{"Symbol_Changed_Virtual"}{$tr_name{$Interface}}}=(
                                "Target"=>$tr_name{$Interface},
                                "Old_Value"=>$Interface,
                                "New_Value"=>$NewSymbol  );
                        }
                        else {
                            %{$CompatProblems{$Interface}{"Symbol_Changed_NonVirtual"}{$tr_name{$Interface}}}=(
                                "Target"=>$tr_name{$Interface},
                                "Old_Value"=>$Interface,
                                "New_Value"=>$NewSymbol  );
                        }
                    }
                    my $ReturnTypeName1 = get_TypeName($CompleteSignature{1}{$Interface}{"Return"}, 1);
                    my $ReturnTypeName2 = get_TypeName($CompleteSignature{2}{$NewSymbol}{"Return"}, 2);
                    if($ReturnTypeName1 ne $ReturnTypeName2)
                    {
                        my $ProblemType = "Symbol_Changed_Return";
                        if($CompleteSignature{1}{$Interface}{"Data"}) {
                            $ProblemType = "Global_Data_Symbol_Changed_Type";
                        }
                        %{$CompatProblems{$Interface}{$ProblemType}{$tr_name{$Interface}}}=(
                            "Target"=>$tr_name{$Interface},
                            "Old_Type"=>$ReturnTypeName1,
                            "New_Type"=>$ReturnTypeName2,
                            "Old_Value"=>$Interface,
                            "New_Value"=>$NewSymbol  );
                    }
                }
            }
        }
        if($Interface=~/\A(_Z|\?)/)
        { # C++
            my $Prefix = get_symbol_prefix($Interface, 1);
            if(my @Overloads = sort keys(%{$AddedOverloads{$Prefix}})
            and not $AddedOverloads{$Prefix}{get_symbol_suffix($Interface, 1)})
            { # changed signature: params, "const"-qualifier
                my $NewSymbol = $AddedOverloads{$Prefix}{$Overloads[0]};
                if($CompleteSignature{1}{$Interface}{"Constructor"}) {
                    if($Interface=~/(C1E|C2E)/) {
                        my $CtorType = $1;
                        $NewSymbol=~s/(C1E|C2E)/$CtorType/g;
                    }
                }
                elsif($CompleteSignature{1}{$Interface}{"Destructor"}) {
                    if($Interface=~/(D0E|D1E|D2E)/) {
                        my $DtorType = $1;
                        $NewSymbol=~s/(D0E|D1E|D2E)/$DtorType/g;
                    }
                }
                if($CompleteSignature{1}{$Interface}{"NameSpace"} eq $CompleteSignature{2}{$NewSymbol}{"NameSpace"})
                { # from the same class and namespace
                    if($CompleteSignature{1}{$Interface}{"Const"}
                    and not $CompleteSignature{2}{$NewSymbol}{"Const"})
                    { # "const" to non-"const"
                        %{$CompatProblems{$Interface}{"Symbol_Changed_Became_NonConst"}{$tr_name{$Interface}}}=(
                            "Target"=>$tr_name{$Interface},
                            "New_Signature"=>get_Signature($NewSymbol, 2),
                            "Old_Value"=>$Interface,
                            "New_Value"=>$NewSymbol  );
                    }
                    elsif(not $CompleteSignature{1}{$Interface}{"Const"}
                    and $CompleteSignature{2}{$NewSymbol}{"Const"})
                    { # non-"const" to "const"
                        %{$CompatProblems{$Interface}{"Symbol_Changed_Became_Const"}{$tr_name{$Interface}}}=(
                            "Target"=>$tr_name{$Interface},
                            "New_Signature"=>get_Signature($NewSymbol, 2),
                            "Old_Value"=>$Interface,
                            "New_Value"=>$NewSymbol  );
                    }
                    if($CompleteSignature{1}{$Interface}{"Volatile"}
                    and not $CompleteSignature{2}{$NewSymbol}{"Volatile"})
                    { # "volatile" to non-"volatile"
                        
                        %{$CompatProblems{$Interface}{"Symbol_Changed_Became_NonVolatile"}{$tr_name{$Interface}}}=(
                            "Target"=>$tr_name{$Interface},
                            "New_Signature"=>get_Signature($NewSymbol, 2),
                            "Old_Value"=>$Interface,
                            "New_Value"=>$NewSymbol  );
                    }
                    elsif(not $CompleteSignature{1}{$Interface}{"Volatile"}
                    and $CompleteSignature{2}{$NewSymbol}{"Volatile"})
                    { # non-"volatile" to "volatile"
                        %{$CompatProblems{$Interface}{"Symbol_Changed_Became_Volatile"}{$tr_name{$Interface}}}=(
                            "Target"=>$tr_name{$Interface},
                            "New_Signature"=>get_Signature($NewSymbol, 2),
                            "Old_Value"=>$Interface,
                            "New_Value"=>$NewSymbol  );
                    }
                    if(get_symbol_suffix($Interface, 0) ne get_symbol_suffix($NewSymbol, 0))
                    { # params list
                        %{$CompatProblems{$Interface}{"Symbol_Changed_Parameters"}{$tr_name{$Interface}}}=(
                            "Target"=>$tr_name{$Interface},
                            "New_Signature"=>get_Signature($NewSymbol, 2),
                            "Old_Value"=>$Interface,
                            "New_Value"=>$NewSymbol  );
                    }
                }
            }
        }
    }
    foreach my $Interface (sort keys(%{$CompleteSignature{1}}))
    { # checking interfaces
        if($Interface!~/[\@\$\?]/)
        { # symbol without version
            if(my $VSym = $SymVer{1}{$Interface})
            { # the symbol is linked with versioned symbol
                if($CompleteSignature{2}{$VSym}{"MnglName"})
                { # show report for symbol@ver only
                    next;
                }
                elsif(not link_symbol($VSym, 2, "-Deps"))
                { # changed version: sym@v1 to sym@v2
                  # do NOT show report for symbol
                    next;
                }
            }
        }
        if($CompleteSignature{1}{$Interface}{"Private"})
        { # private symbols
            next;
        }
        if(not $CompleteSignature{1}{$Interface}{"MnglName"}
        or not $CompleteSignature{2}{$Interface}{"MnglName"})
        { # absent mangled name
            next;
        }
        if(not $CompleteSignature{1}{$Interface}{"Header"}
        or not $CompleteSignature{2}{$Interface}{"Header"})
        { # without a header
            next;
        }
        if($CheckHeadersOnly)
        { # skip added and removed pure virtual methods
            next if(not $CompleteSignature{1}{$Interface}{"PureVirt"} and $CompleteSignature{2}{$Interface}{"PureVirt"});
            next if($CompleteSignature{1}{$Interface}{"PureVirt"} and not $CompleteSignature{2}{$Interface}{"PureVirt"});
        }
        else
        { # skip external, added and removed functions except pure virtual methods
            if(not link_symbol($Interface, 1, "-Deps")
            or not link_symbol($Interface, 2, "-Deps"))
            { # symbols from target library(ies) only
              # excluding dependent libraries
                if(not $CompleteSignature{1}{$Interface}{"PureVirt"}
                or not $CompleteSignature{2}{$Interface}{"PureVirt"}) {
                    next;
                }
            }
        }
        if(not symbolFilter($Interface, 1, "Imported|InlineVirtual"))
        { # symbols that cannot be imported
            next;
        }
        # checking virtual table
        if($CompleteSignature{1}{$Interface}{"Class"}) {
            mergeVirtualTables($Interface);
        }
        if($COMPILE_ERRORS)
        { # if some errors occurred at the compiling stage
          # then some false positives can be skipped here
            if(not $CompleteSignature{1}{$Interface}{"Data"} and $CompleteSignature{2}{$Interface}{"Data"}
            and not $CompleteSignature{2}{$Interface}{"Object"})
            { # missed information about parameters in newer version
                next;
            }
            if($CompleteSignature{1}{$Interface}{"Data"} and not $CompleteSignature{1}{$Interface}{"Object"}
            and not $CompleteSignature{2}{$Interface}{"Data"})
            {# missed information about parameters in older version
                next;
            }
        }
        my ($MnglName, $VersionSpec, $SymbolVersion) = separate_symbol($Interface);
        # checking attributes
        if($CompleteSignature{2}{$Interface}{"Static"}
        and not $CompleteSignature{1}{$Interface}{"Static"} and $Interface=~/\A(_Z|\?)/) {
            %{$CompatProblems{$Interface}{"Method_Became_Static"}{""}}=();
        }
        elsif(not $CompleteSignature{2}{$Interface}{"Static"}
        and $CompleteSignature{1}{$Interface}{"Static"} and $Interface=~/\A(_Z|\?)/) {
            %{$CompatProblems{$Interface}{"Method_Became_NonStatic"}{""}}=();
        }
        if(($CompleteSignature{1}{$Interface}{"Virt"} and $CompleteSignature{2}{$Interface}{"Virt"})
        or ($CompleteSignature{1}{$Interface}{"PureVirt"} and $CompleteSignature{2}{$Interface}{"PureVirt"}))
        { # relative position of virtual and pure virtual methods
            if($CompleteSignature{1}{$Interface}{"RelPos"}!=$CompleteSignature{2}{$Interface}{"RelPos"})
            {
                my $Class_Id = $CompleteSignature{1}{$Interface}{"Class"};
                if($VirtualTable{1}{get_TypeName($Class_Id, 1)}{$Interface}!=$VirtualTable{2}{get_TypeName($Class_Id, 1)}{$Interface})
                { # check the absolute position of virtual method (including added and removed methods)
                    my %Class_Type = get_Type($Tid_TDid{1}{$Class_Id}, $Class_Id, 1);
                    my $ProblemType = "Virtual_Method_Position";
                    if($CompleteSignature{1}{$Interface}{"PureVirt"}) {
                        $ProblemType = "Pure_Virtual_Method_Position";
                    }
                    if(isUsedClass($Class_Id, 1))
                    {
                        my @Affected = ($Interface, keys(%{$OverriddenMethods{1}{$Interface}}));
                        foreach my $AffectedInterface (@Affected)
                        {
                            %{$CompatProblems{$AffectedInterface}{$ProblemType}{$tr_name{$MnglName}}}=(
                                "Type_Name"=>$Class_Type{"Name"},
                                "Type_Type"=>"Class",
                                "Old_Value"=>$CompleteSignature{1}{$Interface}{"RelPos"},
                                "New_Value"=>$CompleteSignature{2}{$Interface}{"RelPos"},
                                "Target"=>get_Signature($Interface, 1)  );
                        }
                        $VTableChanged{$Class_Type{"Name"}} = 1;
                    }
                }
            }
        }
        if($CompleteSignature{1}{$Interface}{"PureVirt"}
        or $CompleteSignature{2}{$Interface}{"PureVirt"})
        { # do NOT check type changes in pure virtuals
            next;
        }
        $CheckedSymbols{$Interface}=1;
        if($Interface=~/\A(_Z|\?)/
        or keys(%{$CompleteSignature{1}{$Interface}{"Param"}})==keys(%{$CompleteSignature{2}{$Interface}{"Param"}}))
        { # C/C++: changes in parameters
            foreach my $ParamPos (sort {int($a) <=> int($b)} keys(%{$CompleteSignature{1}{$Interface}{"Param"}}))
            { # checking parameters
                mergeParameters($Interface, $ParamPos, $ParamPos);
            }
        }
        else
        { # C: added/removed parameters
            foreach my $ParamPos (sort {int($a) <=> int($b)} keys(%{$CompleteSignature{2}{$Interface}{"Param"}}))
            { # checking added parameters
                my $ParamType2_Id = $CompleteSignature{2}{$Interface}{"Param"}{$ParamPos}{"type"};
                last if(get_TypeName($ParamType2_Id, 2) eq "...");
                my $Parameter_Name = $CompleteSignature{2}{$Interface}{"Param"}{$ParamPos}{"name"};
                my $Parameter_OldName = (defined $CompleteSignature{1}{$Interface}{"Param"}{$ParamPos})?$CompleteSignature{1}{$Interface}{"Param"}{$ParamPos}{"name"}:"";
                my $ParamPos_Prev = "-1";
                if($Parameter_Name=~/\Ap\d+\Z/i)
                { # added unnamed parameter ( pN )
                    my @Positions1 = find_ParamPair_Pos_byTypeAndPos(get_TypeName($ParamType2_Id, 2), $ParamPos, "backward", $Interface, 1);
                    my @Positions2 = find_ParamPair_Pos_byTypeAndPos(get_TypeName($ParamType2_Id, 2), $ParamPos, "backward", $Interface, 2);
                    if($#Positions1==-1 or $#Positions2>$#Positions1) {
                        $ParamPos_Prev = "lost";
                    }
                }
                else {
                    $ParamPos_Prev = find_ParamPair_Pos_byName($Parameter_Name, $Interface, 1);
                }
                if($ParamPos_Prev eq "lost")
                {
                    if($ParamPos>keys(%{$CompleteSignature{1}{$Interface}{"Param"}})-1)
                    {
                        my $ProblemType = "Added_Parameter";
                        if($Parameter_Name=~/\Ap\d+\Z/) {
                            $ProblemType = "Added_Unnamed_Parameter";
                        }
                        %{$CompatProblems{$Interface}{$ProblemType}{numToStr($ParamPos+1)." Parameter"}}=(
                            "Target"=>$Parameter_Name,
                            "Param_Pos"=>$ParamPos,
                            "Param_Type"=>get_TypeName($ParamType2_Id, 2),
                            "New_Signature"=>get_Signature($Interface, 2)  );
                    }
                    else
                    {
                        my %ParamType_Pure = get_PureType($Tid_TDid{2}{$ParamType2_Id}, $ParamType2_Id, 2);
                        my $ParamStraightPairType_Id = $CompleteSignature{1}{$Interface}{"Param"}{$ParamPos}{"type"};
                        my %ParamStraightPairType_Pure = get_PureType($Tid_TDid{1}{$ParamStraightPairType_Id}, $ParamStraightPairType_Id, 1);
                        if(($ParamType_Pure{"Name"} eq $ParamStraightPairType_Pure{"Name"} or get_TypeName($ParamType2_Id, 2) eq get_TypeName($ParamStraightPairType_Id, 1))
                        and find_ParamPair_Pos_byName($Parameter_OldName, $Interface, 2) eq "lost")
                        {
                            if($Parameter_OldName!~/\Ap\d+\Z/ and $Parameter_Name!~/\Ap\d+\Z/)
                            {
                                %{$CompatProblems{$Interface}{"Renamed_Parameter"}{numToStr($ParamPos+1)." Parameter"}}=(
                                    "Target"=>$Parameter_OldName,
                                    "Param_Pos"=>$ParamPos,
                                    "Param_Type"=>get_TypeName($ParamType2_Id, 2),
                                    "Old_Value"=>$Parameter_OldName,
                                    "New_Value"=>$Parameter_Name,
                                    "New_Signature"=>get_Signature($Interface, 2)  );
                            }
                        }
                        else
                        {
                            my $ProblemType = "Added_Middle_Parameter";
                            if($Parameter_Name=~/\Ap\d+\Z/) {
                                $ProblemType = "Added_Middle_Unnamed_Parameter";
                            }
                            %{$CompatProblems{$Interface}{$ProblemType}{numToStr($ParamPos+1)." Parameter"}}=(
                                "Target"=>$Parameter_Name,
                                "Param_Pos"=>$ParamPos,
                                "Param_Type"=>get_TypeName($ParamType2_Id, 2),
                                "New_Signature"=>get_Signature($Interface, 2)  );
                        }
                    }
                }
            }
            foreach my $ParamPos (sort {int($a) <=> int($b)} keys(%{$CompleteSignature{1}{$Interface}{"Param"}}))
            { # check relevant parameters
                my $ParamType1_Id = $CompleteSignature{1}{$Interface}{"Param"}{$ParamPos}{"type"};
                my $ParamName1 = $CompleteSignature{1}{$Interface}{"Param"}{$ParamPos}{"name"};
                # FIXME: find relevant parameter by name
                if(defined $CompleteSignature{2}{$Interface}{"Param"}{$ParamPos})
                {
                    my $ParamType2_Id = $CompleteSignature{2}{$Interface}{"Param"}{$ParamPos}{"type"};
                    my $ParamName2 = $CompleteSignature{2}{$Interface}{"Param"}{$ParamPos}{"name"};
                    if(($ParamName1!~/\Ap\d+\Z/i and $ParamName1 eq $ParamName2)
                    or get_TypeName($ParamType1_Id, 1) eq get_TypeName($ParamType2_Id, 2)) {
                        mergeParameters($Interface, $ParamPos, $ParamPos);
                    }
                }
            }
            foreach my $ParamPos (sort {int($a) <=> int($b)} keys(%{$CompleteSignature{1}{$Interface}{"Param"}}))
            { # checking removed parameters
                my $ParamType1_Id = $CompleteSignature{1}{$Interface}{"Param"}{$ParamPos}{"type"};
                last if(get_TypeName($ParamType1_Id, 1) eq "...");
                my $Parameter_Name = $CompleteSignature{1}{$Interface}{"Param"}{$ParamPos}{"name"};
                my $Parameter_NewName = (defined $CompleteSignature{2}{$Interface}{"Param"}{$ParamPos})?$CompleteSignature{2}{$Interface}{"Param"}{$ParamPos}{"name"}:"";
                my $ParamPos_New = "-1";
                if($Parameter_Name=~/\Ap\d+\Z/i)
                { # removed unnamed parameter ( pN )
                    my @Positions1 = find_ParamPair_Pos_byTypeAndPos(get_TypeName($ParamType1_Id, 1), $ParamPos, "forward", $Interface, 1);
                    my @Positions2 = find_ParamPair_Pos_byTypeAndPos(get_TypeName($ParamType1_Id, 1), $ParamPos, "forward", $Interface, 2);
                    if($#Positions2==-1 or $#Positions2<$#Positions1) {
                        $ParamPos_New = "lost";
                    }
                }
                else {
                    $ParamPos_New = find_ParamPair_Pos_byName($Parameter_Name, $Interface, 2);
                }
                if($ParamPos_New eq "lost")
                {
                    if($ParamPos>keys(%{$CompleteSignature{2}{$Interface}{"Param"}})-1)
                    {
                        my $ProblemType = "Removed_Parameter";
                        if($Parameter_Name=~/\Ap\d+\Z/) {
                            $ProblemType = "Removed_Unnamed_Parameter";
                        }
                        %{$CompatProblems{$Interface}{$ProblemType}{numToStr($ParamPos+1)." Parameter"}}=(
                            "Target"=>$Parameter_Name,
                            "Param_Pos"=>$ParamPos,
                            "Param_Type"=>get_TypeName($ParamType1_Id, 1),
                            "New_Signature"=>get_Signature($Interface, 2)  );
                    }
                    elsif($ParamPos<keys(%{$CompleteSignature{1}{$Interface}{"Param"}})-1)
                    {
                        my %ParamType_Pure = get_PureType($Tid_TDid{1}{$ParamType1_Id}, $ParamType1_Id, 1);
                        my $ParamStraightPairType_Id = $CompleteSignature{2}{$Interface}{"Param"}{$ParamPos}{"type"};
                        my %ParamStraightPairType_Pure = get_PureType($Tid_TDid{2}{$ParamStraightPairType_Id}, $ParamStraightPairType_Id, 2);
                        if(($ParamType_Pure{"Name"} eq $ParamStraightPairType_Pure{"Name"} or get_TypeName($ParamType1_Id, 1) eq get_TypeName($ParamStraightPairType_Id, 2))
                        and find_ParamPair_Pos_byName($Parameter_NewName, $Interface, 1) eq "lost")
                        {
                            if($Parameter_NewName!~/\Ap\d+\Z/ and $Parameter_Name!~/\Ap\d+\Z/)
                            {
                                %{$CompatProblems{$Interface}{"Renamed_Parameter"}{numToStr($ParamPos+1)." Parameter"}}=(
                                    "Target"=>$Parameter_Name,
                                    "Param_Pos"=>$ParamPos,
                                    "Param_Type"=>get_TypeName($ParamType1_Id, 1),
                                    "Old_Value"=>$Parameter_Name,
                                    "New_Value"=>$Parameter_NewName,
                                    "New_Signature"=>get_Signature($Interface, 2)  );
                            }
                        }
                        else
                        {
                            my $ProblemType = "Removed_Middle_Parameter";
                            if($Parameter_Name=~/\Ap\d+\Z/) {
                                $ProblemType = "Removed_Middle_Unnamed_Parameter";
                            }
                            %{$CompatProblems{$Interface}{$ProblemType}{numToStr($ParamPos+1)." Parameter"}}=(
                                "Target"=>$Parameter_Name,
                                "Param_Pos"=>$ParamPos,
                                "Param_Type"=>get_TypeName($ParamType1_Id, 1),
                                "New_Signature"=>get_Signature($Interface, 2)  );
                        }
                    }
                }
            }
        }
        # checking return type
        my $ReturnType1_Id = $CompleteSignature{1}{$Interface}{"Return"};
        my $ReturnType2_Id = $CompleteSignature{2}{$Interface}{"Return"};
        %SubProblems = detectTypeChange($ReturnType1_Id, $ReturnType2_Id, "Return");
        foreach my $SubProblemType (keys(%SubProblems))
        {
            my $New_Value = $SubProblems{$SubProblemType}{"New_Value"};
            my $Old_Value = $SubProblems{$SubProblemType}{"Old_Value"};
            my $NewProblemType = $SubProblemType;
            if($SubProblemType eq "Return_Type_Became_Void"
            and keys(%{$CompleteSignature{1}{$Interface}{"Param"}}))
            { # parameters stack has been affected
                $NewProblemType = "Return_Type_Became_Void_And_Stack_Layout";
            }
            elsif($SubProblemType eq "Return_Type_From_Void")
            { # parameters stack has been affected
                if(keys(%{$CompleteSignature{1}{$Interface}{"Param"}})) {
                    $NewProblemType = "Return_Type_From_Void_And_Stack_Layout";
                }
                else
                { # safe
                    delete($SubProblems{$SubProblemType});
                    next;
                }
            }
            elsif($SubProblemType eq "Return_Type_And_Size"
            and $CompleteSignature{1}{$Interface}{"Data"}) {
                $NewProblemType = "Global_Data_Type_And_Size";
            }
            elsif($SubProblemType eq "Return_Type")
            {
                if($CompleteSignature{1}{$Interface}{"Data"})
                {
                    if(removedConstness($Old_Value, $New_Value))
                    { # const -> non-const global data
                        $NewProblemType = "Global_Data_Became_Non_Const";
                    }
                    elsif(removedConstness($New_Value, $Old_Value))
                    { # non-const -> const global data
                        $NewProblemType = "Global_Data_Became_Const";
                    }
                    else {
                        $NewProblemType = "Global_Data_Type";
                    }
                }
                else
                {
                    if(removedConstness($New_Value, $Old_Value)) {
                        $NewProblemType = "Return_Type_Became_Const";
                    }
                }
            }
            @{$CompatProblems{$Interface}{$NewProblemType}{"retval"}}{keys(%{$SubProblems{$SubProblemType}})} = values %{$SubProblems{$SubProblemType}};
        }
        if($ReturnType1_Id and $ReturnType2_Id)
        {
            @RecurTypes = ();
            %SubProblems = mergeTypes($ReturnType1_Id, $Tid_TDid{1}{$ReturnType1_Id},
                                      $ReturnType2_Id, $Tid_TDid{2}{$ReturnType2_Id});
            foreach my $SubProblemType (keys(%SubProblems))
            { # add "Global_Data_Size" problem
                my $New_Value = $SubProblems{$SubProblemType}{"New_Value"};
                my $Old_Value = $SubProblems{$SubProblemType}{"Old_Value"};
                if($SubProblemType eq "DataType_Size"
                and $CompleteSignature{1}{$Interface}{"Data"}
                and get_PointerLevel($Tid_TDid{1}{$ReturnType1_Id}, $ReturnType1_Id, 1)==0)
                { # add a new problem
                    %{$SubProblems{"Global_Data_Size"}} = %{$SubProblems{$SubProblemType}};
                }
            }
            foreach my $SubProblemType (keys(%SubProblems))
            {
                foreach my $SubLocation (keys(%{$SubProblems{$SubProblemType}}))
                {
                    my $NewLocation = ($SubLocation)?"retval->".$SubLocation:"retval";
                    %{$CompatProblems{$Interface}{$SubProblemType}{$NewLocation}}=(
                        "Return_Type_Name"=>get_TypeName($ReturnType1_Id, 1) );
                    @{$CompatProblems{$Interface}{$SubProblemType}{$NewLocation}}{keys(%{$SubProblems{$SubProblemType}{$SubLocation}})} = values %{$SubProblems{$SubProblemType}{$SubLocation}};
                    if($SubLocation!~/\-\>/) {
                        $CompatProblems{$Interface}{$SubProblemType}{$NewLocation}{"Start_Type_Name"} = get_TypeName($ReturnType1_Id, 1);
                    }
                }
            }
        }
        
        # checking object type
        my $ObjectType1_Id = $CompleteSignature{1}{$Interface}{"Class"};
        my $ObjectType2_Id = $CompleteSignature{2}{$Interface}{"Class"};
        if($ObjectType1_Id and $ObjectType2_Id
        and not $CompleteSignature{1}{$Interface}{"Static"})
        {
            my $ThisPtr1_Id = getTypeIdByName(get_TypeName($ObjectType1_Id, 1)."*const", 1);
            my $ThisPtr2_Id = getTypeIdByName(get_TypeName($ObjectType2_Id, 2)."*const", 2);
            if($ThisPtr1_Id and $ThisPtr2_Id)
            {
                @RecurTypes = ();
                %SubProblems = mergeTypes($ThisPtr1_Id, $Tid_TDid{1}{$ThisPtr1_Id},
                                          $ThisPtr2_Id, $Tid_TDid{2}{$ThisPtr2_Id});
                foreach my $SubProblemType (keys(%SubProblems))
                {
                    foreach my $SubLocation (keys(%{$SubProblems{$SubProblemType}}))
                    {
                        my $NewLocation = ($SubLocation)?"this->".$SubLocation:"this";
                        %{$CompatProblems{$Interface}{$SubProblemType}{$NewLocation}}=(
                            "Object_Type_Name"=>get_TypeName($ObjectType1_Id, 1) );
                        @{$CompatProblems{$Interface}{$SubProblemType}{$NewLocation}}{keys(%{$SubProblems{$SubProblemType}{$SubLocation}})} = values %{$SubProblems{$SubProblemType}{$SubLocation}};
                        if($SubLocation!~/\-\>/) {
                            $CompatProblems{$Interface}{$SubProblemType}{$NewLocation}{"Start_Type_Name"} = get_TypeName($ObjectType1_Id, 1);
                        }
                    }
                }
            }
        }
    }
    mergeVTables();
}

sub removedConstness($$)
{
    my ($Old_Value, $New_Value) = @_;
    if($Old_Value eq $New_Value) {
        return 0;
    }
    while($Old_Value=~s/(\A|\W)const(\W|\Z)/$1$2/)
    { # remove all "const" qualifiers
      # one-by-one, left-to-right
        $Old_Value=~s/\s+\Z//g;
        $Old_Value=~s/\A\s+//g;
        $Old_Value = formatName($Old_Value);
        if($Old_Value eq $New_Value)
        { # compare with a new type
            return 1;
        }
    }
    return 0;
}

sub mergeParameters($$$)
{
    my ($Interface, $ParamPos1, $ParamPos2) = @_;
    return if(not $Interface);
    return if(not defined $CompleteSignature{1}{$Interface}{"Param"});
    return if(not defined $CompleteSignature{2}{$Interface}{"Param"});
    my $ParamType1_Id = $CompleteSignature{1}{$Interface}{"Param"}{$ParamPos1}{"type"};
    my $ParamName1 = $CompleteSignature{1}{$Interface}{"Param"}{$ParamPos1}{"name"};
    my $ParamType2_Id = $CompleteSignature{2}{$Interface}{"Param"}{$ParamPos2}{"type"};
    my $ParamName2 = $CompleteSignature{2}{$Interface}{"Param"}{$ParamPos2}{"name"};
    return if(not $ParamType1_Id or not $ParamType2_Id);
    my %Type1 = get_Type($Tid_TDid{1}{$ParamType1_Id}, $ParamType1_Id, 1);
    my %Type2 = get_Type($Tid_TDid{2}{$ParamType2_Id}, $ParamType2_Id, 2);
    my %BaseType1 = get_BaseType($Tid_TDid{1}{$ParamType1_Id}, $ParamType1_Id, 1);
    my %BaseType2 = get_BaseType($Tid_TDid{2}{$ParamType2_Id}, $ParamType2_Id, 2);
    my $Parameter_Location = ($ParamName1)?$ParamName1:numToStr($ParamPos1+1)." Parameter";
    if((not $UsedDump{1}{"V"} or cmpVersions($UsedDump{1}{"V"}, "2.6.1")>=0)
    and (not $UsedDump{2}{"V"} or cmpVersions($UsedDump{2}{"V"}, "2.6.1")>=0))
    { # "reg" attribute added in ACC 1.95.1 (dump 2.6.1 format)
        if($CompleteSignature{1}{$Interface}{"Param"}{$ParamPos1}{"reg"}
        and not $CompleteSignature{2}{$Interface}{"Param"}{$ParamPos2}{"reg"})
        {
            %{$CompatProblems{$Interface}{"Parameter_Became_Non_Register"}{$Parameter_Location}}=(
                "Target"=>$ParamName1,
                "Param_Pos"=>$ParamPos1  );
        }
        elsif(not $CompleteSignature{1}{$Interface}{"Param"}{$ParamPos1}{"reg"}
        and $CompleteSignature{2}{$Interface}{"Param"}{$ParamPos2}{"reg"})
        {
            %{$CompatProblems{$Interface}{"Parameter_Became_Register"}{$Parameter_Location}}=(
                "Target"=>$ParamName1,
                "Param_Pos"=>$ParamPos1  );
        }
    }
    if((not $UsedDump{1}{"V"} or cmpVersions($UsedDump{1}{"V"}, "2.0")>=0)
    and (not $UsedDump{2}{"V"} or cmpVersions($UsedDump{2}{"V"}, "2.0")>=0))
    { # "default" attribute added in ACC 1.22 (dump 2.0 format)
        my $DefaultValue_Old = $CompleteSignature{1}{$Interface}{"Param"}{$ParamPos1}{"default"};
        my $DefaultValue_New = $CompleteSignature{2}{$Interface}{"Param"}{$ParamPos2}{"default"};
        my %PureType1 = get_PureType($Tid_TDid{1}{$ParamType1_Id}, $ParamType1_Id, 1);
        if($PureType1{"Name"}=~/\A(char\*|char const\*)\Z/)
        {
            if($DefaultValue_Old)
            { # FIXME: how to distinguish "0" and 0 (NULL)
                $DefaultValue_Old = "\"$DefaultValue_Old\"";
            }
            if($DefaultValue_New) {
                $DefaultValue_New = "\"$DefaultValue_New\"";
            }
        }
        elsif($PureType1{"Name"}=~/\A(char)\Z/)
        {
            if($DefaultValue_Old) {
                $DefaultValue_Old = "\'$DefaultValue_Old\'";
            }
            if($DefaultValue_New) {
                $DefaultValue_New = "\'$DefaultValue_New\'";
            }
        }
        if($DefaultValue_Old ne "")
        {
            if($DefaultValue_New ne "")
            {
                if($DefaultValue_Old ne $DefaultValue_New)
                {
                    %{$CompatProblems{$Interface}{"Parameter_Default_Value_Changed"}{$Parameter_Location}}=(
                        "Target"=>$ParamName1,
                        "Param_Pos"=>$ParamPos1,
                        "Old_Value"=>$DefaultValue_Old,
                        "New_Value"=>$DefaultValue_New  );
                }
            }
            else
            {
                %{$CompatProblems{$Interface}{"Parameter_Default_Value_Removed"}{$Parameter_Location}}=(
                        "Target"=>$ParamName1,
                        "Param_Pos"=>$ParamPos1,
                        "Old_Value"=>$DefaultValue_Old  );
            }
        }
    }
    if($ParamName1 ne $ParamName2
    and $ParamType1_Id!=-1 and $ParamType2_Id!=-1
    and $ParamName1!~/\Ap\d+\Z/ and $ParamName2!~/\Ap\d+\Z/)
    { # except unnamed "..." value list (Id=-1)
        %{$CompatProblems{$Interface}{"Renamed_Parameter"}{numToStr($ParamPos1+1)." Parameter"}}=(
            "Target"=>$ParamName1,
            "Param_Pos"=>$ParamPos1,
            "Param_Type"=>get_TypeName($ParamType1_Id, 1),
            "Old_Value"=>$ParamName1,
            "New_Value"=>$ParamName2,
            "New_Signature"=>get_Signature($Interface, 2)  );
    }
    # checking type change (replace)
    my %SubProblems = detectTypeChange($ParamType1_Id, $ParamType2_Id, "Parameter");
    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"};
        if($SubProblemType eq "Parameter_Type")
        {
            if((not $UsedDump{1}{"V"} or cmpVersions($UsedDump{1}{"V"}, "2.6")>=0)
            and (not $UsedDump{2}{"V"} or cmpVersions($UsedDump{2}{"V"}, "2.6")>=0))
            {
                if($Old_Value!~/(\A|\W)restrict(\W|\Z)/
                and $New_Value=~/(\A|\W)restrict(\W|\Z)/)
                { # change to be "restrict"
                    %{$SubProblems{"Parameter_Became_Restrict"}} = %{$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});
            }
            if($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});
            }
        }
    }
    foreach my $SubProblemType (keys(%SubProblems))
    { # modify/register problems
        my $New_Value = $SubProblems{$SubProblemType}{"New_Value"};
        my $Old_Value = $SubProblems{$SubProblemType}{"Old_Value"};
        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_NonVaList";
        }
        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($SubProblemType eq "Parameter_Type"
        and removedConstness($Old_Value, $New_Value))
        { # parameter: "const" to non-"const"
            $NewProblemType = "Parameter_Became_Non_Const";
        }
        elsif($SubProblemType eq "Parameter_Type_And_Size"
        or $SubProblemType eq "Parameter_Type")
        {
            my ($Arch1, $Arch2) = (getArch(1), getArch(2));
            if($Arch1 eq "unknown" or $Arch2 eq "unknown")
            { # if one of the architectures is unknown
                # then set other arhitecture to unknown too
                ($Arch1, $Arch2) = ("unknown", "unknown");
            }
            my ($Method1, $Passed1, $SizeOnStack1, $RegName1) = callingConvention($Interface, $ParamPos1, 1, $Arch1);
            my ($Method2, $Passed2, $SizeOnStack2, $RegName2) = callingConvention($Interface, $ParamPos2, 2, $Arch2);
            if($Method1 eq $Method2)
            {
                if($Method1 eq "stack" and $SizeOnStack1 ne $SizeOnStack2) {
                    $NewProblemType = "Parameter_Type_And_Stack";
                }
                elsif($Method1 eq "register" and $RegName1 ne $RegName2) {
                    $NewProblemType = "Parameter_Type_And_Register";
                }
            }
            else
            {
                if($Method1 eq "stack") {
                    $NewProblemType = "Parameter_Type_And_Pass_Through_Register";
                }
                elsif($Method1 eq "register") {
                    $NewProblemType = "Parameter_Type_And_Pass_Through_Stack";
                }
            }
            $SubProblems{$SubProblemType}{"Old_Reg"} = $RegName1;
            $SubProblems{$SubProblemType}{"New_Reg"} = $RegName2;
        }
        %{$CompatProblems{$Interface}{$NewProblemType}{$Parameter_Location}}=(
            "Target"=>$ParamName1,
            "Param_Pos"=>$ParamPos1,
            "New_Signature"=>get_Signature($Interface, 2) );
        @{$CompatProblems{$Interface}{$NewProblemType}{$Parameter_Location}}{keys(%{$SubProblems{$SubProblemType}})} = values %{$SubProblems{$SubProblemType}};
    }
    @RecurTypes = ();
    # checking type definition changes
    my %SubProblems_Merge = mergeTypes($ParamType1_Id, $Tid_TDid{1}{$ParamType1_Id}, $ParamType2_Id, $Tid_TDid{2}{$ParamType2_Id});
    foreach my $SubProblemType (keys(%SubProblems_Merge))
    {
        foreach my $SubLocation (keys(%{$SubProblems_Merge{$SubProblemType}}))
        {
            my $NewProblemType = $SubProblemType;
            if($SubProblemType eq "DataType_Size")
            {
                my $InitialType_Type = $SubProblems_Merge{$SubProblemType}{$SubLocation}{"InitialType_Type"};
                if($InitialType_Type!~/\A(Pointer|Ref)\Z/ and $SubLocation!~/\-\>/)
                { # stack has been affected
                    $NewProblemType = "DataType_Size_And_Stack";
                }
            }
            my $NewLocation = ($SubLocation)?$Parameter_Location."->".$SubLocation:$Parameter_Location;
            %{$CompatProblems{$Interface}{$NewProblemType}{$NewLocation}}=(
                "Param_Type"=>get_TypeName($ParamType1_Id, 1),
                "Param_Pos"=>$ParamPos1,
                "Param_Name"=>$ParamName1  );
            @{$CompatProblems{$Interface}{$NewProblemType}{$NewLocation}}{keys(%{$SubProblems_Merge{$SubProblemType}{$SubLocation}})} = values %{$SubProblems_Merge{$SubProblemType}{$SubLocation}};
            if($SubLocation!~/\-\>/) {
                $CompatProblems{$Interface}{$NewProblemType}{$NewLocation}{"Start_Type_Name"} = get_TypeName($ParamType1_Id, 1);
            }
        }
    }
}

sub callingConvention($$$$)
{ # calling conventions for different compilers and operating systems
    my ($Interface, $ParamPos, $LibVersion, $Arch) = @_;
    my $ParamTypeId = $CompleteSignature{$LibVersion}{$Interface}{"Param"}{$ParamPos}{"type"};
    my %Type = get_PureType($Tid_TDid{$LibVersion}{$ParamTypeId}, $ParamTypeId, $LibVersion);
    my ($Method, $Alignment, $Passed, $Register) = ("", 0, "", "");
    if($OSgroup=~/\A(linux|macos|freebsd)\Z/)
    {# GCC
        if($Arch eq "x86")
        { # System V ABI Intel386 ("Function Calling Sequence")
          # The stack is word aligned. Although the architecture does not require any
          # alignment of the stack, software convention and the operating system
          # requires that the stack be aligned on a word boundary.

          # Argument words are pushed onto the stack in reverse order (that is, the
          # rightmost argument in C call syntax has the highest address), preserving the
          # stack’s word alignment. All incoming arguments appear on the stack, residing
          # in the stack frame of the caller.

          # An argument’s size is increased, if necessary, to make it a multiple of words.
          # This may require tail padding, depending on the size of the argument.

          # Other areas depend on the compiler and the code being compiled. The stan-
          # dard calling sequence does not define a maximum stack frame size, nor does
          # it restrict how a language system uses the ‘‘unspecified’’ area of the stan-
          # dard stack frame.
            ($Method, $Alignment) = ("stack", 4);
        }
        elsif($Arch eq "x86_64")
        { # System V AMD64 ABI ("Function Calling Sequence")
            ($Method, $Alignment) = ("stack", 8);# eightbyte aligned
        }
        elsif($Arch eq "arm")
        { # Procedure Call Standard for the ARM Architecture
          # The stack must be double-word aligned
            ($Method, $Alignment) = ("stack", 8);# double-word
        }
    }
    elsif($OSgroup eq "windows")
    {# MS C++ Compiler
        if($Arch eq "x86")
        {
            if($ParamPos==0) {
                ($Method, $Register, $Passed) = ("register", "ecx", "value");
            }
            elsif($ParamPos==1) {
                ($Method, $Register, $Passed) = ("register", "edx", "value");
            }
            else {
                ($Method, $Alignment) = ("stack", 4);
            }
        }
        elsif($Arch eq "x86_64")
        {
            if($ParamPos<=3)
            {
                if($Type{"Name"}=~/\A(float|double|long double)\Z/) {
                    ($Method, $Passed) = ("xmm".$ParamPos, "value");
                }
                elsif($Type{"Name"}=~/\A(unsigned |)(short|int|long|long long)\Z/
                or $Type{"Type"}=~/\A(Struct|Union|Enum|Array)\Z/
                or $Type{"Name"}=~/\A(__m64|__m128)\Z/)
                {
                    if($ParamPos==0) {
                        ($Method, $Register, $Passed) = ("register", "rcx", "value");
                    }
                    elsif($ParamPos==1) {
                        ($Method, $Register, $Passed) = ("register", "rdx", "value");
                    }
                    elsif($ParamPos==2) {
                        ($Method, $Register, $Passed) = ("register", "r8", "value");
                    }
                    elsif($ParamPos==3) {
                        ($Method, $Register, $Passed) = ("register", "r9", "value");
                    }
                    if($Type{"Size"}>64
                    or $Type{"Type"} eq "Array") {
                        $Passed = "pointer";
                    }
                }
            }
            else {
                ($Method, $Alignment) = ("stack", 8);# word alignment
            }
        }
    }
    if($Method eq "register") {
        return ("register", $Passed, "", $Register);
    }
    else
    {# on the stack
        if(not $Alignment)
        {# default convention
            $Alignment = $WORD_SIZE{$LibVersion};
        }
        if(not $Passed)
        {# default convention
            $Passed = "value";
        }
        my $SizeOnStack = $Type{"Size"};
        # FIXME: improve stack alignment
        if($SizeOnStack!=$Alignment) {
            $SizeOnStack = int(($Type{"Size"}+$Alignment)/$Alignment)*$Alignment;
        }
        return ("stack", $Passed, $SizeOnStack, "");
    }
}

sub find_ParamPair_Pos_byName($$$)
{
    my ($Name, $Interface, $LibVersion) = @_;
    foreach my $ParamPos (sort {int($a)<=>int($b)} keys(%{$CompleteSignature{$LibVersion}{$Interface}{"Param"}}))
    {
        next if(not defined $CompleteSignature{$LibVersion}{$Interface}{"Param"}{$ParamPos});
        if($CompleteSignature{$LibVersion}{$Interface}{"Param"}{$ParamPos}{"name"} eq $Name)
        {
            return $ParamPos;
        }
    }
    return "lost";
}

sub find_ParamPair_Pos_byTypeAndPos($$$$$)
{
    my ($TypeName, $MediumPos, $Order, $Interface, $LibVersion) = @_;
    my @Positions = ();
    foreach my $ParamPos (sort {int($a)<=>int($b)} keys(%{$CompleteSignature{$LibVersion}{$Interface}{"Param"}}))
    {
        next if($Order eq "backward" and $ParamPos>$MediumPos);
        next if($Order eq "forward" and $ParamPos<$MediumPos);
        next if(not defined $CompleteSignature{$LibVersion}{$Interface}{"Param"}{$ParamPos});
        my $PTypeId = $CompleteSignature{$LibVersion}{$Interface}{"Param"}{$ParamPos}{"type"};
        if(get_TypeName($PTypeId, $LibVersion) eq $TypeName) {
            push(@Positions, $ParamPos);
        }
    }
    return @Positions;
}

sub getTypeIdByName($$)
{
    my ($TypeName, $Version) = @_;
    return $TName_Tid{$Version}{formatName($TypeName)};
}

sub checkFormatChange($$)
{
    my ($Type1_Id, $Type2_Id) = @_;
    my $Type1_DId = $Tid_TDid{1}{$Type1_Id};
    my $Type2_DId = $Tid_TDid{2}{$Type2_Id};
    my %Type1_Pure = get_PureType($Type1_DId, $Type1_Id, 1);
    my %Type2_Pure = get_PureType($Type2_DId, $Type2_Id, 2);
    if($Type1_Pure{"Name"} eq $Type2_Pure{"Name"})
    { # equal types
        return 0;
    }
    if($Type1_Pure{"Name"}=~/\*/
    or $Type2_Pure{"Name"}=~/\*/)
    { # compared in detectTypeChange()
        return 0;
    }
    my %FloatType = map {$_=>1} (
        "float",
        "double",
        "long double"
    );
    if($Type1_Pure{"Type"} ne $Type2_Pure{"Type"})
    { # different types
        if($Type1_Pure{"Type"} eq "Intrinsic"
        and $Type2_Pure{"Type"} eq "Enum")
        { # "int" to "enum"
            return 0;
        }
        elsif($Type2_Pure{"Type"} eq "Intrinsic"
        and $Type1_Pure{"Type"} eq "Enum")
        { # "enum" to "int"
            return 0;
        }
        else
        { # "union" to "struct"
          #  ...
            return 1;
        }
    }
    else
    {
        if($Type1_Pure{"Type"} eq "Intrinsic")
        {
            if($FloatType{$Type1_Pure{"Name"}}
            or $FloatType{$Type2_Pure{"Name"}})
            { # "float" to "double"
              # "float" to "int"
                return 1;
            }
        }
        elsif($Type1_Pure{"Type"}=~/Class|Struct|Union|Enum/)
        {
            my @Membs1 = keys(%{$Type1_Pure{"Memb"}});
            my @Membs2 = keys(%{$Type2_Pure{"Memb"}});
            if($#Membs1!=$#Membs2)
            { # different number of elements
                return 1;
            }
            if($Type1_Pure{"Type"} 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)
                { # compare elements by type name
                    my $MT1 = get_TypeName($Type1_Pure{"Memb"}{$Pos}{"type"}, 1);
                    my $MT2 = get_TypeName($Type2_Pure{"Memb"}{$Pos}{"type"}, 2);
                    if($MT1 ne $MT2)
                    { # different types
                        return 1;
                    }
                }
            }
        }
    }
    return 0;
}

sub isScalar($) {
    return ($_[0]=~/\A(unsigned |)(short|int|long|long long)\Z/);
}

sub isFloat($) {
    return ($_[0]=~/\A(float|double|long double)\Z/);
}

sub detectTypeChange($$$)
{
    my ($Type1_Id, $Type2_Id, $Prefix) = @_;
    my %LocalProblems = ();
    my $Type1_DId = $Tid_TDid{1}{$Type1_Id};
    my $Type2_DId = $Tid_TDid{2}{$Type2_Id};
    my %Type1 = get_Type($Type1_DId, $Type1_Id, 1);
    my %Type2 = get_Type($Type2_DId, $Type2_Id, 2);
    my %Type1_Pure = get_PureType($Type1_DId, $Type1_Id, 1);
    my %Type2_Pure = get_PureType($Type2_DId, $Type2_Id, 2);
    my %Type1_Base = ($Type1_Pure{"Type"} eq "Array")?get_OneStep_BaseType($Type1_Pure{"TDid"}, $Type1_Pure{"Tid"}, 1):get_BaseType($Type1_DId, $Type1_Id, 1);
    my %Type2_Base = ($Type2_Pure{"Type"} eq "Array")?get_OneStep_BaseType($Type2_Pure{"TDid"}, $Type2_Pure{"Tid"}, 2):get_BaseType($Type2_DId, $Type2_Id, 2);
    my $Type1_PLevel = get_PointerLevel($Type1_DId, $Type1_Id, 1);
    my $Type2_PLevel = get_PointerLevel($Type2_DId, $Type2_Id, 2);
    return () if(not $Type1{"Name"} or not $Type2{"Name"});
    return () if(not $Type1_Base{"Name"} or not $Type2_Base{"Name"});
    return () if($Type1_PLevel eq "" or $Type2_PLevel eq "");
    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{"Type"} eq "Typedef" and $Type2{"Type"} eq "Typedef"
        and $Type1{"Name"} eq $Type2{"Name"})
        { # will be reported in mergeTypes() as typedef problem
            return ();
        }
        if($Type1_Base{"Name"}!~/anon\-/ and $Type2_Base{"Name"}!~/anon\-/)
        {
            if($Type1_Base{"Size"} ne $Type2_Base{"Size"}
            and $Type1_Base{"Size"} and $Type2_Base{"Size"})
            {
                %{$LocalProblems{$Prefix."_BaseType_And_Size"}}=(
                    "Old_Value"=>$Type1_Base{"Name"},
                    "New_Value"=>$Type2_Base{"Name"},
                    "Old_Size"=>$Type1_Base{"Size"}*$BYTE_SIZE,
                    "New_Size"=>$Type2_Base{"Size"}*$BYTE_SIZE,
                    "InitialType_Type"=>$Type1_Pure{"Type"});
            }
            else
            {
                if(checkFormatChange($Type1_Base{"Tid"}, $Type2_Base{"Tid"}))
                { # format change
                    %{$LocalProblems{$Prefix."_BaseType_Format"}}=(
                        "Old_Value"=>$Type1_Base{"Name"},
                        "New_Value"=>$Type2_Base{"Name"},
                        "Old_Size"=>$Type1_Base{"Size"}*$BYTE_SIZE,
                        "New_Size"=>$Type2_Base{"Size"}*$BYTE_SIZE,
                        "InitialType_Type"=>$Type1_Pure{"Type"});
                }
                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_SIZE,
                        "New_Size"=>$Type2_Base{"Size"}*$BYTE_SIZE,
                        "InitialType_Type"=>$Type1_Pure{"Type"});
                }
            }
        }
    }
    elsif($Type1{"Name"} ne $Type2{"Name"})
    { # type change
        if($Type1{"Name"}!~/anon\-/ and $Type2{"Name"}!~/anon\-/)
        {
            if($Prefix eq "Return" and $Type1{"Name"} eq "void"
            and $Type2_Pure{"Type"}=~/Intrinsic|Enum/) {
                # safe change
            }
            elsif($Prefix eq "Return"
            and $Type1_Pure{"Name"} eq "void")
            {
                %{$LocalProblems{"Return_Type_From_Void"}}=(
                    "New_Value"=>$Type2{"Name"},
                    "New_Size"=>$Type2{"Size"}*$BYTE_SIZE,
                    "InitialType_Type"=>$Type1_Pure{"Type"});
            }
            elsif($Prefix eq "Return" and $Type1_Pure{"Type"}=~/Intrinsic|Enum/
            and $Type2_Pure{"Type"}=~/Struct|Class|Union/)
            { # returns into hidden first parameter instead of a register
                
                # System V ABI Intel386 ("Function Calling Sequence")
                # A function that returns an integral or pointer value places its result in register %eax.

                # A floating-point return value appears on the top of the Intel387 register stack. The
                # caller then must remove the value from the Intel387 stack, even if it doesn’t use the
                # value.

                # If a function returns a structure or union, then the caller provides space for the
                # return value and places its address on the stack as argument word zero. In effect,
                # this address becomes a ‘‘hidden’’ first argument.
                
                %{$LocalProblems{"Return_Type_From_Register_To_Stack"}}=(
                    "Old_Value"=>$Type1{"Name"},
                    "New_Value"=>$Type2{"Name"},
                    "Old_Size"=>$Type1{"Size"}*$BYTE_SIZE,
                    "New_Size"=>$Type2{"Size"}*$BYTE_SIZE,
                    "InitialType_Type"=>$Type1_Pure{"Type"});
            }
            elsif($Prefix eq "Return"
            and $Type2_Pure{"Name"} eq "void")
            {
                %{$LocalProblems{"Return_Type_Became_Void"}}=(
                    "Old_Value"=>$Type1{"Name"},
                    "Old_Size"=>$Type1{"Size"}*$BYTE_SIZE,
                    "InitialType_Type"=>$Type1_Pure{"Type"});
            }
            elsif($Prefix eq "Return"
            and ((isScalar($Type1_Pure{"Name"}) and isFloat($Type2_Pure{"Name"}))
            or (isScalar($Type2_Pure{"Name"}) and isFloat($Type1_Pure{"Name"}))))
            { # The scalar and floating-point values are passed in different registers
                %{$LocalProblems{"Return_Type_And_Register"}}=(
                    "Old_Value"=>$Type1{"Name"},
                    "New_Value"=>$Type2{"Name"},
                    "Old_Size"=>$Type1{"Size"}*$BYTE_SIZE,
                    "New_Size"=>$Type2{"Size"}*$BYTE_SIZE,
                    "InitialType_Type"=>$Type1_Pure{"Type"});
            }
            elsif($Prefix eq "Return" and $Type2_Pure{"Type"}=~/Intrinsic|Enum/
            and $Type1_Pure{"Type"}=~/Struct|Class|Union/)
            { # returns in a register instead of a hidden first parameter
                %{$LocalProblems{"Return_Type_From_Stack_To_Register"}}=(
                    "Old_Value"=>$Type1{"Name"},
                    "New_Value"=>$Type2{"Name"},
                    "Old_Size"=>$Type1{"Size"}*$BYTE_SIZE,
                    "New_Size"=>$Type2{"Size"}*$BYTE_SIZE,
                    "InitialType_Type"=>$Type1_Pure{"Type"});
            }
            else
            {
                if($Type1{"Size"} ne $Type2{"Size"}
                and $Type1{"Size"} and $Type2{"Size"})
                {
                    %{$LocalProblems{$Prefix."_Type_And_Size"}}=(
                        "Old_Value"=>$Type1{"Name"},
                        "New_Value"=>$Type2{"Name"},
                        "Old_Size"=>$Type1{"Size"}*$BYTE_SIZE,
                        "New_Size"=>$Type2{"Size"}*$BYTE_SIZE,
                        "InitialType_Type"=>$Type1_Pure{"Type"});
                }
                else
                {
                    if(checkFormatChange($Type1_Id, $Type2_Id))
                    { # format change
                        %{$LocalProblems{$Prefix."_Type_Format"}}=(
                            "Old_Value"=>$Type1{"Name"},
                            "New_Value"=>$Type2{"Name"},
                            "Old_Size"=>$Type1{"Size"}*$BYTE_SIZE,
                            "New_Size"=>$Type2{"Size"}*$BYTE_SIZE,
                            "InitialType_Type"=>$Type1_Pure{"Type"});
                    }
                    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_SIZE,
                            "New_Size"=>$Type2{"Size"}*$BYTE_SIZE,
                            "InitialType_Type"=>$Type1_Pure{"Type"});
                    }
                }
            }
        }
    }
    if($Type1_PLevel!=$Type2_PLevel)
    {
        if($Type1{"Name"} ne "void" and $Type1{"Name"} ne "..."
        and $Type2{"Name"} ne "void" and $Type2{"Name"} ne "...")
        {
            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")
    { # base_type[N] -> base_type[N]
      # base_type: older_structure -> typedef to newer_structure
        my %SubProblems = detectTypeChange($Type1_Base{"Tid"}, $Type2_Base{"Tid"}, $Prefix);
        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) = @_;
    if(differentFmts())
    { # different formats
        if($UseOldDumps)
        { # old dumps
            return 0;
        }
        my $TN1 = get_TypeName($Tid1, 1);
        my $TN2 = get_TypeName($Tid2, 2);
        my %Base1 = get_Type($Tid_TDid{1}{$Tid1}, $Tid1, 1);
        while($Base1{"Type"} eq "Typedef") {
            %Base1 = get_OneStep_BaseType($Base1{"TDid"}, $Base1{"Tid"}, 1);
        }
        my %Base2 = get_Type($Tid_TDid{2}{$Tid2}, $Tid2, 2);
        while($Base2{"Type"} eq "Typedef") {
            %Base2 = get_OneStep_BaseType($Base2{"TDid"}, $Base2{"Tid"}, 2);
        }
        my $Base1 = uncover_typedefs($Base1{"Name"}, 1);
        my $Base2 = uncover_typedefs($Base2{"Name"}, 2);
        if($TN1 ne $TN2
        and $Base1 eq $Base2)
        { # equal base types
            return 0;
        }
        if(($UsedDump{1}{"V"} and cmpVersions($UsedDump{1}{"V"}, "2.6")<0)
        or ($UsedDump{2}{"V"} and cmpVersions($UsedDump{2}{"V"}, "2.6")<0))
        {
            if($TN1!~/(\A|\W)restrict(\W|\Z)/
            and $TN2=~/(\A|\W)restrict(\W|\Z)/) {
                return 0;
            }
        }
        
    }
    return 1;
}

sub differentFmts()
{
    if(getGccVersion(1) ne getGccVersion(2))
    { # different GCC versions
        return 1;
    }
    if(cmpVersions(formatVersion($UsedDump{1}{"V"}, 2),
    formatVersion($UsedDump{2}{"V"}, 2))!=0)
    { # different dump versions (skip micro version)
        return 1;
    }
    return 0;
}

sub formatVersion($$)
{ # cut off version digits
    my ($Version, $Digits) = @_;
    my @Elems = split(/\./, $Version);
    return join(".", splice(@Elems, 0, $Digits));
} 

sub htmlSpecChars($)
{
    my $Str = $_[0];
    $Str=~s/\&([^#]|\Z)/&amp;$1/g;
    $Str=~s/</&lt;/g;
    $Str=~s/\-\>/&#45;&gt;/g; # &minus;
    $Str=~s/>/&gt;/g;
    $Str=~s/([^ ])( )([^ ])/$1\@ALONE_SP\@$3/g;
    $Str=~s/ /&#160;/g; # &nbsp;
    $Str=~s/\@ALONE_SP\@/ /g;
    $Str=~s/\n/<br\/>/g;
    $Str=~s/\"/&quot;/g;
    $Str=~s/\'/&#39;/g;
    return $Str;
}

sub black_name($)
{
    my $Name = $_[0];
    return "<span class='iname_b'>".highLight_Signature($Name)."</span>";
}

sub highLight_Signature($)
{
    my $Signature = $_[0];
    return highLight_Signature_PPos_Italic($Signature, "", 0, 0, 0);
}

sub highLight_Signature_Italic_Color($)
{
    my $Signature = $_[0];
    return highLight_Signature_PPos_Italic($Signature, "", 1, 1, 1);
}

sub separate_symbol($)
{
    my $Symbol = $_[0];
    my ($Name, $Spec, $Ver) = ($Symbol, "", "");
    if($Symbol=~/\A([^\@\$\?]+)([\@\$]+)([^\@\$]+)\Z/) {
        ($Name, $Spec, $Ver) = ($1, $2, $3);
    }
    return ($Name, $Spec, $Ver);
}

sub cut_f_attrs($)
{
    if($_[0]=~s/(\))((| (const volatile|const|volatile))(| \[static\]))\Z/$1/) {
        return $2;
    }
    return "";
}

sub highLight_Signature_PPos_Italic($$$$$)
{
    my ($FullSignature, $Parameter_Position, $ItalicParams, $ColorParams, $ShowReturn) = @_;
    if($CheckObjectsOnly) {
        $ItalicParams=$ColorParams=0;
    }
    my ($Signature, $VersionSpec, $SymbolVersion) = separate_symbol($FullSignature);
    my $Return = "";
    if($ShowRetVal and $Signature=~s/([^:]):([^:].+?)\Z/$1/g) {
        $Return = $2;
    }
    my $SCenter = detect_center($Signature, "(");
    if(not $SCenter)
    {# global data
        $Signature = htmlSpecChars($Signature);
        $Signature=~s!(\[data\])!<span style='color:Black;font-weight:normal;'>$1</span>!g;
        $Signature .= (($SymbolVersion)?"<span class='symver'>&#160;$VersionSpec&#160;$SymbolVersion</span>":"");
        if($Return and $ShowReturn) {
            $Signature .= "<span class='int_p nowrap'> &#160;<b>:</b>&#160;&#160;".htmlSpecChars($Return)."</span>";
        }
        return $Signature;
    }
    my ($Begin, $End) = (substr($Signature, 0, $SCenter), "");
    $Begin.=" " if($Begin!~/ \Z/);
    $End = cut_f_attrs($Signature);
    my @Parts = ();
    my @SParts = get_s_params($Signature, 1);
    foreach my $Num (0 .. $#SParts)
    {
        my $Part = $SParts[$Num];
        $Part=~s/\A\s+|\s+\Z//g;
        my ($Part_Styled, $ParamName) = (htmlSpecChars($Part), "");
        if($Part=~/\([\*]+(\w+)\)/i) {
            $ParamName = $1;#func-ptr
        }
        elsif($Part=~/(\w+)[\,\)]*\Z/i) {
            $ParamName = $1;
        }
        if(not $ParamName) {
            push(@Parts, $Part_Styled);
            next;
        }
        if($ItalicParams and not $TName_Tid{1}{$Part}
        and not $TName_Tid{2}{$Part})
        {
            if($Parameter_Position ne ""
            and $Num==$Parameter_Position) {
                $Part_Styled =~ s!(\W)$ParamName([\,\)]|\Z)!$1<span class='focus_p'>$ParamName</span>$2!ig;
            }
            elsif($ColorParams) {
                $Part_Styled =~ s!(\W)$ParamName([\,\)]|\Z)!$1<span class='color_p'>$ParamName</span>$2!ig;
            }
            else {
                $Part_Styled =~ s!(\W)$ParamName([\,\)]|\Z)!$1<span style='font-style:italic;'>$ParamName</span>$2!ig;
            }
        }
        $Part_Styled=~s/,(\w)/, $1/g;
        push(@Parts, $Part_Styled);
    }
    if(@Parts)
    {
        foreach my $Num (0 .. $#Parts)
        {
            if($Num==$#Parts)
            { # add ")" to the last parameter
                $Parts[$Num] = "<span class='nowrap'>".$Parts[$Num]." )</span>";
            }
            elsif(length($Parts[$Num])<=45) {
                $Parts[$Num] = "<span class='nowrap'>".$Parts[$Num]."</span>";
            }
        }
        $Signature = htmlSpecChars($Begin)."<span class='int_p'>(&#160;".join(" ", @Parts)."</span>".$End;
    }
    else {
        $Signature = htmlSpecChars($Begin)."<span class='int_p'>(&#160;)</span>".$End;
    }
    if($Return and $ShowReturn) {
        $Signature .= "<span class='int_p nowrap'> &#160;<b>:</b>&#160;&#160;".htmlSpecChars($Return)."</span>";
    }
    $Signature=~s!\[\]![<span style='padding-left:2px;'>]</span>!g;
    $Signature=~s!operator=!operator<span style='padding-left:2px'>=</span>!g;
    $Signature=~s!(\[in-charge\]|\[not-in-charge\]|\[in-charge-deleting\]|\[static\])!<span style='color:Black;font-weight:normal;'>$1</span>!g;
    return $Signature.(($SymbolVersion)?"<span class='symver'>&#160;$VersionSpec&#160;$SymbolVersion</span>":"");
}

sub get_s_params($$)
{
    my ($Signature, $Comma) = @_;
    my @Parts = ();
    my $Signature = $Signature;
    my $ShortName = substr($Signature, 0, detect_center($Signature, "("));
    $Signature=~s/\A\Q$ShortName\E\(//g;
    cut_f_attrs($Signature);
    $Signature=~s/\)\Z//;
    return separate_params($Signature, $Comma);
}

sub separate_params($$)
{
    my ($Params, $Comma) = @_;
    my @Parts = ();
    my ($Bracket_Num, $Bracket2_Num, $Part_Num) = (0, 0, 0);
    foreach my $Pos (0 .. length($Params) - 1)
    {
        my $Symbol = substr($Params, $Pos, 1);
        $Bracket_Num += 1 if($Symbol eq "(");
        $Bracket_Num -= 1 if($Symbol eq ")");
        $Bracket2_Num += 1 if($Symbol eq "<");
        $Bracket2_Num -= 1 if($Symbol eq ">");
        if($Symbol eq "," and $Bracket_Num==0 and $Bracket2_Num==0)
        {
            if($Comma) {# include comma
                $Parts[$Part_Num] .= $Symbol;
            }
            $Part_Num += 1;
        }
        else {
            $Parts[$Part_Num] .= $Symbol;
        }
    }
    return @Parts;
}

sub detect_center($$)
{
    my ($Sign, $Target) = @_;
    my %B = (
        "("=>0,
        "<"=>0,
        ")"=>0,
        ">"=>0 );
    my $Center = 0;
    if($Sign=~s/(operator([<>\-\=\*]+|\(\)))//g)
    { # operators: (),->,->*,<,<=,<<,<<=,>,>=,>>,>>=
        $Center+=length($1);
    }
    foreach my $Pos (0 .. length($Sign)-1)
    {
        my $S = substr($Sign, $Pos, 1);
        if($S eq $Target)
        {
            if($B{"("}==$B{")"}
            and $B{"<"}==$B{">"}) {
                return $Center;
            }
        }
        if(defined $B{$S}) {
            $B{$S}+=1;
        }
        $Center+=1;
    }
    return 0;
}

sub appendFile($$)
{
    my ($Path, $Content) = @_;
    return if(not $Path);
    if(my $Dir = get_dirname($Path)) {
        mkpath($Dir);
    }
    open(FILE, ">>".$Path) || die ("can't open file \'$Path\': $!\n");
    print FILE $Content;
    close(FILE);
}

sub writeFile($$)
{
    my ($Path, $Content) = @_;
    return if(not $Path);
    if(my $Dir = get_dirname($Path)) {
        mkpath($Dir);
    }
    open (FILE, ">".$Path) || die ("can't open file \'$Path\': $!\n");
    print FILE $Content;
    close(FILE);
}

sub readFile($)
{
    my $Path = $_[0];
    return "" if(not $Path or not -f $Path);
    open (FILE, $Path);
    local $/ = undef;
    my $Content = <FILE>;
    close(FILE);
    if($Path!~/\.(tu|class)\Z/) {
        $Content=~s/\r/\n/g;
    }
    return $Content;
}

sub get_filename($)
{ # much faster than basename() from File::Basename module
    if($_[0]=~/([^\/\\]+)[\/\\]*\Z/) {
        return $1;
    }
    return "";
}

sub get_dirname($)
{ # much faster than dirname() from File::Basename module
    if($_[0]=~/\A(.*?)[\/\\]+[^\/\\]*[\/\\]*\Z/) {
        return $1;
    }
    return "";
}

sub separate_path($) {
    return (get_dirname($_[0]), get_filename($_[0]));
}

sub esc($)
{
    my $Str = $_[0];
    $Str=~s/([()\[\]{}$ &'"`;,<>\+])/\\$1/g;
    return $Str;
}

sub readLineNum($$)
{
    my ($Path, $Num) = @_;
    return "" if(not $Path or not -f $Path);
    open (FILE, $Path);
    foreach (1 ... $Num) {
        <FILE>;
    }
    my $Line = <FILE>;
    close(FILE);
    return $Line;
}

sub readAttributes($)
{
    my $Path = $_[0];
    return () if(not $Path or not -f $Path);
    my %Attributes = ();
    if(readLineNum($Path, 0)=~/<!--\s+(.+)\s+-->/) {
        foreach my $AttrVal (split(/;/, $1)) {
            if($AttrVal=~/(.+):(.+)/)
            {
                my ($Name, $Value) = ($1, $2);
                $Attributes{$Name} = $Value;
            }
        }
    }
    return \%Attributes;
}

sub is_abs($) {
    return ($_[0]=~/\A(\/|\w+:[\/\\])/);
}

sub get_abs_path($)
{ # abs_path() should NOT be called for absolute inputs
  # because it can change them
    my $Path = $_[0];
    if(not is_abs($Path)) {
        $Path = abs_path($Path);
    }
    return $Path;
}

sub get_OSgroup()
{
    $_ = $Config{"osname"};
    if(/macos|darwin|rhapsody/i) {
        return "macos";
    }
    elsif(/freebsd|openbsd|netbsd/i) {
        return "bsd";
    }
    elsif(/haiku|beos/i) {
        return "beos";
    }
    elsif(/symbian|epoc/i) {
        return "symbian";
    }
    elsif(/win/i) {
        return "windows";
    }
    else {
        return $_;
    }
}

sub getGccVersion($)
{
    my $LibVersion = $_[0];
    if($GCC_VERSION{$LibVersion})
    { # dump version
        return $GCC_VERSION{$LibVersion};
    }
    elsif($UsedDump{$LibVersion}{"V"})
    { # old-version dumps
        return "unknown";
    }
    my $GccVersion = get_dumpversion($GCC_PATH); # host version
    if(not $GccVersion) {
        return "unknown";
    }
    return $GccVersion;
}

sub showArch($)
{
    my $Arch = $_[0];
    if($Arch eq "arm"
    or $Arch eq "mips") {
        return uc($Arch);
    }
    return $Arch;
}

sub getArch($)
{
    my $LibVersion = $_[0];
    if($CPU_ARCH{$LibVersion})
    { # dump version
        return $CPU_ARCH{$LibVersion};
    }
    elsif($UsedDump{$LibVersion}{"V"})
    { # old-version dumps
        return "unknown";
    }
    if(defined $Cache{"getArch"}{$LibVersion}) {
        return $Cache{"getArch"}{$LibVersion};
    }
    my $Arch = get_dumpmachine($GCC_PATH); # host version
    if(not $Arch) {
        return "unknown";
    }
    if($Arch=~/\A([\w]{3,})(-|\Z)/) {
        $Arch = $1;
    }
    $Arch = "x86" if($Arch=~/\Ai[3-7]86\Z/);
    if($OSgroup eq "windows") {
        $Arch = "x86" if($Arch=~/win32|mingw32/i);
        $Arch = "x86_64" if($Arch=~/win64|mingw64/i);
    }
    $Cache{"getArch"}{$LibVersion} = $Arch;
    return $Arch;
}

sub get_Report_Header()
{
    my $ArchInfo = " on <span style='color:Blue;'>".showArch(getArch(1))."</span>";
    if(getArch(1) ne getArch(2) or getArch(1) eq "unknown")
    { # don't show architecture in the header
        $ArchInfo="";
    }
    my $Report_Header = "<h1><span class='nowrap'>Binary compatibility report for the <span style='color:Blue;'>$TargetLibraryFName</span> $TargetComponent</span>";
    $Report_Header .= " <span class='nowrap'>&#160;between <span style='color:Red;'>".$Descriptor{1}{"Version"}."</span> and <span style='color:Red;'>".$Descriptor{2}{"Version"}."</span> versions$ArchInfo</span>";
    if($AppPath) {
        $Report_Header .= " <span class='nowrap'>&#160;(relating to the portability of application <span style='color:Blue;'>".get_filename($AppPath)."</span>)</span>";
    }
    $Report_Header .= "</h1>\n";
    return $Report_Header;
}

sub get_SourceInfo()
{
    my $CheckedHeaders = "<a name='Headers'></a><h2>Header Files (".keys(%{$Registered_Headers{1}}).")</h2><hr/>\n";
    $CheckedHeaders .= "<div class='h_list'>\n";
    foreach my $Header_Path (sort {lc($Registered_Headers{1}{$a}{"Identity"}) cmp lc($Registered_Headers{1}{$b}{"Identity"})} keys(%{$Registered_Headers{1}}))
    {
        my $Identity = $Registered_Headers{1}{$Header_Path}{"Identity"};
        my $Header_Name = get_filename($Identity);
        my $Dest_Comment = ($Identity=~/[\/\\]/)?" ($Identity)":"";
        $CheckedHeaders .= "$Header_Name$Dest_Comment<br/>\n";
    }
    $CheckedHeaders .= "</div>\n";
    $CheckedHeaders .= "<br/><a style='font-size:11px;' href='#Top'>to the top</a><br/>\n";
    my $CheckedLibs = "<a name='Libs'></a><h2>".ucfirst($SLIB_TYPE)." Libraries (".keys(%{$Library_Symbol{1}}).")</h2><hr/>\n";
    $CheckedLibs .= "<div class='lib_list'>\n";
    foreach my $Library (sort {lc($a) cmp lc($b)}  keys(%{$Library_Symbol{1}}))
    {
        $Library.=" (.$LIB_EXT)" if($Library!~/\.\w+\Z/);
        $CheckedLibs .= "$Library<br/>\n";
    }
    $CheckedLibs .= "</div>\n";
    $CheckedLibs .= "<br/><a style='font-size:11px;' href='#Top'>to the top</a><br/>\n";
    if($CheckObjectsOnly) {
        $CheckedHeaders = "";
    }
    if($CheckHeadersOnly) {
        $CheckedLibs = "";
    }
    return $CheckedHeaders.$CheckedLibs;
}

sub get_TypeProblems_Count($$$)
{
    my ($TypeChanges, $TargetPriority, $Level) = @_;
    my $Type_Problems_Count = 0;
    foreach my $Type_Name (sort keys(%{$TypeChanges}))
    {
        my %Kinds_Target = ();
        foreach my $Kind (keys(%{$TypeChanges->{$Type_Name}}))
        {
            foreach my $Location (keys(%{$TypeChanges->{$Type_Name}{$Kind}}))
            {
                my $Target = $TypeChanges->{$Type_Name}{$Kind}{$Location}{"Target"};
                my $Priority = getProblemSeverity($Level, $Kind);
                next if($Priority ne $TargetPriority);
                if($Kinds_Target{$Kind}{$Target}) {
                    next;
                }
                if(cmp_priority($Type_MaxPriority{$Level}{$Type_Name}{$Kind}{$Target}, $Priority))
                { # select a problem with the highest priority
                    next;
                }
                $Kinds_Target{$Kind}{$Target} = 1;
                $Type_Problems_Count += 1;
            }
        }
    }
    return $Type_Problems_Count;
}

sub get_Summary($)
{
    my $Level = $_[0]; # API or ABI
    my ($Added, $Removed, $I_Problems_High, $I_Problems_Medium, $I_Problems_Low,
    $T_Problems_High, $T_Problems_Medium, $T_Problems_Low, $I_Other, $T_Other) = (0,0,0,0,0,0,0,0,0,0);
    # check rules
    foreach my $Interface (sort keys(%CompatProblems))
    {
        foreach my $Kind (keys(%{$CompatProblems{$Interface}}))
        {
            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{$Interface}{$Kind});
            }
        }
    }
    foreach my $Interface (sort keys(%CompatProblems))
    {
        foreach my $Kind (sort keys(%{$CompatProblems{$Interface}}))
        {
            if($CompatRules{$Level}{$Kind}{"Kind"} eq "Symbols")
            {
                foreach my $Location (sort keys(%{$CompatProblems{$Interface}{$Kind}}))
                {
                    my $Priority = getProblemSeverity($Level, $Kind);
                    if($Kind eq "Added_Interface") {
                        $Added += 1;
                    }
                    elsif($Kind eq "Removed_Interface")
                    {
                        $Removed += 1;
                        $TotalAffected{$Level}{$Interface} = $Priority;
                    }
                    else
                    {
                        if($Priority eq "Safe") {
                            $I_Other += 1;
                        }
                        elsif($Priority eq "High") {
                            $I_Problems_High += 1;
                        }
                        elsif($Priority eq "Medium") {
                            $I_Problems_Medium += 1;
                        }
                        elsif($Priority eq "Low") {
                            $I_Problems_Low += 1;
                        }
                        if(($Priority ne "Low" or $StrictCompat)
                        and $Priority ne "Safe") {
                            $TotalAffected{$Level}{$Interface} = $Priority;
                        }
                    }
                }
            }
        }
    }
    my %TypeChanges = ();
    foreach my $Interface (sort keys(%CompatProblems))
    {
        foreach my $Kind (keys(%{$CompatProblems{$Interface}}))
        {
            if($CompatRules{$Level}{$Kind}{"Kind"} eq "Types")
            {
                foreach my $Location (sort {cmp_locations($b, $a)} sort keys(%{$CompatProblems{$Interface}{$Kind}}))
                {
                    my $Type_Name = $CompatProblems{$Interface}{$Kind}{$Location}{"Type_Name"};
                    my $Target = $CompatProblems{$Interface}{$Kind}{$Location}{"Target"};
                    my $Priority = getProblemSeverity($Level, $Kind);
                    if(cmp_priority($Type_MaxPriority{$Level}{$Type_Name}{$Kind}{$Target}, $Priority))
                    { # select a problem with the highest priority
                        next;
                    }
                    if(($Priority ne "Low" or $StrictCompat)
                    and $Priority ne "Safe") {
                        $TotalAffected{$Level}{$Interface} = max_priority($TotalAffected{$Level}{$Interface}, $Priority);
                    }
                    %{$TypeChanges{$Type_Name}{$Kind}{$Location}} = %{$CompatProblems{$Interface}{$Kind}{$Location}};
                    $Type_MaxPriority{$Level}{$Type_Name}{$Kind}{$Target} = max_priority($Type_MaxPriority{$Level}{$Type_Name}{$Kind}{$Target}, $Priority);
                }
            }
        }
    }
    $T_Problems_High = get_TypeProblems_Count(\%TypeChanges, "High", $Level);
    $T_Problems_Medium = get_TypeProblems_Count(\%TypeChanges, "Medium", $Level);
    $T_Problems_Low = get_TypeProblems_Count(\%TypeChanges, "Low", $Level);
    $T_Other = get_TypeProblems_Count(\%TypeChanges, "Safe", $Level);
    if($CheckObjectsOnly)
    { # only removed exported symbols
        $RESULT{"Affected"} = $Removed*100/keys(%{$Symbol_Library{1}});
    }
    else
    { # changed and removed public symbols
        my $SCount = keys(%CheckedSymbols);
        if($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{"Affected"}+=$Weight{$TotalAffected{$Level}{$_}};
            }
            $RESULT{"Affected"} = $RESULT{"Affected"}/$SCount;
        }
        else {
            $RESULT{"Affected"} = 0;
        }
    }
    $RESULT{"Affected"} = show_number($RESULT{"Affected"});
    if($RESULT{"Affected"}>=100) {
        $RESULT{"Affected"} = 100;
    }
    
    $RESULT{"Problems"} = $Removed + $T_Problems_High + $I_Problems_High;
    $RESULT{"Problems"} += $T_Problems_Medium + $I_Problems_Medium;
    $RESULT{$StrictCompat?"Problems":"Warnings"} += $T_Problems_Low + $I_Problems_Low;
    $RESULT{$StrictCompat?"Problems":"Warnings"} += keys(%ProblemsWithConstants);
    if($CheckImpl) {
        $RESULT{$StrictCompat?"Problems":"Warnings"} += keys(%ImplProblems);
    }
    $RESULT{"Verdict"} = $RESULT{"Problems"}?"incompatible":"compatible";
    
    my $TotalTypes = keys(%CheckedTypes);
    if(not $TotalTypes) {# list all the types
        $TotalTypes = keys(%{$TName_Tid{1}});
    }
    
    my ($Arch1, $Arch2) = (getArch(1), getArch(2));
    my ($GccV1, $GccV2) = (getGccVersion(1), getGccVersion(2));
    
    my ($TestInfo, $TestResults, $Problem_Summary) = ();
    
    if($ReportFormat eq "xml")
    { # XML
        # test info
        $TestInfo .= "  <library>$TargetLibraryName</library>\n";
        $TestInfo .= "  <version1>\n";
        $TestInfo .= "    <number>".$Descriptor{1}{"Version"}."</number>\n";
        $TestInfo .= "    <architecture>$Arch1</architecture>\n";
        $TestInfo .= "    <gcc>$GccV1</gcc>\n";
        $TestInfo .= "  </version1>\n";
        
        $TestInfo .= "  <version2>\n";
        $TestInfo .= "    <number>".$Descriptor{2}{"Version"}."</number>\n";
        $TestInfo .= "    <architecture>$Arch2</architecture>\n";
        $TestInfo .= "    <gcc>$GccV2</gcc>\n";
        $TestInfo .= "  </version2>\n";
        $TestInfo = "<test_info>\n".$TestInfo."</test_info>\n\n";
        
        # test results
        $TestResults .= "  <headers>\n";
        foreach my $Name (sort {lc($Registered_Headers{1}{$a}{"Identity"}) cmp lc($Registered_Headers{1}{$b}{"Identity"})} keys(%{$Registered_Headers{1}}))
        {
            my $Identity = $Registered_Headers{1}{$_}{"Identity"};
            my $Comment = ($Identity=~/[\/\\]/)?" ($Identity)":"";
            $TestResults .= "    <name>".get_filename($Name).$Comment."</name>\n";
        }
        $TestResults .= "  </headers>\n";
        
        $TestResults .= "  <libs>\n";
        foreach my $Library (sort {lc($a) cmp lc($b)}  keys(%{$Library_Symbol{1}}))
        {
            $Library.=" (.$LIB_EXT)" if($Library!~/\.\w+\Z/);
            $TestResults .= "    <name>$Library</name>\n";
        }
        $TestResults .= "  </libs>\n";
        
        $TestResults .= "  <symbols>".(keys(%CheckedSymbols) - keys(%GeneratedSymbols))."</symbols>\n";
        $TestResults .= "  <types>".$TotalTypes."</types>\n";
        
        $TestResults .= "  <verdict>".$RESULT{"Verdict"}."</verdict>\n";
        $TestResults .= "  <affected>".$RESULT{"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 .= "  </problems_with_symbols>\n";
        
        $Problem_Summary .= "  <problems_with_constants>\n";
        $Problem_Summary .= "    <low>".keys(%ProblemsWithConstants)."</low>\n";
        $Problem_Summary .= "  </problems_with_constants>\n";
        if($CheckImpl)
        {
            $Problem_Summary .= "  <impl>\n";
            $Problem_Summary .= "    <low>".keys(%ImplProblems)."</low>\n";
            $Problem_Summary .= "  </impl>\n";
        }
        $Problem_Summary = "<problem_summary>\n".$Problem_Summary."</problem_summary>\n\n";
        
        return ($TestInfo.$TestResults.$Problem_Summary, "");
    }
    else
    { # HTML
        # test info
        $TestInfo = "<h2>Test Info</h2><hr/>\n";
        $TestInfo .= "<table cellpadding='3' cellspacing='0' class='summary'>\n";
        $TestInfo .= "<tr><th>".ucfirst($TargetComponent)." Name</th><td>$TargetLibraryFName</td></tr>\n";
        
        my (@VInf1, @VInf2, $AddTestInfo) = ();
        if($Arch1 ne "unknown"
        and $Arch2 ne "unknown")
        { # CPU arch
            if($Arch1 eq $Arch2)
            { # go to the separate section
                $AddTestInfo .= "<tr><th>CPU Architecture</th><td>".showArch($Arch1)."</td></tr>\n";
            }
            else
            { # go to the version number
                push(@VInf1, showArch($Arch1));
                push(@VInf2, showArch($Arch2));
            }
        }
        if($GccV1 ne "unknown"
        and $GccV2 ne "unknown"
        and $OStarget ne "windows")
        { # 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);
            }
        }
        # show long version names with GCC version and CPU architecture name (if different)
        $TestInfo .= "<tr><th>Version #1</th><td>".$Descriptor{1}{"Version"}.(@VInf1?" (".join(", ", reverse(@VInf1)).")":"")."</td></tr>\n";
        $TestInfo .= "<tr><th>Version #2</th><td>".$Descriptor{2}{"Version"}.(@VInf2?" (".join(", ", reverse(@VInf2)).")":"")."</td></tr>\n";
        $TestInfo .= $AddTestInfo;
        #if($COMMON_LANGUAGE{1}) {
        #    $TestInfo .= "<tr><th>Language</th><td>".$COMMON_LANGUAGE{1}."</td></tr>\n";
        #}
        if($ExtendedCheck) {
            $TestInfo .= "<tr><th>Mode</th><td>Extended</td></tr>\n";
        }
        $TestInfo .= "</table>\n";
        
        # test results
        $TestResults = "<h2>Test Results</h2><hr/>\n";
        $TestResults .= "<table cellpadding='3' cellspacing='0' class='summary'>";
        
        my $Headers_Link = "0";
        $Headers_Link = "<a href='#Headers' style='color:Blue;'>".keys(%{$Registered_Headers{1}})."</a>" if(keys(%{$Registered_Headers{1}})>0);
        $TestResults .= "<tr><th>Total Header Files</th><td>".($CheckObjectsOnly?"0&#160;(not&#160;analyzed)":$Headers_Link)."</td></tr>\n";
        
        if(not $ExtendedCheck)
        {
            my $Libs_Link = "0";
            $Libs_Link = "<a href='#Libs' style='color:Blue;'>".keys(%{$Library_Symbol{1}})."</a>" if(keys(%{$Library_Symbol{1}})>0);
            $TestResults .= "<tr><th>Total ".ucfirst($SLIB_TYPE)." Libraries</th><td>".($CheckHeadersOnly?"0&#160;(not&#160;analyzed)":$Libs_Link)."</td></tr>\n";
        }
        
        $TestResults .= "<tr><th>Total Symbols / Types</th><td>".(keys(%CheckedSymbols) - keys(%GeneratedSymbols))." / ".$TotalTypes."</td></tr>\n";
        
        my $Verdict = "";
        if($RESULT{"Problems"}) {
            $Verdict = "<span style='color:Red;'><b>Incompatible<br/>(".$RESULT{"Affected"}."%)</b></span>";
        }
        else {
            $Verdict = "<span style='color:Green;'><b>Compatible</b></span>";
        }
        my $META_DATA = $RESULT{"Problems"}?"verdict:incompatible;":"verdict:compatible;";
        
        $TestResults .= "<tr><th>Verdict</th><td>$Verdict</td></tr>";
        $TestResults .= "</table>\n";
        
        $META_DATA .= "affected:".$RESULT{"Affected"}.";";# in percents
        # problem summary
        $Problem_Summary = "<h2>Problem Summary</h2><hr/>\n";
        $Problem_Summary .= "<table cellpadding='3' cellspacing='0' 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";
        $Added_Link = "<a href='#Added' style='color:Blue;'>$Added</a>" if($Added>0);
        #$Added_Link = "n/a" if($CheckHeadersOnly);
        $META_DATA .= "added:$Added;";
        $Problem_Summary .= "<tr><th>Added Symbols</th><td>-</td><td>$Added_Link</td></tr>\n";
        
        my $Removed_Link = "0";
        $Removed_Link = "<a href='#Removed' style='color:Blue;'>$Removed</a>" if($Removed>0);
        #$Removed_Link = "n/a" if($CheckHeadersOnly);
        $META_DATA .= "removed:$Removed;";
        $Problem_Summary .= "<tr><th>Removed Symbols</th><td style='color:Red;'>High</td><td>$Removed_Link</td></tr>\n";
        
        my $TH_Link = "0";
        $TH_Link = "<a href='#Type_Problems_High' style='color:Blue;'>$T_Problems_High</a>" if($T_Problems_High>0);
        $TH_Link = "n/a" if($CheckObjectsOnly);
        $META_DATA .= "type_problems_high:$T_Problems_High;";
        $Problem_Summary .= "<tr><th rowspan='3'>Problems with<br/>Data Types</th><td style='color:Red;'>High</td><td>$TH_Link</td></tr>\n";
        
        my $TM_Link = "0";
        $TM_Link = "<a href='#Type_Problems_Medium' style='color:Blue;'>$T_Problems_Medium</a>" if($T_Problems_Medium>0);
        $TM_Link = "n/a" if($CheckObjectsOnly);
        $META_DATA .= "type_problems_medium:$T_Problems_Medium;";
        $Problem_Summary .= "<tr><td>Medium</td><td>$TM_Link</td></tr>\n";
        
        my $TL_Link = "0";
        $TL_Link = "<a href='#Type_Problems_Low' style='color:Blue;'>$T_Problems_Low</a>" if($T_Problems_Low>0);
        $TL_Link = "n/a" if($CheckObjectsOnly);
        $META_DATA .= "type_problems_low:$T_Problems_Low;";
        $Problem_Summary .= "<tr><td>Low</td><td>$TL_Link</td></tr>\n";
        
        my $IH_Link = "0";
        $IH_Link = "<a href='#Interface_Problems_High' style='color:Blue;'>$I_Problems_High</a>" if($I_Problems_High>0);
        $IH_Link = "n/a" if($CheckObjectsOnly);
        $META_DATA .= "interface_problems_high:$I_Problems_High;";
        $Problem_Summary .= "<tr><th rowspan='3'>Problems with<br/>Symbols</th><td style='color:Red;'>High</td><td>$IH_Link</td></tr>\n";
        
        my $IM_Link = "0";
        $IM_Link = "<a href='#Interface_Problems_Medium' style='color:Blue;'>$I_Problems_Medium</a>" if($I_Problems_Medium>0);
        $IM_Link = "n/a" if($CheckObjectsOnly);
        $META_DATA .= "interface_problems_medium:$I_Problems_Medium;";
        $Problem_Summary .= "<tr><td>Medium</td><td>$IM_Link</td></tr>\n";
        
        my $IL_Link = "0";
        $IL_Link = "<a href='#Interface_Problems_Low' style='color:Blue;'>$I_Problems_Low</a>" if($I_Problems_Low>0);
        $IL_Link = "n/a" if($CheckObjectsOnly);
        $META_DATA .= "interface_problems_low:$I_Problems_Low;";
        $Problem_Summary .= "<tr><td>Low</td><td>$IL_Link</td></tr>\n";
        
        my $ChangedConstants_Link = "0";
        if(keys(%CheckedSymbols) and keys(%ProblemsWithConstants)>0) {
            $ChangedConstants_Link = "<a href='#Changed_Constants' style='color:Blue;'>".keys(%ProblemsWithConstants)."</a>";
        }
        $ChangedConstants_Link = "n/a" if($CheckObjectsOnly);
        $META_DATA .= "changed_constants:".keys(%ProblemsWithConstants).";";
        $Problem_Summary .= "<tr><th>Problems with<br/>Constants</th><td>Low</td><td>$ChangedConstants_Link</td></tr>\n";
        
        if($CheckImpl)
        {
            my $ChangedImpl_Link = "0";
            $ChangedImpl_Link = "<a href='#Changed_Implementation' style='color:Blue;'>".keys(%ImplProblems)."</a>" if(keys(%ImplProblems)>0);
            $ChangedImpl_Link = "n/a" if($CheckHeadersOnly);
            $META_DATA .= "changed_implementation:".keys(%ImplProblems).";";
            $Problem_Summary .= "<tr><th>Problems with<br/>Implementation</th><td>Low</td><td>$ChangedImpl_Link</td></tr>\n";
        }
        # Safe Changes
        my $TS_Link = "0";
        if($T_Other) {
            $TS_Link = "<a href='#Other_Changes_In_Types' style='color:Blue;'>$T_Other</a>";
        }
        $TS_Link = "n/a" if($CheckObjectsOnly);
        $Problem_Summary .= "<tr><th>Other Changes</th><td>-</td><td>$TS_Link</td></tr>\n";
        
        $META_DATA .= "tool_version:$TOOL_VERSION";
        $Problem_Summary .= "</table>\n";
        return ($TestInfo.$TestResults.$Problem_Summary, $META_DATA);
    }
}

sub show_number($)
{
    if($_[0]
    and my $Num = cut_off_number($_[0], 3))
    {
        if($Num eq "0") {
            $Num = cut_off_number($_[0], 7);
        }
        if($Num eq "0") {
            $Num = $_[0];
        }
        return $Num;
    }
    return $_[0];
}

sub cut_off_number($$)
{
    my ($num, $digs_to_cut) = @_;
    if($num!~/\./)
    {
        $num .= ".";
        foreach (1 .. $digs_to_cut-1) {
            $num .= "0";
        }
    }
    elsif($num=~/\.(.+)\Z/ and length($1)<$digs_to_cut-1)
    {
        foreach (1 .. $digs_to_cut - 1 - length($1)) {
            $num .= "0";
        }
    }
    elsif($num=~/\d+\.(\d){$digs_to_cut,}/) {
      $num=sprintf("%.".($digs_to_cut-1)."f", $num);
    }
    $num=~s/\.[0]+\Z//g;
    return $num;
}

sub get_Report_ChangedConstants()
{
    my ($CHANGED_CONSTANTS, %HeaderConstant) = ();
    foreach my $Constant (keys(%ProblemsWithConstants)) {
        $HeaderConstant{$Constants{1}{$Constant}{"Header"}}{$Constant} = 1;
    }
    my $Kind = "Changed_Constant";
    if($ReportFormat eq "xml")
    { # XML
        foreach my $HeaderName (sort {lc($a) cmp lc($b)} keys(%HeaderConstant))
        {
            $CHANGED_CONSTANTS .= "  <header name=\"$HeaderName\">\n";
            foreach my $Constant (sort {lc($a) cmp lc($b)} keys(%{$HeaderConstant{$HeaderName}}))
            {
                $CHANGED_CONSTANTS .= "    <constant name=\"$Constant\">\n";
                my $Change = $CompatRules{"Binary"}{$Kind}{"Change"};
                my $Effect = $CompatRules{"Binary"}{$Kind}{"Effect"};
                my $Overcome = $CompatRules{"Binary"}{$Kind}{"Overcome"};
                $CHANGED_CONSTANTS .= "      <problem id=\"$Kind\">\n";
                $CHANGED_CONSTANTS .= "        <change".getXmlParams($Change, $ProblemsWithConstants{$Constant}).">$Change</change>\n";
                $CHANGED_CONSTANTS .= "        <effect".getXmlParams($Effect, $ProblemsWithConstants{$Constant}).">$Effect</effect>\n";
                $CHANGED_CONSTANTS .= "        <overcome".getXmlParams($Overcome, $ProblemsWithConstants{$Constant}).">$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 $Number = 0;
        foreach my $HeaderName (sort {lc($a) cmp lc($b)} keys(%HeaderConstant))
        {
            $CHANGED_CONSTANTS .= "<span class='h_name'>$HeaderName</span><br/>\n";
            foreach my $Name (sort {lc($a) cmp lc($b)} keys(%{$HeaderConstant{$HeaderName}}))
            {
                $Number += 1;
                my $Change = applyMacroses("Binary", $Kind, $CompatRules{"Binary"}{$Kind}{"Change"}, $ProblemsWithConstants{$Name});
                my $Effect = $CompatRules{"Binary"}{$Kind}{"Effect"};
                my $Report = "<tr><th>1</th><td align='left' valign='top'>".$Change."</td><td align='left' valign='top'>$Effect</td></tr>\n";
                $Report = $ContentDivStart."<table cellpadding='3' cellspacing='0' class='problems_table'><tr><th width='2%'></th><th width='47%'>Change</th><th>Effect</th></tr>".$Report."</table><br/>$ContentDivEnd\n";
                $Report = $ContentSpanStart."<span class='extendable'>[+]</span> ".$Name.$ContentSpanEnd."<br/>\n".$Report;
                $CHANGED_CONSTANTS .= insertIDs($Report);
            }
            $CHANGED_CONSTANTS .= "<br/>\n";
        }
        if($CHANGED_CONSTANTS) {
            $CHANGED_CONSTANTS = "<a name='Changed_Constants'></a><h2>Problems with Constants ($Number)</h2><hr/>\n".$CHANGED_CONSTANTS."<a style='font-size:11px;' href='#Top'>to the top</a><br/>\n";
        }
    }
    return $CHANGED_CONSTANTS;
}

sub get_Report_Impl()
{
    my ($CHANGED_IMPLEMENTATION, %HeaderLibFunc);
    foreach my $Interface (sort keys(%ImplProblems))
    {
        my $HeaderName = $CompleteSignature{1}{$Interface}{"Header"};
        my $DyLib = $Symbol_Library{1}{$Interface};
        $HeaderLibFunc{$HeaderName}{$DyLib}{$Interface} = 1;
    }
    my $Changed_Number = 0;
    foreach my $HeaderName (sort {lc($a) cmp lc($b)} keys(%HeaderLibFunc))
    {
        foreach my $DyLib (sort {lc($a) cmp lc($b)} keys(%{$HeaderLibFunc{$HeaderName}}))
        {
            my $FDyLib=$DyLib.($DyLib!~/\.\w+\Z/?" (.$LIB_EXT)":"");
            if($HeaderName) {
                $CHANGED_IMPLEMENTATION .= "<span class='h_name'>$HeaderName</span>, <span class='lib_name'>$FDyLib</span><br/>\n";
            }
            else {
                $CHANGED_IMPLEMENTATION .= "<span class='lib_name'>$DyLib</span><br/>\n";
            }
            my %NameSpace_Interface = ();
            foreach my $Interface (keys(%{$HeaderLibFunc{$HeaderName}{$DyLib}})) {
                $NameSpace_Interface{get_IntNameSpace($Interface, 2)}{$Interface} = 1;
            }
            foreach my $NameSpace (sort keys(%NameSpace_Interface))
            {
                $CHANGED_IMPLEMENTATION .= ($NameSpace)?"<span class='ns_title'>namespace</span> <span class='ns'>$NameSpace</span>"."<br/>\n":"";
                my @SortedInterfaces = sort {lc(get_Signature($a, 1)) cmp lc(get_Signature($b, 1))} keys(%{$NameSpace_Interface{$NameSpace}});
                foreach my $Interface (@SortedInterfaces)
                {
                    $Changed_Number += 1;
                    my $Signature = get_Signature($Interface, 1);
                    if($NameSpace) {
                        $Signature=~s/(\W|\A)\Q$NameSpace\E\:\:(\w)/$1$2/g;
                    }
                    $CHANGED_IMPLEMENTATION .= insertIDs($ContentSpanStart.highLight_Signature_Italic_Color($Signature).$ContentSpanEnd."<br/>\n".$ContentDivStart."<span class='mangled'>[ symbol: <b>$Interface</b> ]</span>".$ImplProblems{$Interface}{"Diff"}."<br/><br/>".$ContentDivEnd."\n");
                }
            }
            $CHANGED_IMPLEMENTATION .= "<br/>\n";
        }
    }
    if($CHANGED_IMPLEMENTATION) {
        $CHANGED_IMPLEMENTATION = "<a name='Changed_Implementation'></a><h2>Problems with Implementation ($Changed_Number)</h2><hr/>\n".$CHANGED_IMPLEMENTATION."<a style='font-size:11px;' href='#Top'>to the top</a><br/>\n";
    }
    return $CHANGED_IMPLEMENTATION;
}

sub get_Report_Added($)
{
    my $Level = $_[0]; # API or ABI
    
    my ($ADDED_INTERFACES, %FuncAddedInHeaderLib);
    foreach my $Interface (sort keys(%CompatProblems))
    {
        foreach my $Kind (sort keys(%{$CompatProblems{$Interface}}))
        {
            if($Kind eq "Added_Interface")
            {
                my $HeaderName = $CompleteSignature{2}{$Interface}{"Header"};
                my $DyLib = $Symbol_Library{2}{$Interface};
                $FuncAddedInHeaderLib{$HeaderName}{$DyLib}{$Interface} = 1;
            }
        }
    }
    if($ReportFormat eq "xml")
    { # XML
        foreach my $HeaderName (sort {lc($a) cmp lc($b)} keys(%FuncAddedInHeaderLib))
        {
            $ADDED_INTERFACES .= "  <header name=\"$HeaderName\">\n";
            foreach my $DyLib (sort {lc($a) cmp lc($b)} keys(%{$FuncAddedInHeaderLib{$HeaderName}}))
            {
                $ADDED_INTERFACES .= "    <library name=\"$DyLib\">\n";
                foreach my $Interface (keys(%{$FuncAddedInHeaderLib{$HeaderName}{$DyLib}})) {
                    $ADDED_INTERFACES .= "      <name>$Interface</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(%FuncAddedInHeaderLib))
        {
            foreach my $DyLib (sort {lc($a) cmp lc($b)} keys(%{$FuncAddedInHeaderLib{$HeaderName}}))
            {
                my $FDyLib=$DyLib.($DyLib!~/\.\w+\Z/?" (.$LIB_EXT)":"");
                if($HeaderName and $DyLib) {
                    $ADDED_INTERFACES .= "<span class='h_name'>$HeaderName</span>, <span class='lib_name'>$FDyLib</span><br/>\n";
                }
                elsif($DyLib) {
                    $ADDED_INTERFACES .= "<span class='lib_name'>$FDyLib</span><br/>\n";
                }
                elsif($HeaderName) {
                    $ADDED_INTERFACES .= "<span class='h_name'>$HeaderName</span><br/>\n";
                }
                my %NameSpace_Interface = ();
                foreach my $Interface (keys(%{$FuncAddedInHeaderLib{$HeaderName}{$DyLib}})) {
                    $NameSpace_Interface{get_IntNameSpace($Interface, 2)}{$Interface} = 1;
                }
                foreach my $NameSpace (sort keys(%NameSpace_Interface))
                {
                    $ADDED_INTERFACES .= ($NameSpace)?"<span class='ns_title'>namespace</span> <span class='ns'>$NameSpace</span>"."<br/>\n":"";
                    my @SortedInterfaces = sort {lc(get_Signature($a, 2)) cmp lc(get_Signature($b, 2))} keys(%{$NameSpace_Interface{$NameSpace}});
                    foreach my $Interface (@SortedInterfaces)
                    {
                        $Added_Number += 1;
                        my $SubReport = "";
                        my $Signature = get_Signature($Interface, 2);
                        if($NameSpace) {
                            $Signature=~s/(\W|\A)\Q$NameSpace\E\:\:(\w)/$1$2/g;
                        }
                        if($Interface=~/\A(_Z|\?)/) {
                            if($Signature) {
                                $SubReport = insertIDs($ContentSpanStart.highLight_Signature_Italic_Color($Signature).$ContentSpanEnd."<br/>\n".$ContentDivStart."<span class='mangled'>[ symbol: <b>$Interface</b> ]</span><br/><br/>".$ContentDivEnd."\n");
                            }
                            else {
                                $SubReport = "<span class=\"iname\">".$Interface."</span><br/>\n";
                            }
                        }
                        else {
                            if($Signature) {
                                $SubReport = "<span class=\"iname\">".highLight_Signature_Italic_Color($Signature)."</span><br/>\n";
                            }
                            else {
                                $SubReport = "<span class=\"iname\">".$Interface."</span><br/>\n";
                            }
                        }
                        $ADDED_INTERFACES .= $SubReport;
                    }
                }
                $ADDED_INTERFACES .= "<br/>\n";
            }
        }
        if($ADDED_INTERFACES) {
            $ADDED_INTERFACES = "<a name='Added'></a><h2>Added Symbols ($Added_Number)</h2><hr/>\n".$ADDED_INTERFACES."<a style='font-size:11px;' href='#Top'>to the top</a><br/>\n";
        }
    }
    return $ADDED_INTERFACES;
}

sub get_Report_Removed($)
{
    my $Level = $_[0];# API or ABI
    my (%FuncRemovedFromHeaderLib, $REMOVED_INTERFACES) = ();
    foreach my $Interface (sort keys(%CompatProblems))
    {
        foreach my $Kind (sort keys(%{$CompatProblems{$Interface}}))
        {
            if($Kind eq "Removed_Interface")
            {
                my $HeaderName = $CompleteSignature{1}{$Interface}{"Header"};
                my $DyLib = $Symbol_Library{1}{$Interface};
                $FuncRemovedFromHeaderLib{$HeaderName}{$DyLib}{$Interface} = 1;
            }
        }
    }
    if($ReportFormat eq "xml")
    { # XML
        foreach my $HeaderName (sort {lc($a) cmp lc($b)} keys(%FuncRemovedFromHeaderLib))
        {
            $REMOVED_INTERFACES .= "  <header name=\"$HeaderName\">\n";
            foreach my $DyLib (sort {lc($a) cmp lc($b)} keys(%{$FuncRemovedFromHeaderLib{$HeaderName}}))
            {
                $REMOVED_INTERFACES .= "    <library name=\"$DyLib\">\n";
                foreach my $Interface (keys(%{$FuncRemovedFromHeaderLib{$HeaderName}{$DyLib}})) {
                    $REMOVED_INTERFACES .= "      <name>$Interface</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(%FuncRemovedFromHeaderLib))
        {
            foreach my $DyLib (sort {lc($a) cmp lc($b)} keys(%{$FuncRemovedFromHeaderLib{$HeaderName}}))
            {
                my $FDyLib=$DyLib.($DyLib!~/\.\w+\Z/?" (.$LIB_EXT)":"");
                if($HeaderName and $DyLib) {
                    $REMOVED_INTERFACES .= "<span class='h_name'>$HeaderName</span>, <span class='lib_name'>$FDyLib</span><br/>\n";
                }
                elsif($DyLib) {
                    $REMOVED_INTERFACES .= "<span class='lib_name'>$FDyLib</span><br/>\n";
                }
                elsif($HeaderName) {
                    $REMOVED_INTERFACES .= "<span class='h_name'>$HeaderName</span><br/>\n";
                }
                my %NameSpace_Interface = ();
                foreach my $Interface (keys(%{$FuncRemovedFromHeaderLib{$HeaderName}{$DyLib}}))
                {
                    $NameSpace_Interface{get_IntNameSpace($Interface, 1)}{$Interface} = 1;
                }
                foreach my $NameSpace (sort keys(%NameSpace_Interface))
                {
                    $REMOVED_INTERFACES .= ($NameSpace)?"<span class='ns_title'>namespace</span> <span class='ns'>$NameSpace</span>"."<br/>\n":"";
                    my @SortedInterfaces = sort {lc(get_Signature($a, 1)) cmp lc(get_Signature($b, 1))} keys(%{$NameSpace_Interface{$NameSpace}});
                    foreach my $Interface (@SortedInterfaces)
                    {
                        $Removed_Number += 1;
                        my $SubReport = "";
                        my $Signature = get_Signature($Interface, 1);
                        if($NameSpace) {
                            $Signature=~s/(\W|\A)\Q$NameSpace\E\:\:(\w)/$1$2/g;
                        }
                        if($Interface=~/\A(_Z|\?)/) {
                            if($Signature) {
                                $SubReport = insertIDs($ContentSpanStart.highLight_Signature_Italic_Color($Signature).$ContentSpanEnd."<br/>\n".$ContentDivStart."<span class='mangled'>[ symbol: <b>$Interface</b> ]</span><br/><br/>".$ContentDivEnd."\n");
                            }
                            else {
                                $SubReport = "<span class=\"iname\">".$Interface."</span><br/>\n";
                            }
                        }
                        else {
                            if($Signature) {
                                $SubReport = "<span class=\"iname\">".highLight_Signature_Italic_Color($Signature)."</span><br/>\n";
                            }
                            else {
                                $SubReport = "<span class=\"iname\">".$Interface."</span><br/>\n";
                            }
                        }
                        $REMOVED_INTERFACES .= $SubReport;
                    }
                }
                $REMOVED_INTERFACES .= "<br/>\n";
            }
        }
        if($REMOVED_INTERFACES) {
            $REMOVED_INTERFACES = "<a name='Removed'></a><a name='Withdrawn'></a><h2>Removed Symbols ($Removed_Number)</h2><hr/>\n".$REMOVED_INTERFACES."<a style='font-size:11px;' href='#Top'>to the top</a><br/>\n";
        }
    }
    return $REMOVED_INTERFACES;
}

sub getXmlParams($$)
{
    my ($Content, $Problem) = @_;
    return "" if(not $Content or not $Problem);
    my %XMLparams = ();
    foreach my $Attr (sort {$b cmp $a} keys(%{$Problem}))
    {
        my $Macro = "\@".lc($Attr);
        if($Content=~/\Q$Macro\E/) {
            $XMLparams{lc($Attr)} = $Problem->{$Attr};
        }
    }
    my @PString = ();
    foreach my $P (sort {$b cmp $a} keys(%XMLparams)) {
        push(@PString, $P."=\"".htmlSpecChars($XMLparams{$P})."\"");
    }
    if(@PString) {
        return " ".join(" ", @PString);
    }
    else {
        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!([2-9]\))!<br/>$1!g; # 1), 2), ...
    if($Content=~/\ANOTE:/)
    { # notes
        $Content=~s!(NOTE):!<b>$1</b>:!g;
    }
    else {
        $Content=~s!(NOTE):!<br/><b>$1</b>:!g;
    }
    $Content=~s! (out)-! <b>$1</b>-!g; # out-parameters
    my @Keywords = (
        "void",
        "const",
        "static",
        "restrict",
        "volatile",
        "register",
        "virtual",
        "virtually"
    );
    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
    return $Content;
}

sub applyMacroses($$$$)
{
    my ($Level, $Kind, $Content, $Problem) = @_;
    return "" if(not $Content or not $Problem);
    $Problem->{"Word_Size"} = $WORD_SIZE{2};
    $Content = addMarkup($Content);
    # macros
    foreach my $Attr (sort {$b cmp $a} keys(%{$Problem}))
    {
        my $Macro = "\@".lc($Attr);
        my $Value = $Problem->{$Attr};
        if($Value=~/\s\(/)
        { # functions
            $Value = black_name($Value);
        }
        elsif($Value=~/\s/) {
            $Value = "<span class='value'>".htmlSpecChars($Value)."</span>";
        }
        elsif($Value=~/\A\d+\Z/
        and ($Attr eq "Old_Size" or $Attr eq "New_Size"))
        { # bits to bytes
            if($Value % $BYTE_SIZE)
            { # bits
                if($Value==1) {
                    $Value = "<b>".$Value."</b> bit";
                }
                else {
                    $Value = "<b>".$Value."</b> bits";
                }
            }
            else
            { # bytes
                $Value /= $BYTE_SIZE;
                if($Value==1) {
                    $Value = "<b>".$Value."</b> byte";
                }
                else {
                    $Value = "<b>".$Value."</b> bytes";
                }
            }
        }
        else {
            $Value = "<b>".htmlSpecChars($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;
        }
    }
    $Content=~s!<nowrap>(.+?)</nowrap>!<span class='nowrap'>$1</span>!g;
    return $Content;
}

sub get_Report_InterfaceProblems($$)
{
    my ($TargetPriority, $Level) = @_;
    my ($INTERFACE_PROBLEMS, %FuncHeaderLib, %SymbolChanges);
    foreach my $Interface (sort keys(%CompatProblems))
    {
        next if($Interface=~/\A([^\@\$\?]+)[\@\$]+/ and defined $CompatProblems{$1});
        foreach my $Kind (sort keys(%{$CompatProblems{$Interface}}))
        {
            if($CompatRules{$Level}{$Kind}{"Kind"} eq "Symbols"
            and $Kind ne "Added_Interface" and $Kind ne "Removed_Interface")
            {
                my $HeaderName = $CompleteSignature{1}{$Interface}{"Header"};
                my $DyLib = $Symbol_Library{1}{$Interface};
                if(not $DyLib and my $VSym = $SymVer{1}{$Interface})
                { # Symbol with Version
                    $DyLib = $Symbol_Library{1}{$VSym};
                }
                $FuncHeaderLib{$HeaderName}{$DyLib}{$Interface} = 1;
                %{$SymbolChanges{$Interface}{$Kind}} = %{$CompatProblems{$Interface}{$Kind}};
                foreach my $Location (sort keys(%{$SymbolChanges{$Interface}{$Kind}}))
                {
                    my $Priority = getProblemSeverity($Level, $Kind);
                    if($Priority ne $TargetPriority) {
                        delete($SymbolChanges{$Interface}{$Kind}{$Location});
                    }
                }
                if(not keys(%{$SymbolChanges{$Interface}{$Kind}})) {
                    delete($SymbolChanges{$Interface}{$Kind});
                }
                if(not keys(%{$SymbolChanges{$Interface}})) {
                    delete($SymbolChanges{$Interface});
                }
            }
        }
    }
    if($ReportFormat eq "xml")
    { # XML
        foreach my $HeaderName (sort {lc($a) cmp lc($b)} keys(%FuncHeaderLib))
        {
            $INTERFACE_PROBLEMS .= "  <header name=\"$HeaderName\">\n";
            foreach my $DyLib (sort {lc($a) cmp lc($b)} keys(%{$FuncHeaderLib{$HeaderName}}))
            {
                $INTERFACE_PROBLEMS .= "    <library name=\"$DyLib\">\n";
                foreach my $Symbol (sort {lc($tr_name{$a}) cmp lc($tr_name{$b})} keys(%SymbolChanges))
                {
                    $INTERFACE_PROBLEMS .= "      <symbol name=\"$Symbol\">\n";
                    foreach my $Kind (keys(%{$SymbolChanges{$Symbol}}))
                    {
                        foreach my $Location (sort keys(%{$SymbolChanges{$Symbol}{$Kind}}))
                        {
                            my %Problem = %{$SymbolChanges{$Symbol}{$Kind}{$Location}};
                            $Problem{"Param_Pos"} = numToStr($Problem{"Param_Pos"} + 1);
                            $INTERFACE_PROBLEMS .= "        <problem id=\"$Kind\">\n";
                            my $Change = $CompatRules{$Level}{$Kind}{"Change"};
                            $INTERFACE_PROBLEMS .= "          <change".getXmlParams($Change, \%Problem).">$Change</change>\n";
                            my $Effect = $CompatRules{$Level}{$Kind}{"Effect"};
                            $INTERFACE_PROBLEMS .= "          <effect".getXmlParams($Effect, \%Problem).">$Effect</effect>\n";
                            my $Overcome = $CompatRules{$Level}{$Kind}{"Overcome"};
                            $INTERFACE_PROBLEMS .= "          <overcome".getXmlParams($Overcome, \%Problem).">$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=\"$TargetPriority\">\n".$INTERFACE_PROBLEMS."</problems_with_symbols>\n\n";
    }
    else
    { # HTML
        my $ProblemsNum = 0;
        foreach my $HeaderName (sort {lc($a) cmp lc($b)} keys(%FuncHeaderLib))
        {
            foreach my $DyLib (sort {lc($a) cmp lc($b)} keys(%{$FuncHeaderLib{$HeaderName}}))
            {
                my ($HEADER_LIB_REPORT, %NameSpace_Interface, %NewSignature) = ();
                foreach my $Interface (keys(%{$FuncHeaderLib{$HeaderName}{$DyLib}})) {
                    $NameSpace_Interface{get_IntNameSpace($Interface, 1)}{$Interface} = 1;
                }
                foreach my $NameSpace (sort keys(%NameSpace_Interface))
                {
                    my $NS_REPORT = "";
                    my @SortedInterfaces = sort {lc($tr_name{$a}) cmp lc($tr_name{$b})} keys(%{$NameSpace_Interface{$NameSpace}});
                    foreach my $Interface (@SortedInterfaces)
                    {
                        my $Signature = get_Signature($Interface, 1);
                        my $InterfaceProblemsReport = "";
                        my $ProblemNum = 1;
                        foreach my $Kind (keys(%{$SymbolChanges{$Interface}}))
                        {
                            foreach my $Location (sort keys(%{$SymbolChanges{$Interface}{$Kind}}))
                            {
                                my %Problem = %{$SymbolChanges{$Interface}{$Kind}{$Location}};
                                $Problem{"Param_Pos"} = numToStr($Problem{"Param_Pos"} + 1);
                                if($Problem{"New_Signature"}) {
                                    $NewSignature{$Interface} = $Problem{"New_Signature"};
                                }
                                if(my $Change = applyMacroses($Level, $Kind, $CompatRules{$Level}{$Kind}{"Change"}, \%Problem))
                                {
                                    my $Effect = applyMacroses($Level, $Kind, $CompatRules{$Level}{$Kind}{"Effect"}, \%Problem);
                                    $InterfaceProblemsReport .= "<tr><th>$ProblemNum</th><td align='left' valign='top'>".$Change."</td><td align='left' valign='top'>".$Effect."</td></tr>\n";
                                    $ProblemNum += 1;
                                    $ProblemsNum += 1;
                                }
                            }
                        }
                        $ProblemNum -= 1;
                        if($InterfaceProblemsReport)
                        {
                            if($Signature) {
                                $NS_REPORT .= $ContentSpanStart."<span class='extendable'>[+]</span> ".highLight_Signature_Italic_Color($Signature)." ($ProblemNum)".$ContentSpanEnd."<br/>\n$ContentDivStart\n";
                                if($Interface=~/\A(_Z|\?)/
                                and not $NewSignature{$Interface}) {
                                    $NS_REPORT .= "<span class='mangled'>&#160;&#160;[ symbol: <b>$Interface</b> ]</span><br/>\n";
                                }
                            }
                            else {
                                $NS_REPORT .= $ContentSpanStart."<span class='extendable'>[+]</span> ".$Interface." ($ProblemNum)".$ContentSpanEnd."<br/>\n$ContentDivStart\n";
                            }
                            if($NewSignature{$Interface}) {# argument list changed to
                                $NS_REPORT .= "\n<span class='new_signature_label'>changed to:</span><br/><span class='new_signature'>".highLight_Signature_Italic_Color($NewSignature{$Interface})."</span>\n";
                            }
                            $NS_REPORT .= "<table cellpadding='3' cellspacing='0' class='problems_table'><tr><th width='2%'></th><th width='47%'>Change</th><th>Effect</th></tr>$InterfaceProblemsReport</table><br/>$ContentDivEnd\n";
                            $NS_REPORT = insertIDs($NS_REPORT);
                            if($NameSpace) {
                                $NS_REPORT=~s/(\W|\A)\Q$NameSpace\E\:\:(\w)/$1$2/g;
                            }
                        }
                    }
                    if($NS_REPORT) {
                        $HEADER_LIB_REPORT .= (($NameSpace)?"<span class='ns_title'>namespace</span> <span class='ns'>$NameSpace</span>"."<br/>\n":"").$NS_REPORT;
                    }
                }
                if($HEADER_LIB_REPORT)
                {
                    my $FDyLib=$DyLib.($DyLib!~/\.\w+\Z/?" (.$LIB_EXT)":"");
                    if($HeaderName and $DyLib) {
                        $INTERFACE_PROBLEMS .= "<span class='h_name'>$HeaderName</span>, <span class='lib_name'>$FDyLib</span><br/>\n".$HEADER_LIB_REPORT."<br/>";
                    }
                    elsif($HeaderName) {
                        $INTERFACE_PROBLEMS .= "<span class='h_name'>$HeaderName</span><br/>\n".$HEADER_LIB_REPORT."<br/>";
                    }
                    elsif($DyLib) {
                        $INTERFACE_PROBLEMS .= "<span class='lib_name'>$FDyLib</span><br/>\n".$HEADER_LIB_REPORT."<br/>";
                    }
                    else {
                        $INTERFACE_PROBLEMS .= $HEADER_LIB_REPORT."<br/>";
                    }
                }
            }
        }
        if($INTERFACE_PROBLEMS)
        {
            my $Title = "Problems with Symbols, $TargetPriority Severity";
            my $Anchor = "Interface_Problems_$TargetPriority";
            if($TargetPriority eq "Safe")
            { # Safe Changes
                $Title = "Other Changes in Symbols";
                $Anchor = "Other_Changes_In_Symbols";
            }
            $INTERFACE_PROBLEMS = "<a name=\'$Anchor\'></a>\n<h2>$Title ($ProblemsNum)</h2><hr/>\n".$INTERFACE_PROBLEMS."<a style='font-size:11px;' href='#Top'>to the top</a><br/>\n";
        }
    }
    return $INTERFACE_PROBLEMS;
}

sub get_Report_TypeProblems($$)
{
    my ($TargetPriority, $Level) = @_;
    my ($TYPE_PROBLEMS, %TypeHeader, %TypeChanges, %TypeType) = ();
    foreach my $Interface (sort keys(%CompatProblems))
    {
        foreach my $Kind (keys(%{$CompatProblems{$Interface}}))
        {
            if($CompatRules{$Level}{$Kind}{"Kind"} eq "Types")
            {
                foreach my $Location (sort {cmp_locations($b, $a)} sort keys(%{$CompatProblems{$Interface}{$Kind}}))
                {
                    my $TypeName = $CompatProblems{$Interface}{$Kind}{$Location}{"Type_Name"};
                    my $Target = $CompatProblems{$Interface}{$Kind}{$Location}{"Target"};
                    my $Priority = getProblemSeverity($Level, $Kind);
                    if($Priority eq "Safe"
                    and $TargetPriority ne "Safe") {
                        next;
                    }
                    if(not $TypeType{$TypeName}
                    or $TypeType{$TypeName} eq "Struct")
                    { # register type of the type, select "class" if type has "class"- and "struct"-type changes
                        $TypeType{$TypeName} = $CompatProblems{$Interface}{$Kind}{$Location}{"Type_Type"};
                    }
                    if(cmp_priority($Type_MaxPriority{$Level}{$TypeName}{$Kind}{$Target}, $Priority))
                    { # select a problem with the highest priority
                        next;
                    }
                    %{$TypeChanges{$TypeName}{$Kind}{$Location}} = %{$CompatProblems{$Interface}{$Kind}{$Location}};
                }
            }
        }
    }
    my %Kinds_Locations = ();
    foreach my $TypeName (keys(%TypeChanges))
    {
        my %Kinds_Target = ();
        foreach my $Kind (sort keys(%{$TypeChanges{$TypeName}}))
        {
            foreach my $Location (sort {cmp_locations($b, $a)} sort keys(%{$TypeChanges{$TypeName}{$Kind}}))
            {
                my $Priority = getProblemSeverity($Level, $Kind);
                if($Priority ne $TargetPriority)
                { # other priority
                    delete($TypeChanges{$TypeName}{$Kind}{$Location});
                    next;
                }
                $Kinds_Locations{$TypeName}{$Kind}{$Location} = 1;
                my $Target = $TypeChanges{$TypeName}{$Kind}{$Location}{"Target"};
                if($Kinds_Target{$Kind}{$Target})
                { # duplicate target
                    delete($TypeChanges{$TypeName}{$Kind}{$Location});
                    next;
                }
                $Kinds_Target{$Kind}{$Target} = 1;
                my $HeaderName = get_TypeAttr($TName_Tid{1}{$TypeName}, 1, "Header");
                $TypeHeader{$HeaderName}{$TypeName} = 1;
            }
            if(not keys(%{$TypeChanges{$TypeName}{$Kind}})) {
                delete($TypeChanges{$TypeName}{$Kind});
            }
        }
    }
    if($ReportFormat eq "xml")
    { # XML
        foreach my $HeaderName (sort {lc($a) cmp lc($b)} keys(%TypeHeader))
        {
            $TYPE_PROBLEMS .= "  <header name=\"$HeaderName\">\n";
            foreach my $TypeName (keys(%{$TypeHeader{$HeaderName}}))
            {
                $TYPE_PROBLEMS .= "    <type name=\"".htmlSpecChars($TypeName)."\">\n";
                foreach my $Kind (sort {$b=~/Size/ <=> $a=~/Size/} sort keys(%{$TypeChanges{$TypeName}}))
                {
                    foreach my $Location (sort {cmp_locations($b, $a)} sort keys(%{$TypeChanges{$TypeName}{$Kind}}))
                    {
                        my %Problem = %{$TypeChanges{$TypeName}{$Kind}{$Location}};
                        $TYPE_PROBLEMS .= "      <problem id=\"$Kind\">\n";
                        my $Change = $CompatRules{$Level}{$Kind}{"Change"};
                        $TYPE_PROBLEMS .= "        <change".getXmlParams($Change, \%Problem).">$Change</change>\n";
                        my $Effect = $CompatRules{$Level}{$Kind}{"Effect"};
                        $TYPE_PROBLEMS .= "        <effect".getXmlParams($Effect, \%Problem).">$Effect</effect>\n";
                        my $Overcome = $CompatRules{$Level}{$Kind}{"Overcome"};
                        $TYPE_PROBLEMS .= "        <overcome".getXmlParams($Overcome, \%Problem).">$Overcome</overcome>\n";
                        $TYPE_PROBLEMS .= "      </problem>\n";
                    }
                }
                $TYPE_PROBLEMS .= getAffectedInterfaces($Level, $TypeName, $Kinds_Locations{$TypeName});
                if(grep {$_=~/Virtual|Base_Class/} keys(%{$Kinds_Locations{$TypeName}})) {
                    $TYPE_PROBLEMS .= showVTables($TypeName);
                }
                $TYPE_PROBLEMS .= "    </type>\n";
            }
            $TYPE_PROBLEMS .= "  </header>\n";
        }
        $TYPE_PROBLEMS = "<problems_with_types severity=\"$TargetPriority\">\n".$TYPE_PROBLEMS."</problems_with_types>\n\n";
    }
    else
    { # HTML
        my $ProblemsNum = 0;
        foreach my $HeaderName (sort {lc($a) cmp lc($b)} keys(%TypeHeader))
        {
            my ($HEADER_REPORT, %NameSpace_Type) = ();
            foreach my $TypeName (keys(%{$TypeHeader{$HeaderName}})) {
                $NameSpace_Type{parse_TypeNameSpace($TypeName, 1)}{$TypeName} = 1;
            }
            foreach my $NameSpace (sort keys(%NameSpace_Type))
            {
                my $NS_REPORT = "";
                my @SortedTypes = sort {lc($TypeType{$a}." ".$a) cmp lc($TypeType{$b}." ".$b)} keys(%{$NameSpace_Type{$NameSpace}});
                foreach my $TypeName (@SortedTypes)
                {
                    my $ProblemNum = 1;
                    my $TYPE_REPORT = "";
                    foreach my $Kind (sort {$b=~/Size/ <=> $a=~/Size/} sort keys(%{$TypeChanges{$TypeName}}))
                    {
                        foreach my $Location (sort {cmp_locations($b, $a)} sort keys(%{$TypeChanges{$TypeName}{$Kind}}))
                        {
                            my %Problem = %{$TypeChanges{$TypeName}{$Kind}{$Location}};
                            if(my $Change = applyMacroses($Level, $Kind, $CompatRules{$Level}{$Kind}{"Change"}, \%Problem))
                            {
                                my $Effect = applyMacroses($Level, $Kind, $CompatRules{$Level}{$Kind}{"Effect"}, \%Problem);
                                $TYPE_REPORT .= "<tr><th>$ProblemNum</th><td align='left' valign='top'>".$Change."</td><td align='left' valign='top'>$Effect</td></tr>\n";
                                $ProblemNum += 1;
                                $ProblemsNum += 1;
                            }
                        }
                    }
                    $ProblemNum -= 1;
                    if($TYPE_REPORT)
                    {
                        my $Affected = getAffectedInterfaces($Level, $TypeName, $Kinds_Locations{$TypeName});
                        my $ShowVTables = "";
                        if(grep {$_=~/Virtual|Base_Class/} keys(%{$Kinds_Locations{$TypeName}})) {
                            $ShowVTables = showVTables($TypeName);
                        }
                        $NS_REPORT .= $ContentSpanStart."<span class='extendable'>[+]</span> <span class='ttype'>".lc($TypeType{$TypeName})."</span> ".htmlSpecChars($TypeName)." ($ProblemNum)".$ContentSpanEnd;
                        $NS_REPORT .= "<br/>\n".$ContentDivStart."<table cellpadding='3' cellspacing='0' class='problems_table'><tr>\n";
                        $NS_REPORT .= "<th width='2%'></th><th width='47%'>Change</th>\n";
                        $NS_REPORT .= "<th>Effect</th></tr>".$TYPE_REPORT."</table>\n";
                        $NS_REPORT .= $ShowVTables.$Affected."<br/><br/>".$ContentDivEnd."\n";
                        $NS_REPORT = insertIDs($NS_REPORT);
                        if($NameSpace) {
                            $NS_REPORT=~s/(\W|\A)\Q$NameSpace\E\:\:(\w|\~)/$1$2/g;
                        }
                    }
                }
                if($NS_REPORT) {
                    $HEADER_REPORT .= (($NameSpace)?"<span class='ns_title'>namespace</span> <span class='ns'>$NameSpace</span>"."<br/>\n":"").$NS_REPORT;
                }
            }
            if($HEADER_REPORT) {
                $TYPE_PROBLEMS .= "<span class='h_name'>$HeaderName</span><br/>\n".$HEADER_REPORT."<br/>";
            }
        }
        if($TYPE_PROBLEMS)
        {
            my $Title = "Problems with Data Types, $TargetPriority Severity";
            my $Anchor = "Type_Problems_$TargetPriority";
            if($TargetPriority eq "Safe")
            { # Safe Changes
                $Title = "Other Changes in Data Types";
                $Anchor = "Other_Changes_In_Types";
            }
            $TYPE_PROBLEMS = "<a name=\'$Anchor\'></a>\n<h2>$Title ($ProblemsNum)</h2><hr/>\n".$TYPE_PROBLEMS."<a style='font-size:11px;' href='#Top'>to the top</a><br/>\n";
        }
    }
    return $TYPE_PROBLEMS;
}

sub showVTables($)
{
    my $TypeName = $_[0];
    my $TypeId1 = $TName_Tid{1}{$TypeName};
    my %Type1 = get_Type($Tid_TDid{1}{$TypeId1}, $TypeId1, 1);
    if(defined $Type1{"VTable"}
    and keys(%{$Type1{"VTable"}}))
    {
        my $TypeId2 = $TName_Tid{2}{$TypeName};
        my %Type2 = get_Type($Tid_TDid{2}{$TypeId2}, $TypeId2, 2);
        if(defined $Type2{"VTable"}
        and keys(%{$Type2{"VTable"}}))
        {
            my %Indexes = map {$_=>1} (keys(%{$Type1{"VTable"}}), keys(%{$Type2{"VTable"}}));
            my %Entries = ();
            foreach (sort {int($a)<=>int($b)} (keys(%Indexes)))
            {
                $Entries{$_}{"E1"} = simpleVEntry($Type1{"VTable"}{$_});
                $Entries{$_}{"E2"} = simpleVEntry($Type2{"VTable"}{$_});
            }
            my $VTABLES = "";
            if($ReportFormat eq "xml")
            { # XML
                $VTABLES .= "      <vtable>\n";
                foreach (sort {int($a)<=>int($b)} (keys(%Entries)))
                {
                    $VTABLES .= "        <entry offset=\"$_\">\n";
                    $VTABLES .= "          <old>".htmlSpecChars($Entries{$_}{"E1"})."</old>\n";
                    $VTABLES .= "          <new>".htmlSpecChars($Entries{$_}{"E2"})."</new>\n";
                    $VTABLES .= "        </entry>\n";
                }
                $VTABLES .= "      </vtable>\n\n";
            }
            else
            { # HTML
                $VTABLES .= "<table cellpadding='3' cellspacing='0' class='virtual_table'>";
                $VTABLES .= "<tr><th width='2%'>Offset</th>";
                $VTABLES .= "<th width='45%'>Virtual Table (Old) - ".(keys(%{$Type1{"VTable"}}))." entries</th>";
                $VTABLES .= "<th>Virtual Table (New) - ".(keys(%{$Type2{"VTable"}}))." entries</th></tr>";
                foreach (sort {int($a)<=>int($b)} (keys(%Entries)))
                {
                    my ($Color1, $Color2) = ("", "");
                    if($Entries{$_}{"E1"} ne $Entries{$_}{"E2"})
                    {
                        if($Entries{$_}{"E1"})
                        {
                            $Color1 = " class='vtable_red'";
                            $Color2 = " class='vtable_red'";
                        }
                        else {
                            $Color2 = " class='vtable_yellow'";
                        }
                    }
                    $VTABLES .= "<tr><th>$_</th>\n";
                    $VTABLES .= "<td$Color1>".htmlSpecChars($Entries{$_}{"E1"})."</td>\n";
                    $VTABLES .= "<td$Color2>".htmlSpecChars($Entries{$_}{"E2"})."</td></tr>\n";
                }
                $VTABLES .= "</table><br/>\n";
                $VTABLES = $ContentDivStart.$VTABLES.$ContentDivEnd;
                $VTABLES = "<span style='padding-left:15px;'>".$ContentSpanStart_Info."[+] show v-table (old and new)".$ContentSpanEnd."</span><br/>\n".$VTABLES;
            }
            return $VTABLES;
        }
    }
    return "";
}

sub simpleVEntry($)
{
    my $VEntry = $_[0];
    $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 \(\*\)\(...\)\)([^\(\d])/$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/;
    # templates
    if($VEntry=~s/ \[with (\w+) = (.+?)(, [^=]+ = .+|])\Z//g)
    { # std::basic_streambuf<_CharT, _Traits>::imbue [with _CharT = char, _Traits = std::char_traits<char>]
      # become std::basic_streambuf<char, ...>::imbue
        my ($Pname, $Pval) = ($1, $2);
        if($Pname eq "_CharT" and $VEntry=~/\Astd::/)
        { # stdc++ typedefs
            $VEntry=~s/<$Pname(, [^<>]+|)>/<$Pval>/g;
            # FIXME: simplify names using stdcxx typedefs (StdCxxTypedef)
            # The typedef info should be added to ABI dumps
        }
        else
        {
            $VEntry=~s/<$Pname>/<$Pval>/g;
            $VEntry=~s/<$Pname, [^<>]+>/<$Pval, ...>/g;
        }
    }
    $VEntry=~s/([^:]+)::\~([^:]+)\Z/~$1/; # destructors
    return $VEntry;
}

sub getAffectedInterfaces($$$)
{
    my ($Level, $Target_TypeName, $Kinds_Locations) = @_;
    my (%INumber, %IProblems) = ();
    my $LIMIT = 1000;
    foreach my $Interface (sort {lc($tr_name{$a}?$tr_name{$a}:$a) cmp lc($tr_name{$b}?$tr_name{$b}:$b)} keys(%CompatProblems))
    {
        last if(keys(%INumber)>$LIMIT);
        if(($Interface=~/C2E|D2E|D0E/))
        { # duplicated problems for C2 constructors, D2 and D0 destructors
            next;
        }
        my ($MinPath_Length, $ProblemLocation_Last) = ();
        my $MaxPriority = 0;
        my $Signature = get_Signature($Interface, 1);
        foreach my $Kind (keys(%{$CompatProblems{$Interface}}))
        {
            foreach my $Location (keys(%{$CompatProblems{$Interface}{$Kind}}))
            {
                if(not defined $Kinds_Locations->{$Kind}
                or not $Kinds_Locations->{$Kind}{$Location}) {
                    next;
                }
                if($Interface=~/\A([^\@\$\?]+)[\@\$]+/ and defined $CompatProblems{$1}
                and defined $CompatProblems{$1}{$Kind}{$Location})
                { # duplicated problems for versioned symbols
                    next;
                }
                my $Type_Name = $CompatProblems{$Interface}{$Kind}{$Location}{"Type_Name"};
                next if($Type_Name ne $Target_TypeName);
                
                my $Position = $CompatProblems{$Interface}{$Kind}{$Location}{"Param_Pos"};
                my $Param_Name = $CompatProblems{$Interface}{$Kind}{$Location}{"Param_Name"};
                my $Priority = $CompatProblems{$Interface}{$Kind}{$Location}{"Priority"};
                $INumber{$Interface} = 1;
                my $Path_Length = 0;
                my $ProblemLocation = $Location;
                if($Type_Name) {
                    $ProblemLocation=~s/->\Q$Type_Name\E\Z//g;
                }
                while($ProblemLocation=~/\-\>/g){$Path_Length += 1;}
                if($MinPath_Length eq "" or ($Path_Length<=$MinPath_Length and $Priority_Value{$Priority}>$MaxPriority)
                or (cmp_locations($ProblemLocation, $ProblemLocation_Last) and $Priority_Value{$Priority}==$MaxPriority))
                {
                    $MinPath_Length = $Path_Length;
                    $MaxPriority = $Priority_Value{$Priority};
                    $ProblemLocation_Last = $ProblemLocation;
                    %{$IProblems{$Interface}} = (
                        "Description"=>getAffectDescription($Level, $Interface, $Kind, $Location),
                        "Priority"=>$MaxPriority,
                        "Signature"=>$Signature,
                        "Position"=>$Position,
                        "Param_Name"=>$Param_Name,
                        "Location"=>$Location
                    );
                }
            }
        }
    }
    my @Interfaces = keys(%IProblems);
    @Interfaces = sort {lc($tr_name{$a}?$tr_name{$a}:$a) cmp lc($tr_name{$b}?$tr_name{$b}:$b)} @Interfaces;
    @Interfaces = sort {$IProblems{$b}{"Priority"}<=>$IProblems{$a}{"Priority"}} @Interfaces;
    my $Affected = "";
    if($ReportFormat eq "xml")
    { # XML
        $Affected .= "      <affected>\n";
        foreach my $Interface (@Interfaces)
        {
            my $Param_Name = $IProblems{$Interface}{"Param_Name"};
            my $Description = $IProblems{$Interface}{"Description"};
            my $Location = $IProblems{$Interface}{"Location"};
            my $Target = "";
            if($Param_Name) {
                $Target = " affected=\"param\" param_name=\"$Param_Name\"";
            }
            elsif($Location=~/\Aretval(\-|\Z)/i) {
                $Target = " affected=\"retval\"";
            }
            elsif($Location=~/\Athis(\-|\Z)/i) {
                $Target = " affected=\"this\"";
            }
            $Affected .= "        <symbol$Target name=\"$Interface\">\n";
            $Affected .= "          <comment>".htmlSpecChars($Description)."</comment>\n";
            $Affected .= "        </symbol>\n";
        }
        $Affected .= "      </affected>\n";
    }
    else
    { # HTML
        foreach my $Interface (@Interfaces)
        {
            my $Description = $IProblems{$Interface}{"Description"};
            my $Signature = $IProblems{$Interface}{"Signature"};
            my $Pos = $IProblems{$Interface}{"Position"};
            $Affected .= "<span class='iname_b'>".highLight_Signature_PPos_Italic($Signature, $Pos, 1, 0, 0)."</span><br/>"."<div class='affect'>".htmlSpecChars($Description)."</div>\n";
        }
        $Affected = "<div class='affected'>".$Affected."</div>";
        if(keys(%INumber)>$LIMIT) {
            $Affected .= "and others ...<br/>";
        }
        if($Affected)
        {
            $Affected = $ContentDivStart.$Affected.$ContentDivEnd;
            my $AHeader = $ContentSpanStart_Affected."[+] affected symbols (".(keys(%INumber)>$LIMIT?"more than $LIMIT":keys(%INumber)).")".$ContentSpanEnd;
            $AHeader = "<span style='padding-left:15px'>".$AHeader."</span>";
            $Affected = $AHeader.$Affected;
        }
    }
    return $Affected;
}

sub cmp_locations($$)
{
    my ($Location1, $Location2) = @_;
    if($Location2=~/(\A|\W)(retval|this)(\W|\Z)/
    and $Location1!~/(\A|\W)(retval|this)(\W|\Z)/ and $Location1!~/\-\>/) {
        return 1;
    }
    if($Location2=~/(\A|\W)(retval|this)(\W|\Z)/ and $Location2=~/\-\>/
    and $Location1!~/(\A|\W)(retval|this)(\W|\Z)/ and $Location1=~/\-\>/) {
        return 1;
    }
    return 0;
}

sub getAffectDescription($$$$)
{
    my ($Level, $Interface, $Kind, $Location) = @_;
    my %Problem = %{$CompatProblems{$Interface}{$Kind}{$Location}};
    my $PPos = numToStr($Problem{"Param_Pos"} + 1);
    my @Sentence = ();
    $Location=~s/\A(.*)\-\>.+?\Z/$1/;
    if($Kind eq "Overridden_Virtual_Method"
    or $Kind eq "Overridden_Virtual_Method_B") {
        push(@Sentence, "The method '".$Problem{"New_Value"}."' will be called instead of this method.");
    }
    elsif($CompatRules{$Level}{$Kind}{"Kind"} eq "Types")
    {
        if($Location eq "this" or $Kind=~/(\A|_)Virtual(_|\Z)/)
        {
            my $METHOD_TYPE = $CompleteSignature{1}{$Interface}{"Constructor"}?"constructor":"method";
            my $ClassName = get_TypeName($CompleteSignature{1}{$Interface}{"Class"}, 1);
            if($ClassName eq $Problem{"Type_Name"}) {
                push(@Sentence, "This $METHOD_TYPE is from \'".$Problem{"Type_Name"}."\' class.");
            }
            else {
                push(@Sentence, "This $METHOD_TYPE is from derived class \'".$ClassName."\'.");
            }
        }
        else
        {
            if($Location=~/retval/)
            { # return value
                if($Location=~/\-\>/) {
                    push(@Sentence, "Field \'".$Location."\' in return value");
                }
                else {
                    push(@Sentence, "Return value");
                }
                if($Problem{"InitialType_Type"} eq "Pointer") {
                    push(@Sentence, "(pointer)");
                }
                elsif($Problem{"InitialType_Type"} eq "Ref") {
                    push(@Sentence, "(reference)");
                }
            }
            elsif($Location=~/this/)
            { # "this" pointer
                if($Location=~/\-\>/) {
                    push(@Sentence, "Field \'".$Location."\' in the object of this method");
                }
                else {
                    push(@Sentence, "\'this\' pointer");
                }
            }
            else
            { # parameters
                if($Location=~/\-\>/) {
                    push(@Sentence, "Field \'".$Location."\' in $PPos parameter");
                }
                else {
                    push(@Sentence, "$PPos parameter");
                }
                if($Problem{"Param_Name"}) {
                    push(@Sentence, "\'".$Problem{"Param_Name"}."\'");
                }
                if($Problem{"InitialType_Type"} eq "Pointer") {
                    push(@Sentence, "(pointer)");
                }
                elsif($Problem{"InitialType_Type"} eq "Ref") {
                    push(@Sentence, "(reference)");
                }
            }
            if($Location eq "this") {
                push(@Sentence, "has base type \'".$Problem{"Type_Name"}."\'.");
            }
            elsif($Problem{"Start_Type_Name"} eq $Problem{"Type_Name"}) {
                push(@Sentence, "has type \'".$Problem{"Type_Name"}."\'.");
            }
            else {
                push(@Sentence, "has base type \'".$Problem{"Type_Name"}."\'.");
            }
        }
    }
    if($ExtendedFuncs{$Interface}) {
        push(@Sentence, " This is a symbol from an artificial external library that may use the \'$TargetLibraryName\' library and change its ABI after recompiling.");
    }
    return join(" ", @Sentence);
}

sub get_XmlSign($$)
{
    my ($Symbol, $LibVersion) = @_;
    my $Info = $CompleteSignature{$LibVersion}{$Symbol};
    my $Report = "";
    foreach my $Pos (sort {int($a)<=>int($b)} keys(%{$Info->{"Param"}}))
    {
        my $Name = $Info->{"Param"}{$Pos}{"name"};
        my $TypeName = get_TypeName($Info->{"Param"}{$Pos}{"type"}, $LibVersion);
        foreach my $Typedef (keys(%ChangedTypedef))
        {
            my $Base = $Typedef_BaseName{$LibVersion}{$Typedef};
            $TypeName=~s/(\A|\W)\Q$Typedef\E(\W|\Z)/$1$Base$2/g;
        }
        $Report .= "    <param pos=\"$Pos\">\n";
        $Report .= "      <name>".$Name."</name>\n";
        $Report .= "      <type>".htmlSpecChars($TypeName)."</type>\n";
        $Report .= "    </param>\n";
    }
    if(my $Return = $Info->{"Return"})
    {
        my $RTName = get_TypeName($Return, $LibVersion);
        $Report .= "    <retval>\n";
        $Report .= "      <type>".htmlSpecChars($RTName)."</type>\n";
        $Report .= "    </retval>\n";
    }
    return $Report;
}

sub get_Report_SymbolsInfo()
{
    my $Report = "<symbols_info>\n";
    foreach my $Symbol (sort keys(%CompatProblems))
    {
        if($Symbol=~/\A([^\@\$\?]+)[\@\$]+/
        and defined $CompatProblems{$1}) {
            next;
        }
        $Report .= "  <symbol name=\"$Symbol\">\n";
        my ($S1, $P1, $S2, $P2) = ();
        if(not $AddedInt{$Symbol})
        {
            if(defined $CompleteSignature{1}{$Symbol}
            and defined $CompleteSignature{1}{$Symbol}{"Header"})
            {
                $P1 = get_XmlSign($Symbol, 1);
                $S1 = get_Signature($Symbol, 1);
            }
            elsif($Symbol=~/\A(_Z|\?)/) {
                $S1 = $tr_name{$Symbol};
            }
        }
        if(not $RemovedInt{$Symbol})
        {
            if(defined $CompleteSignature{2}{$Symbol}
            and defined $CompleteSignature{2}{$Symbol}{"Header"})
            {
                $P2 = get_XmlSign($Symbol, 2);
                $S2 = get_Signature($Symbol, 2);
            }
            elsif($Symbol=~/\A(_Z|\?)/) {
                $S2 = $tr_name{$Symbol};
            }
        }
        if($S1)
        {
            $Report .= "    <old signature=\"".htmlSpecChars($S1)."\">\n";
            $Report .= $P1;
            $Report .= "    </old>\n";
        }
        if($S2 and $S2 ne $S1)
        {
            $Report .= "    <new signature=\"".htmlSpecChars($S2)."\">\n";
            $Report .= $P2;
            $Report .= "    </new>\n";
        }
        $Report .= "  </symbol>\n";
    }
    $Report .= "</symbols_info>\n";
    return $Report;
}

sub createHtmlReport($)
{
    my $Path = $_[0];
    my $Report = "";
    if($ReportFormat eq "xml")
    { # XML
        $Report .= "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
        $Report .= "<report version=\"$XML_REPORT_VERSION\">\n\n";
        my ($Summary, $MetaData) = get_Summary("Binary");
        $Report .= $Summary."\n";
        $Report .= get_Report_Added("Binary").get_Report_Removed("Binary");
        $Report .= get_Report_Problems("High", "Binary").get_Report_Problems("Medium", "Binary").get_Report_Problems("Low", "Binary").get_Report_Problems("Safe", "Binary");
        $Report .= get_Report_SymbolsInfo();
        $Report .= "</report>\n";
    }
    else
    { # HTML
        my $CssStyles = readStyles("CompatReport.css");
        my ($Summary, $MetaData) = get_Summary("Binary");
        my $Title = "$TargetLibraryFName: ".$Descriptor{1}{"Version"}." to ".$Descriptor{2}{"Version"}." binary compatibility report";
        my $Keywords = "$TargetLibraryFName, binary compatibility, API, report";
        my $Description = "Binary compatibility report for the $TargetLibraryFName $TargetComponent between ".$Descriptor{1}{"Version"}." and ".$Descriptor{2}{"Version"}." versions";
        if(getArch(1) eq getArch(2)
        and getArch(1) ne "unknown") {
            $Description .= " on ".showArch(getArch(1));
        }
        $Report .= "<!-\- $MetaData -\->\n".composeHTML_Head($Title, $Keywords, $Description, $CssStyles."\n".$JScripts)."\n<body>\n<div><a name='Top'></a>\n";
        $Report .= get_Report_Header()."\n".$Summary."\n";
        $Report .= get_Report_Added("Binary").get_Report_Removed("Binary");
        $Report .= get_Report_Problems("High", "Binary").get_Report_Problems("Medium", "Binary").get_Report_Problems("Low", "Binary").get_Report_Problems("Safe", "Binary");
        $Report .= get_SourceInfo();
        $Report .= "</div>\n<br/><br/><br/><hr/>\n";
        $Report .= getReportFooter($TargetLibraryFName);
        $Report .= "\n<div style='height:999px;'></div>\n</body></html>";
    }
    if($StdOut)
    { # --stdout option
        print STDOUT $Report;
    }
    else {
        writeFile($Path, $Report);
    }
}

sub getReportFooter($)
{
    my $LibName = $_[0];
    my $Footer = "<div style='width:100%;font-size:11px;' align='right'><i>Generated on ".(localtime time); # report date
    $Footer .= " for <span style='font-weight:bold'>$LibName</span>"; # tested library/system name
    $Footer .= " by <a href='".$HomePage{"Dev"}."'>ABI Compliance Checker</a>"; # tool name
    my $ToolSummary = "<br/>A tool for checking backward binary compatibility of a shared C/C++ library API&#160;&#160;";
    $Footer .= " $TOOL_VERSION &#160;$ToolSummary</i></div>"; # tool version
    return $Footer;
}

sub get_Report_Problems($$)
{
    my ($Priority, $Level) = @_;
    my $Report = get_Report_TypeProblems($Priority, $Level).get_Report_InterfaceProblems($Priority, $Level);
    if($Priority eq "Low")
    {
        $Report .= get_Report_ChangedConstants();
        if($ReportFormat eq "html") {
            if($CheckImpl and $Level eq "Binary") {
                $Report .= get_Report_Impl();
            }
        }
    }
    if($ReportFormat eq "html")
    {
        if($Report)
        { # add anchor
            if($Priority eq "Safe") {
                $Report = "<a name=\'Other_Changes\'></a>".$Report;
            }
            else {
                $Report = "<a name=\'".$Priority."_Risk_Problems\'></a>".$Report;
            }
        }
    }
    return $Report;
}

sub composeHTML_Head($$$$)
{
    my ($Title, $Keywords, $Description, $OtherInHead) = @_;
    return "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n<head>
    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />
    <meta name=\"keywords\" content=\"$Keywords\" />
    <meta name=\"description\" content=\"$Description\" />
    <title>\n        $Title\n    </title>\n$OtherInHead\n</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 checkPreprocessedUnit($)
{
    my $Path = $_[0];
    my $CurHeader = "";
    open(PREPROC, "$Path") || die ("can't open file \'$Path\': $!\n");
    while(<PREPROC>)
    {# detecting public and private constants
        next if(not /\A#/);
        chomp($_);
        if(/#[ \t]+\d+[ \t]+\"(.+)\"/) {
            $CurHeader=path_format($1, $OSgroup);
        }
        if(not $Include_Neighbors{$Version}{get_filename($CurHeader)}
        and not $Registered_Headers{$Version}{$CurHeader})
        { # not a target
            next;
        }
        if(not is_target_header(get_filename($CurHeader)))
        { # user-defined header
            next;
        }
        if(/\#[ \t]*define[ \t]+([_A-Z0-9]+)[ \t]+(.+)[ \t]*\Z/)
        {
            my ($Name, $Value) = ($1, $2);
            if(not $Constants{$Version}{$Name}{"Access"})
            {
                $Constants{$Version}{$Name}{"Access"} = "public";
                $Constants{$Version}{$Name}{"Value"} = $Value;
                $Constants{$Version}{$Name}{"Header"} = get_filename($CurHeader);
            }
        }
        elsif(/\#[ \t]*undef[ \t]+([_A-Z]+)[ \t]*/) {
            $Constants{$Version}{$1}{"Access"} = "private";
        }
    }
    close(PREPROC);
    foreach my $Constant (keys(%{$Constants{$Version}}))
    {
        if($Constants{$Version}{$Constant}{"Access"} eq "private" or $Constant=~/_h\Z/i
        or isBuiltIn($Constants{$Version}{$Constant}{"Header"}))
        { # skip private constants
            delete($Constants{$Version}{$Constant});
        }
        else {
            delete($Constants{$Version}{$Constant}{"Access"});
        }
    }
}

my %IgnoreConstant=(
    "VERSION"=>1,
    "VERSIONCODE"=>1,
    "VERNUM"=>1,
    "VERS_INFO"=>1,
    "PATCHLEVEL"=>1,
    "INSTALLPREFIX"=>1,
    "VBUILD"=>1,
    "VPATCH"=>1,
    "VMINOR"=>1,
    "BUILD_STRING"=>1,
    "BUILD_TIME"=>1,
    "PACKAGE_STRING"=>1,
    "PRODUCTION"=>1,
    "CONFIGURE_COMMAND"=>1,
    "INSTALLDIR"=>1,
    "BINDIR"=>1,
    "CONFIG_FILE_PATH"=>1,
    "DATADIR"=>1,
    "EXTENSION_DIR"=>1,
    "INCLUDE_PATH"=>1,
    "LIBDIR"=>1,
    "LOCALSTATEDIR"=>1,
    "SBINDIR"=>1,
    "SYSCONFDIR"=>1,
    "RELEASE"=>1,
    "SOURCE_ID"=>1,
    "SUBMINOR"=>1,
    "MINOR"=>1,
    "MINNOR"=>1,
    "MINORVERSION"=>1,
    "MAJOR"=>1,
    "MAJORVERSION"=>1,
    "MICRO"=>1,
    "MICROVERSION"=>1,
    "BINARY_AGE"=>1,
    "INTERFACE_AGE"=>1,
    "CORE_ABI"=>1,
    "PATCH"=>1,
    "COPYRIGHT"=>1,
    "TIMESTAMP"=>1,
    "REVISION"=>1,
    "PACKAGE_TAG"=>1,
    "PACKAGEDATE"=>1,
    "NUMVERSION"=>1
);

sub mergeConstants()
{
    foreach my $Constant (keys(%{$Constants{1}}))
    {
        if($SkipConstants{1}{$Constant})
        { # skipped by the user
            next;
        }
        if($Constants{2}{$Constant}{"Value"} eq "")
        { # empty value
            next;
        }
        if(not is_target_header($Constants{1}{$Constant}{"Header"}))
        { # user-defined header
            next;
        }
        my ($Old_Value, $New_Value, $Old_Value_Pure, $New_Value_Pure);
        $Old_Value = $Old_Value_Pure = uncover_constant(1, $Constant);
        $New_Value = $New_Value_Pure = uncover_constant(2, $Constant);
        $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(grep {$Constant=~/(\A|_)$_(_|\Z)/} keys(%IgnoreConstant))
            { # ignore library version
                next;
            }
            if($Constant=~/(\A|_)(lib|open|)$TargetLibraryShortName(_|)(VERSION|VER|DATE|API|PREFIX)(_|\Z)/i)
            { # ignore library version
                next;
            }
            if($Old_Value=~/\A('|"|)[\/\\]\w+([\/\\]|:|('|"|)\Z)/ or $Old_Value=~/[\/\\]\w+[\/\\]\w+/)
            { # ignoring path defines:
              #  /lib64:/usr/lib64:/lib:/usr/lib:/usr/X11R6/lib/Xaw3d ...
                next;
            }
            if($Old_Value=~/\A\(*[a-z_]+(\s+|\|)/i)
            { # ignore source defines:
              #  static int gcry_pth_init ( void) { return ...
              #  (RE_BACKSLASH_ESCAPE_IN_LISTS | RE...
                next;
            }
            if(convert_integer($Old_Value) eq convert_integer($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;
            }
            %{$ProblemsWithConstants{$Constant}} = (
                "Target"=>$Constant,
                "Old_Value"=>$Old_Value,
                "New_Value"=>$New_Value  );
        }
    }
}

sub convert_integer($)
{
    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 uncover_constant($$)
{
    my ($LibVersion, $Constant) = @_;
    return "" if(not $LibVersion or not $Constant);
    return $Constant if(isCyclical(\@RecurConstant, $Constant));
    if($Cache{"uncover_constant"}{$LibVersion}{$Constant} ne "") {
        return $Cache{"uncover_constant"}{$LibVersion}{$Constant};
    }
    my $Value = $Constants{$LibVersion}{$Constant}{"Value"};
    if($Value=~/\A[A-Z0-9_]+\Z/ and $Value=~/[A-Z]/)
    {
        push(@RecurConstant, $Constant);
        if((my $Uncovered = uncover_constant($LibVersion, $Value)) ne "") {
            $Value = $Uncovered;
        }
        pop(@RecurConstant);
    }
    # FIXME: uncover $Value using all the enum constants
    # USECASE: change of define NC_LONG from NC_INT (enum value) to NC_INT (define)
    $Cache{"uncover_constant"}{$LibVersion}{$Constant} = $Value;
    return $Value;
}

sub getSymbols($)
{
    my $LibVersion = $_[0];
    my @DyLibPaths = getSoPaths($LibVersion);
    if($#DyLibPaths==-1 and not $CheckHeadersOnly)
    {
        if($LibVersion==1)
        {
            printMsg("WARNING", "checking headers only");
            $CheckHeadersOnly = 1;
        }
        else {
            exitStatus("Error", "$SLIB_TYPE libraries are not found in ".$Descriptor{$LibVersion}{"Version"});
        }
    }
    my %GroupNames = map {parse_libname(get_filename($_), "name+ext", $OStarget)=>1} @DyLibPaths;
    foreach my $DyLibPath (sort {length($a)<=>length($b)} @DyLibPaths) {
        getSymbols_Lib($LibVersion, $DyLibPath, 0, \%GroupNames, "+Weak");
    }
}

sub get_VTableSymbolSize($$)
{
    my ($ClassName, $LibVersion) = @_;
    return 0 if(not $ClassName);
    my $Symbol = $ClassVTable{$LibVersion}{$ClassName};
    if(defined $Symbol_Library{$LibVersion}{$Symbol}
    and my $DyLib = $Symbol_Library{$LibVersion}{$Symbol})
    { # bind class name and v-table size
        if(defined $Library_Symbol{$LibVersion}{$DyLib}{$Symbol}
        and my $Size = -$Library_Symbol{$LibVersion}{$DyLib}{$Symbol})
        { # size from the shared library
            if($Size>=12) {
#               0     (int (*)(...))0
#               4     (int (*)(...))(& _ZTIN7mysqlpp8DateTimeE)
#               8     mysqlpp::DateTime::~DateTime
                return $Size;
            }
            else {
                return 0;
            }
        }
    }
}

sub canonifyName($)
{ # make TIFFStreamOpen(char const*, std::basic_ostream<char, std::char_traits<char> >*)
  # to be TIFFStreamOpen(char const*, std::basic_ostream<char>*)
    my $Name = $_[0];
    my $Rem = "std::(allocator|less|char_traits|regex_traits)";
    if($Name=~/([^<>,]+),\s*$Rem<([^<>,]+)>\s*/)
    {
        if($1 eq $3)
        {
            my $P = $1;
            while($Name=~s/\Q$P\E,\s*$Rem<\Q$P\E>\s*/$P/g){};
        }
    }
    return $Name;
}

sub translateSymbols(@_$)
{
    my $LibVersion = pop(@_);
    my (@MnglNames1, @MnglNames2, @UnMnglNames) = ();
    foreach my $Interface (sort @_)
    {
        if($Interface=~/\A_Z/)
        {
            next if($tr_name{$Interface});
            $Interface=~s/[\@\$]+(.*)\Z//;
            push(@MnglNames1, $Interface);
        }
        elsif($Interface=~/\A\?/) {
            push(@MnglNames2, $Interface);
        }
        else
        { # not mangled
            $tr_name{$Interface} = $Interface;
            $mangled_name_gcc{$Interface} = $Interface;
            $mangled_name{$LibVersion}{$Interface} = $Interface;
        }
    }
    if($#MnglNames1 > -1)
    { # GCC names
        @UnMnglNames = reverse(unmangleArray(@MnglNames1));
        foreach my $MnglName (@MnglNames1)
        {
            my $Unmangled = $tr_name{$MnglName} = formatName(canonifyName(pop(@UnMnglNames)));
            if(not $mangled_name_gcc{$Unmangled}) {
                $mangled_name_gcc{$Unmangled} = $MnglName;
            }
            if($MnglName=~/\A_ZTV/ and $Unmangled=~/vtable for (.+)/)
            { # bind class name and v-table symbol
                $ClassVTable{$LibVersion}{$1} = $MnglName;
                $VTableClass{$LibVersion}{$MnglName} = $1;
            }
        }
    }
    if($#MnglNames2 > -1)
    { # MSVC names
        @UnMnglNames = reverse(unmangleArray(@MnglNames2));
        foreach my $MnglName (@MnglNames2)
        {
            $tr_name{$MnglName} = formatName(pop(@UnMnglNames));
            $mangled_name{$LibVersion}{$tr_name{$MnglName}} = $MnglName;
        }
    }
    return \%tr_name;
}

sub link_symbol($$$)
{
    my ($Symbol, $RunWith, $Deps) = @_;
    if(link_symbol_internal($Symbol, $RunWith, \%Symbol_Library)) {
        return 1;
    }
    if($Deps eq "+Deps")
    { # check the dependencies
        if(link_symbol_internal($Symbol, $RunWith, \%DepSymbols)) {
            return 1;
        }
    }
    return 0;
}

sub link_symbol_internal($$$)
{
    my ($Symbol, $RunWith, $Where) = @_;
    return 0 if(not $Where or not $Symbol);
    if($Where->{$RunWith}{$Symbol})
    { # the exact match by symbol name
        return 1;
    }
    if(my $VSym = $SymVer{$RunWith}{$Symbol})
    { # indirect symbol version, i.e.
      # foo_old and its symlink foo@v (or foo@@v)
      # foo_old may be in .symtab table
        if($Where->{$RunWith}{$VSym}) {
            return 1;
        }
    }
    my ($Sym, $Del, $Ver) = separate_symbol($Symbol);
    if($Sym and $Ver)
    { # search for the symbol with the same version
      # or without version
        if($Where->{$RunWith}{$Sym})
        { # old: foo@v|foo@@v
          # new: foo
            return 1;
        }
        if($Where->{$RunWith}{$Sym."\@".$Ver})
        { # old: foo|foo@@v
          # new: foo@v
            return 1;
        }
        if($Where->{$RunWith}{$Sym."\@\@".$Ver})
        { # old: foo|foo@v
          # new: foo@@v
            return 1;
        }
    }
    return 0;
}

sub getSymbols_App($)
{
    my $Path = $_[0];
    return () if(not $Path or not -f $Path);
    my @Imported = ();
    if($OSgroup eq "macos")
    {
        my $OtoolCmd = get_CmdPath("otool");
        if(not $OtoolCmd) {
            exitStatus("Not_Found", "can't find \"otool\"");
        }
        open(APP, "$OtoolCmd -IV $Path 2>$TMP_DIR/null |");
        while(<APP>) {
            if(/[^_]+\s+_?([\w\$]+)\s*\Z/) {
                push(@Imported, $1);
            }
        }
        close(APP);
    }
    elsif($OSgroup eq "windows")
    {
        my $DumpBinCmd = get_CmdPath("dumpbin");
        if(not $DumpBinCmd) {
            exitStatus("Not_Found", "can't find \"dumpbin.exe\"");
        }
        open(APP, "$DumpBinCmd /IMPORTS $Path 2>$TMP_DIR/null |");
        while(<APP>) {
            if(/\s*\w+\s+\w+\s+\w+\s+([\w\?\@]+)\s*/) {
                push(@Imported, $1);
            }
        }
        close(APP);
    }
    else
    {
        my $ReadelfCmd = get_CmdPath("readelf");
        if(not $ReadelfCmd) {
            exitStatus("Not_Found", "can't find \"readelf\"");
        }
        open(APP, "$ReadelfCmd -WhlSsdA $Path 2>$TMP_DIR/null |");
        my $symtab=0; # indicates that we are processing 'symtab' section of 'readelf' output
        while(<APP>)
        {
            if( /'.dynsym'/ ) {
                $symtab=0;
            }
            elsif($symtab == 1) {
                # do nothing with symtab (but there are some plans for the future)
                next;
            }
            elsif( /'.symtab'/ ) {
                $symtab=1;
            }
            elsif(my ($fullname, $idx, $Ndx, $type, $size, $bind) = readline_ELF($_))
            {
                if( $Ndx eq "UND" ) {
                    #only imported symbols
                    push(@Imported, $fullname);
                }
            }
        }
        close(APP);
    }
    return @Imported;
}

sub readline_ELF($)
{
    if($_[0]=~/\s*\d+:\s+(\w*)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\w+)\s([^\s]+)/)
    { # the line of 'readelf' output corresponding to the interface
      # symbian-style: _ZN12CCTTokenType4NewLE4TUid3RFs@@ctfinder{000a0000}[102020e5].dll
        my ($value, $size, $type, $bind,
        $vis, $Ndx, $fullname)=($1, $2, $3, $4, $5, $6, $7);
        if($bind!~/\A(WEAK|GLOBAL)\Z/) {
            return ();
        }
        if($type!~/\A(FUNC|IFUNC|OBJECT|COMMON)\Z/) {
            return ();
        }
        if($vis!~/\A(DEFAULT|PROTECTED)\Z/) {
            return ();
        }
        if($Ndx eq "ABS" and $value!~/\D|1|2|3|4|5|6|7|8|9/) {
            return ();
        }
        if($OStarget eq "symbian")
        {
            if($fullname=~/_\._\.absent_export_\d+/)
            {# "_._.absent_export_111"@@libstdcpp{00010001}[10282872].dll
                return ();
            }
            my @Elems = separate_symbol($fullname);
            $fullname = $Elems[0];# remove internal version, {00020001}[10011235].dll
        }
        return ($fullname, $value, $Ndx, $type, $size, $bind);
    }
    else {
        return ();
    }
}

sub getSymbols_Lib($$$$$)
{
    my ($LibVersion, $Lib_Path, $IsNeededLib, $GroupNames, $Weak) = @_;
    return if(not $Lib_Path or not -f $Lib_Path);
    my ($Lib_Dir, $Lib_Name) = separate_path(resolve_symlink($Lib_Path));
    return if($CheckedDyLib{$LibVersion}{$Lib_Name} and $IsNeededLib);
    return if(isCyclical(\@RecurLib, $Lib_Name) or $#RecurLib>=1);
    $CheckedDyLib{$LibVersion}{$Lib_Name} = 1;
    getImplementations($LibVersion, $Lib_Path) if($CheckImpl and not $IsNeededLib);
    push(@RecurLib, $Lib_Name);
    my (%Value_Interface, %Interface_Value, %NeededLib) = ();
    if(not $IsNeededLib)
    { # libstdc++ and libc are always used by other libs
      # if you test one of these libs then you not need
      # to find them in the system for reusing
        if(parse_libname($Lib_Name, "short", $OStarget) eq "libstdc++")
        { # libstdc++.so.6
            $STDCXX_TESTING = 1;
        }
        if(parse_libname($Lib_Name, "short", $OStarget) eq "libc")
        { # libc-2.11.3.so
            $GLIBC_TESTING = 1;
        }
    }
    if($OStarget eq "macos")
    { # Mac OS X: *.dylib, *.a
        my $OtoolCmd = get_CmdPath("otool");
        if(not $OtoolCmd) {
            exitStatus("Not_Found", "can't find \"otool\"");
        }
        open(LIB, "$OtoolCmd -TV $Lib_Path 2>$TMP_DIR/null |");
        while(<LIB>)
        {
            if(/[^_]+\s+_([\w\$]+)\s*\Z/)
            {
                my $realname = $1;
                if($IsNeededLib and $GroupNames
                and not $GroupNames->{parse_libname($Lib_Name, "name+ext", $OStarget)}) {
                    $DepSymbols{$LibVersion}{$realname} = 1;
                }
                if(not $IsNeededLib)
                {
                    $Symbol_Library{$LibVersion}{$realname} = $Lib_Name;
                    $Library_Symbol{$LibVersion}{$Lib_Name}{$realname} = 1;
                    if($COMMON_LANGUAGE{$LibVersion} ne "C++"
                    and $realname=~/\A(_Z|\?)/) {
                        setLanguage($LibVersion, "C++");
                    }
                    if($CheckObjectsOnly
                    and $LibVersion==1) {
                        $CheckedSymbols{$realname} = 1;
                    }
                }
            }
        }
        close(LIB);
        if($LIB_TYPE eq "dynamic")
        { # dependencies
            open(LIB, "$OtoolCmd -L $Lib_Path 2>$TMP_DIR/null |");
            while(<LIB>) {
                if(/\s*([\/\\].+\.$LIB_EXT)\s*/
                and $1 ne $Lib_Path) {
                    $NeededLib{$1} = 1;
                }
            }
            close(LIB);
        }
    }
    elsif($OStarget eq "windows")
    { # Windows *.dll, *.lib
        my $DumpBinCmd = get_CmdPath("dumpbin");
        if(not $DumpBinCmd) {
            exitStatus("Not_Found", "can't find \"dumpbin\"");
        }
        open(LIB, "$DumpBinCmd /EXPORTS \"$Lib_Path\" 2>$TMP_DIR/null |");
        while(<LIB>)
        { # 1197 4AC 0000A620 SetThreadStackGuarantee
          # 1198 4AD          SetThreadToken (forwarded to ...)
          # 3368 _o2i_ECPublicKey
            if(/\A\s*\d+\s+[a-f\d]+\s+[a-f\d]+\s+([\w\?\@]+)\s*\Z/i
            or /\A\s*\d+\s+[a-f\d]+\s+([\w\?\@]+)\s*\(\s*forwarded\s+/
            or /\A\s*\d+\s+_([\w\?\@]+)\s*\Z/)
            { # dynamic, static and forwarded symbols
                my $realname = $1;
                if($IsNeededLib and not $GroupNames->{parse_libname($Lib_Name, "name+ext", $OStarget)}) {
                    $DepSymbols{$LibVersion}{$realname} = 1;
                }
                if(not $IsNeededLib)
                {
                    $Symbol_Library{$LibVersion}{$realname} = $Lib_Name;
                    $Library_Symbol{$LibVersion}{$Lib_Name}{$realname} = 1;
                    if($COMMON_LANGUAGE{$LibVersion} ne "C++"
                    and $realname=~/\A(_Z|\?)/) {
                        setLanguage($LibVersion, "C++");
                    }
                    if($CheckObjectsOnly
                    and $LibVersion==1) {
                        $CheckedSymbols{$realname} = 1;
                    }
                }
            }
        }
        close(LIB);
        if($LIB_TYPE eq "dynamic")
        { # dependencies
            open(LIB, "$DumpBinCmd /DEPENDENTS $Lib_Path 2>$TMP_DIR/null |");
            while(<LIB>) {
                if(/\s*([^\s]+?\.$LIB_EXT)\s*/i
                and $1 ne $Lib_Path) {
                    $NeededLib{path_format($1, $OSgroup)} = 1;
                }
            }
            close(LIB);
        }
    }
    else
    { # Unix; *.so, *.a
      # Symbian: *.dso, *.lib
        my $ReadelfCmd = get_CmdPath("readelf");
        if(not $ReadelfCmd) {
            exitStatus("Not_Found", "can't find \"readelf\"");
        }
        open(LIB, "$ReadelfCmd -WhlSsdA $Lib_Path 2>$TMP_DIR/null |");
        my $symtab=0; # indicates that we are processing 'symtab' section of 'readelf' output
        while(<LIB>)
        {
            if($LIB_TYPE eq "dynamic")
            { # dynamic library specifics
                if(/NEEDED.+\[([^\[\]]+)\]/)
                { # dependencies:
                  # 0x00000001 (NEEDED) Shared library: [libc.so.6]
                    $NeededLib{$1} = 1;
                    next;
                }
                if(/'\.dynsym'/)
                { # dynamic table
                    $symtab=0;
                    next;
                }
                if($symtab == 1)
                { # do nothing with symtab
                    next;
                }
                if(/'\.symtab'/)
                { # symbol table
                    $symtab=1;
                    next;
                }
            }
            if(not $LIB_ARCH{$LibVersion})
            {
                if(/Machine:.*?([\w\-]+)\s*\Z/)
                { # architecture
                    $LIB_ARCH{$LibVersion}=$1;
                    next;
                }
            }
            if(my ($fullname, $idx, $Ndx, $type, $size, $bind) = readline_ELF($_))
            { # read ELF entry
                if( $Ndx eq "UND" )
                { # ignore interfaces that are imported from somewhere else
                    next;
                }
                if($bind eq "WEAK"
                and $Weak eq "-Weak")
                { # skip WEAK symbols
                    next;
                }
                my ($realname, $version_spec, $version) = separate_symbol($fullname);
                if($type eq "OBJECT")
                { # global data
                    $CompleteSignature{$LibVersion}{$fullname}{"Object"} = 1;
                    $CompleteSignature{$LibVersion}{$realname}{"Object"} = 1;
                }
                if($IsNeededLib and not $GroupNames->{parse_libname($Lib_Name, "name+ext", $OStarget)}) {
                    $DepSymbols{$LibVersion}{$fullname} = 1;
                }
                if(not $IsNeededLib)
                {
                    $Symbol_Library{$LibVersion}{$fullname} = $Lib_Name;
                    $Library_Symbol{$LibVersion}{$Lib_Name}{$fullname} = ($type eq "OBJECT")?-$size:1;
                    if($LIB_EXT eq "so")
                    { # value
                        $Interface_Value{$LibVersion}{$fullname} = $idx;
                        $Value_Interface{$LibVersion}{$idx}{$fullname} = 1;
                    }
                    if($COMMON_LANGUAGE{$LibVersion} ne "C++"
                    and $realname=~/\A(_Z|\?)/) {
                        setLanguage($LibVersion, "C++");
                    }
                    if($CheckObjectsOnly
                    and $LibVersion==1) {
                        $CheckedSymbols{$fullname} = 1;
                    }
                }
            }
        }
        close(LIB);
    }
    if(not $IsNeededLib and $LIB_EXT eq "so")
    { # get symbol versions
        foreach my $Symbol (keys(%{$Symbol_Library{$LibVersion}}))
        {
            next if($Symbol!~/\@/);
            my $Interface_SymName = "";
            foreach my $Symbol_SameValue (keys(%{$Value_Interface{$LibVersion}{$Interface_Value{$LibVersion}{$Symbol}}}))
            {
                if($Symbol_SameValue ne $Symbol
                and $Symbol_SameValue!~/\@/)
                {
                    $SymVer{$LibVersion}{$Symbol_SameValue} = $Symbol;
                    $Interface_SymName = $Symbol_SameValue;
                    last;
                }
            }
            if(not $Interface_SymName)
            {
                if($Symbol=~/\A([^\@\$\?]*)[\@\$]+([^\@\$]*)\Z/
                and not $SymVer{$LibVersion}{$1}) {
                    $SymVer{$LibVersion}{$1} = $Symbol;
                }
            }
        }
    }
    foreach my $DyLib (sort keys(%NeededLib))
    {
        my $DepPath = find_lib_path($LibVersion, $DyLib);
        if($DepPath and -f $DepPath) {
            getSymbols_Lib($LibVersion, $DepPath, 1, $GroupNames, "+Weak");
        }
    }
    pop(@RecurLib);
    return $Library_Symbol{$LibVersion};
}

sub get_path_prefixes($)
{
    my $Path = $_[0];
    my ($Dir, $Name) = separate_path($Path);
    my %Prefixes = ();
    foreach my $Prefix (reverse(split(/[\/\\]+/, $Dir)))
    {
        $Prefixes{$Name} = 1;
        $Name = joinPath($Prefix, $Name);
        last if(keys(%Prefixes)>5 or $Prefix eq "include");
    }
    return keys(%Prefixes);
}

sub detectSystemHeaders()
{
    my @SysHeaders = ();
    foreach my $DevelPath (keys(%{$SystemPaths{"include"}}))
    {
        next if(not -d $DevelPath);
        # search for all header files in the /usr/include
        # with or without extension (ncurses.h, QtCore, ...)
        @SysHeaders = (@SysHeaders, cmd_find($DevelPath,"f","",""));
        foreach my $Link (cmd_find($DevelPath,"l","",""))
        { # add symbolic links
            if(-f $Link) {
                push(@SysHeaders, $Link);
            }
        }
    }
    foreach my $DevelPath (keys(%{$SystemPaths{"lib"}}))
    {
        next if(not -d $DevelPath);
        # search for config headers in the /usr/lib
        @SysHeaders = (@SysHeaders, cmd_find($DevelPath,"f","*.h",""));
        foreach my $Dir (cmd_find($DevelPath,"d","include",""))
        { # search for all include directories
          # this is for headers that are installed to /usr/lib
          # Example: Qt4 headers in Mandriva (/usr/lib/qt4/include/)
            if($Dir=~/\/(gcc|jvm|syslinux|kdb)\//) {
                next;
            }
            @SysHeaders = (@SysHeaders, cmd_find($Dir,"f","",""));
        }
    }
    foreach my $Path (@SysHeaders)
    {
        foreach my $Part (get_path_prefixes($Path)) {
            $SystemHeaders{$Part}{$Path}=1;
        }
    }
}

sub detectSystemObjects()
{
    foreach my $DevelPath (keys(%{$SystemPaths{"lib"}}))
    {
        next if(not -d $DevelPath);
        foreach my $Path (find_libs($DevelPath,"",""))
        { # search for shared libraries in the /usr/lib (including symbolic links)
            $SystemObjects{parse_libname(get_filename($Path), "name+ext", $OStarget)}{$Path}=1;
        }
    }
}

sub find_lib_path($$)
{
    my ($LibVersion, $DyLib) = @_;
    return "" if(not $DyLib or not $LibVersion);
    return $DyLib if(is_abs($DyLib));
    if(defined $Cache{"find_lib_path"}{$LibVersion}{$DyLib}) {
        return $Cache{"find_lib_path"}{$LibVersion}{$DyLib};
    }
    if(my @Paths = sort keys(%{$InputObject_Paths{$LibVersion}{$DyLib}})) {
        return ($Cache{"find_lib_path"}{$LibVersion}{$DyLib} = $Paths[0]);
    }
    elsif(my $DefaultPath = $DyLib_DefaultPath{$DyLib}) {
        return ($Cache{"find_lib_path"}{$LibVersion}{$DyLib} = $DefaultPath);
    }
    else
    {
        foreach my $Dir (sort keys(%DefaultLibPaths), sort keys(%{$SystemPaths{"lib"}}))
        { # search in default linker paths and then in all system paths
            if(-f $Dir."/".$DyLib) {
                return ($Cache{"find_lib_path"}{$LibVersion}{$DyLib} = joinPath($Dir,$DyLib));
            }
        }
        detectSystemObjects() if(not keys(%SystemObjects));
        if(my @AllObjects = keys(%{$SystemObjects{$DyLib}})) {
            return ($Cache{"find_lib_path"}{$LibVersion}{$DyLib} = $AllObjects[0]);
        }
        my $ShortName = parse_libname($DyLib, "name+ext", $OStarget);
        if($ShortName ne $DyLib
        and my $Path = find_lib_path($ShortName))
        { # FIXME: check this case
            return ($Cache{"find_lib_path"}{$LibVersion}{$DyLib} = $Path);
        }
        return ($Cache{"find_lib_path"}{$LibVersion}{$DyLib} = "");
    }
}

sub getSoPaths($)
{
    my $LibVersion = $_[0];
    my @SoPaths = ();
    foreach my $Dest (split(/\s*\n\s*/, $Descriptor{$LibVersion}{"Libs"}))
    {
        if(not -e $Dest) {
            exitStatus("Access_Error", "can't access \'$Dest\'");
        }
        my @SoPaths_Dest = getSOPaths_Dest($Dest, $LibVersion);
        foreach (@SoPaths_Dest) {
            push(@SoPaths, $_);
        }
    }
    return @SoPaths;
}

sub skip_lib($$)
{
    my ($Path, $LibVersion) = @_;
    return 1 if(not $Path or not $LibVersion);
    my $LibName = get_filename($Path);
    if($SkipLibs{$LibVersion}{"Name"}{$LibName}) {
        return 1;
    }
    my $ShortName = parse_libname($LibName, "name+ext", $OStarget);
    if($SkipLibs{$LibVersion}{"Name"}{$ShortName}) {
        return 1;
    }
    foreach my $Dir (keys(%{$SkipLibs{$LibVersion}{"Path"}}))
    {
        if($Path=~/\Q$Dir\E([\/\\]|\Z)/) {
            return 1;
        }
    }
    foreach my $Pattern (keys(%{$SkipLibs{$LibVersion}{"Pattern"}}))
    {
        if($LibName=~/$Pattern/) {
            return 1;
        }
        if($Pattern=~/[\/\\]/ and $Path=~/$Pattern/) {
            return 1;
        }
    }
    return 0;
}

sub skip_header($$)
{ # returns:
  #  1 - if header should NOT be included and checked
  #  2 - if header should NOT be included, but should be checked
    my ($Path, $LibVersion) = @_;
    return 1 if(not $Path or not $LibVersion);
    my $HeaderName = get_filename($Path);
    if(my $Kind = $SkipHeaders{$LibVersion}{"Name"}{$HeaderName}) {
        return $Kind;
    }
    foreach my $Dir (keys(%{$SkipHeaders{$LibVersion}{"Path"}}))
    {
        if($Path=~/\Q$Dir\E([\/\\]|\Z)/) {
            return $SkipHeaders{$LibVersion}{"Path"}{$Dir};
        }
    }
    foreach my $Pattern (keys(%{$SkipHeaders{$LibVersion}{"Pattern"}}))
    {
        if($HeaderName=~/$Pattern/) {
            return $SkipHeaders{$LibVersion}{"Pattern"}{$Pattern};
        }
        if($Pattern=~/[\/\\]/ and $Path=~/$Pattern/) {
            return $SkipHeaders{$LibVersion}{"Pattern"}{$Pattern};
        }
    }
    return 0;
}

sub register_objects($$)
{
    my ($Dir, $LibVersion) = @_;
    if($SystemPaths{"lib"}{$Dir})
    { # system directory
        return;
    }
    if($RegisteredObjDirs{$LibVersion}{$Dir})
    { # already registered
        return;
    }
    foreach my $Path (find_libs($Dir,"",1))
    {
        next if(ignore_path($Path));
        next if(skip_lib($Path, $LibVersion));
        $InputObject_Paths{$LibVersion}{get_filename($Path)}{$Path} = 1;
    }
    $RegisteredObjDirs{$LibVersion}{$Dir} = 1;
}

sub getSOPaths_Dest($$)
{
    my ($Dest, $LibVersion) = @_;
    if(skip_lib($Dest, $LibVersion)) {
        return ();
    }
    if(-f $Dest)
    {
        if(not parse_libname($Dest, "name", $OStarget)) {
            exitStatus("Error", "incorrect format of library (should be *.$LIB_EXT): \'$Dest\'");
        }
        $InputObject_Paths{$LibVersion}{get_filename($Dest)}{$Dest} = 1;
        register_objects(get_dirname($Dest), $LibVersion);
        return ($Dest);
    }
    elsif(-d $Dest)
    {
        $Dest=~s/[\/\\]+\Z//g;
        my @AllObjects = ();
        if($SystemPaths{"lib"}{$Dest})
        { # you have specified /usr/lib as the search directory (<libs>) in the XML descriptor
          # and the real name of the library by -l option (bz2, stdc++, Xaw, ...)
            foreach my $Path (cmd_find($Dest,"","*".esc($TargetLibraryName)."*\.$LIB_EXT*",2))
            { # all files and symlinks that match the name of a library
                if(get_filename($Path)=~/\A(|lib)\Q$TargetLibraryName\E[\d\-]*\.$LIB_EXT[\d\.]*\Z/i)
                {
                    $InputObject_Paths{$LibVersion}{get_filename($Path)}{$Path} = 1;
                    push(@AllObjects, resolve_symlink($Path));
                }
            }
        }
        else
        { # search for all files and symlinks
            foreach my $Path (find_libs($Dest,"",""))
            {
                next if(ignore_path($Path));
                next if(skip_lib($Path, $LibVersion));
                $InputObject_Paths{$LibVersion}{get_filename($Path)}{$Path} = 1;
                push(@AllObjects, resolve_symlink($Path));
            }
            if($OSgroup eq "macos")
            { # shared libraries on MacOS X may have no extension
                foreach my $Path (cmd_find($Dest,"f","",""))
                {
                    next if(ignore_path($Path));
                    next if(skip_lib($Path, $LibVersion));
                    if(get_filename($Path)!~/\./
                    and cmd_file($Path)=~/(shared|dynamic)\s+library/i) {
                        $InputObject_Paths{$LibVersion}{get_filename($Path)}{$Path} = 1;
                        push(@AllObjects, resolve_symlink($Path));
                    }
                }
            }
        }
        return @AllObjects;
    }
    else {
        return ();
    }
}

sub isCyclical($$) {
    return (grep {$_ eq $_[1]} @{$_[0]});
}

sub read_symlink($)
{
    my $Path = $_[0];
    return "" if(not $Path);
    return "" if(not -f $Path and not -l $Path);
    if(defined $Cache{"read_symlink"}{$Path}) {
        return $Cache{"read_symlink"}{$Path};
    }
    if(my $Res = readlink($Path)) {
        return ($Cache{"read_symlink"}{$Path} = $Res);
    }
    elsif(my $ReadlinkCmd = get_CmdPath("readlink")) {
        return ($Cache{"read_symlink"}{$Path} = `$ReadlinkCmd -n $Path`);
    }
    elsif(my $FileCmd = get_CmdPath("file"))
    {
        my $Info = `$FileCmd $Path`;
        if($Info=~/symbolic\s+link\s+to\s+['`"]*([\w\d\.\-\/\\]+)['`"]*/i) {
            return ($Cache{"read_symlink"}{$Path} = $1);
        }
    }
    return ($Cache{"read_symlink"}{$Path} = "");
}

sub resolve_symlink($)
{
    my $Path = $_[0];
    return "" if(not $Path);
    return "" if(not -f $Path and not -l $Path);
    if(defined $Cache{"resolve_symlink"}{$Path}) {
        return $Cache{"resolve_symlink"}{$Path};
    }
    return $Path if(isCyclical(\@RecurSymlink, $Path));
    push(@RecurSymlink, $Path);
    if(-l $Path and my $Redirect=read_symlink($Path))
    {
        if(is_abs($Redirect))
        { # absolute path
            if($SystemRoot and $SystemRoot ne "/"
            and $Path=~/\A\Q$SystemRoot\E\//
            and (-f $SystemRoot.$Redirect or -l $SystemRoot.$Redirect))
            { # symbolic links from the sysroot
              # should be corrected to point to
              # the files inside sysroot
                $Redirect = $SystemRoot.$Redirect;
            }
            my $Res = resolve_symlink($Redirect);
            pop(@RecurSymlink);
            return ($Cache{"resolve_symlink"}{$Path} = $Res);
        }
        elsif($Redirect=~/\.\.[\/\\]/)
        { # relative path
            $Redirect = joinPath(get_dirname($Path),$Redirect);
            while($Redirect=~s&(/|\\)[^\/\\]+(\/|\\)\.\.(\/|\\)&$1&){};
            my $Res = resolve_symlink($Redirect);
            pop(@RecurSymlink);
            return ($Cache{"resolve_symlink"}{$Path} = $Res);
        }
        elsif(-f get_dirname($Path)."/".$Redirect)
        { # file name in the same directory
            my $Res = resolve_symlink(joinPath(get_dirname($Path),$Redirect));
            pop(@RecurSymlink);
            return ($Cache{"resolve_symlink"}{$Path} = $Res);
        }
        else
        { # broken link
            pop(@RecurSymlink);
            return ($Cache{"resolve_symlink"}{$Path} = "");
        }
    }
    pop(@RecurSymlink);
    return ($Cache{"resolve_symlink"}{$Path} = $Path);
}

sub generateTemplate()
{
    writeFile("VERSION.xml", $DescriptorTemplate."\n");
    printMsg("INFO", "XML-descriptor template ./VERSION.xml has been generated");
}

sub detectWordSize()
{
    return "" if(not $GCC_PATH);
    if($Cache{"detectWordSize"}) {
        return $Cache{"detectWordSize"};
    }
    writeFile("$TMP_DIR/empty.h", "");
    my $Defines = `$GCC_PATH -E -dD $TMP_DIR/empty.h`;
    unlink("$TMP_DIR/empty.h");
    my $WSize = 0;
    if($Defines=~/ __SIZEOF_POINTER__\s+(\d+)/)
    {# GCC 4
        $WSize = $1;
    }
    elsif($Defines=~/ __PTRDIFF_TYPE__\s+(\w+)/)
    {# GCC 3
        my $PTRDIFF = $1;
        if($PTRDIFF=~/long/) {
            $WSize = 8;
        }
        else {
            $WSize = 4;
        }
    }
    if(not int($WSize)) {
        exitStatus("Error", "can't check WORD size");
    }
    return ($Cache{"detectWordSize"} = $WSize);
}

sub majorVersion($)
{
    my $V = $_[0];
    return 0 if(not $V);
    my @VParts = split(/\./, $V);
    return $VParts[0];
}

sub cmpVersions($$)
{# compare two versions in dotted-numeric format
    my ($V1, $V2) = @_;
    return 0 if($V1 eq $V2);
    return undef if($V1!~/\A\d+[\.\d+]*\Z/);
    return undef if($V2!~/\A\d+[\.\d+]*\Z/);
    my @V1Parts = split(/\./, $V1);
    my @V2Parts = split(/\./, $V2);
    for (my $i = 0; $i <= $#V1Parts && $i <= $#V2Parts; $i++) {
        return -1 if(int($V1Parts[$i]) < int($V2Parts[$i]));
        return 1 if(int($V1Parts[$i]) > int($V2Parts[$i]));
    }
    return -1 if($#V1Parts < $#V2Parts);
    return 1 if($#V1Parts > $#V2Parts);
    return 0;
}

sub read_ABI_Dump($$)
{
    my ($LibVersion, $Path) = @_;
    return if(not $LibVersion or not -e $Path);
    my $FilePath = "";
    if($Path=~/\.abi\Z/)
    { # input *.abi
        $FilePath = $Path;
    }
    else
    { # input *.abi.tar.gz
        $FilePath = unpackDump($Path);
    }
    if($FilePath!~/\.abi\Z/) {
        exitStatus("Invalid_Dump", "specified ABI dump \'$Path\' is not valid, try to recreate it");
    }
    my $Content = readFile($FilePath);
    if($Path!~/\.abi\Z/)
    { # remove temp file
        unlink($FilePath);
    }
    if($Content!~/};\s*\Z/) {
        exitStatus("Invalid_Dump", "specified ABI dump \'$Path\' is not valid, try to recreate it");
    }
    my $LibraryABI = eval($Content);
    if(not $LibraryABI) {
        exitStatus("Error", "internal error - eval() procedure seem to not working correctly, try to remove 'use strict' and try again");
    }
    # new dumps (>=1.22) have a personal versioning
    my $DumpVersion = $LibraryABI->{"ABI_DUMP_VERSION"};
    my $ToolVersion = $LibraryABI->{"ABI_COMPLIANCE_CHECKER_VERSION"};
    if(not $DumpVersion)
    { # old dumps (<=1.21.6) have been marked by the tool version
        $DumpVersion = $ToolVersion;
    }
    $UsedDump{$LibVersion}{"V"} = $DumpVersion;
    if(majorVersion($DumpVersion) ne majorVersion($ABI_DUMP_VERSION))
    { # should be compatible with dumps of the same major version
        if(cmpVersions($DumpVersion, $ABI_DUMP_VERSION)>0)
        { # Don't know how to parse future dump formats
            exitStatus("Dump_Version", "incompatible version $DumpVersion of specified ABI dump (newer than $ABI_DUMP_VERSION)");
        }
        elsif(cmpVersions($DumpVersion, $TOOL_VERSION)>0 and not $LibraryABI->{"ABI_DUMP_VERSION"})
        { # Don't know how to parse future dump formats
            exitStatus("Dump_Version", "incompatible version $DumpVersion of specified ABI dump (newer than $TOOL_VERSION)");
        }
        if($UseOldDumps)
        {
            if(cmpVersions($DumpVersion, $OLDEST_SUPPORTED_VERSION)<0) {
                exitStatus("Dump_Version", "incompatible version $DumpVersion of specified ABI dump (older than $OLDEST_SUPPORTED_VERSION)");
            }
        }
        else
        {
            my $Msg = "incompatible version $DumpVersion of specified ABI dump (allowed only ".majorVersion($ABI_DUMP_VERSION).".0<=V<=$ABI_DUMP_VERSION)";
            if(cmpVersions($DumpVersion, $OLDEST_SUPPORTED_VERSION)>=0) {
                $Msg .= "\nUse -old-dumps option to use old-version dumps ($OLDEST_SUPPORTED_VERSION<=V<".majorVersion($ABI_DUMP_VERSION).".0)";
            }
            exitStatus("Dump_Version", $Msg);
        }
    }
    if($LibraryABI->{"Mode"} eq "Extended")
    { # --ext option
        $ExtendedCheck = 1;
    }
    if(my $Lang = $LibraryABI->{"Language"})
    {
        $UsedDump{$LibVersion}{"L"} = $Lang;
        setLanguage($LibVersion, $Lang);
    }
    $TypeInfo{$LibVersion} = $LibraryABI->{"TypeInfo"};
    if(not $TypeInfo{$LibVersion})
    { # support for old ABI dumps
        $TypeInfo{$LibVersion} = $LibraryABI->{"TypeDescr"};
    }
    read_Machine_DumpInfo($LibraryABI, $LibVersion);
    $SymbolInfo{$LibVersion} = $LibraryABI->{"SymbolInfo"};
    if(not $SymbolInfo{$LibVersion})
    { # support for old dumps
        $SymbolInfo{$LibVersion} = $LibraryABI->{"FuncDescr"};
    }
    if(not keys(%{$SymbolInfo{$LibVersion}}))
    { # validation of old-version dumps
        if(not $ExtendedCheck) {
            exitStatus("Invalid_Dump", "the input dump d$LibVersion is invalid");
        }
    }
    $Library_Symbol{$LibVersion} = $LibraryABI->{"Symbols"};
    if(not $Library_Symbol{$LibVersion})
    { # support for old dumps
        $Library_Symbol{$LibVersion} = $LibraryABI->{"Interfaces"};
    }
    $DepSymbols{$LibVersion} = $LibraryABI->{"DepSymbols"};
    if(not $DepSymbols{$LibVersion})
    { # support for old dumps
        $DepSymbols{$LibVersion} = $LibraryABI->{"DepInterfaces"};
    }
    if(not $DepSymbols{$LibVersion})
    { # support for old dumps
      # Cannot reconstruct DepSymbols. This may result in false
      # positives if the old dump is for library 2. Not a problem if
      # old dumps are only from old libraries.
        $DepSymbols{$LibVersion} = {};
    }
    $SymVer{$LibVersion} = $LibraryABI->{"SymbolVersion"};
    $Tid_TDid{$LibVersion} = $LibraryABI->{"Tid_TDid"};
    $Descriptor{$LibVersion}{"Version"} = $LibraryABI->{"LibraryVersion"};
    $SkipTypes{$LibVersion} = $LibraryABI->{"SkipTypes"};
    if(not $SkipTypes{$LibVersion})
    { # support for old dumps
        $SkipTypes{$LibVersion} = $LibraryABI->{"OpaqueTypes"};
    }
    $SkipSymbols{$LibVersion} = $LibraryABI->{"SkipSymbols"};
    if(not $SkipSymbols{$LibVersion})
    { # support for old dumps
        $SkipSymbols{$LibVersion} = $LibraryABI->{"SkipInterfaces"};
    }
    if(not $SkipSymbols{$LibVersion})
    { # support for old dumps
        $SkipSymbols{$LibVersion} = $LibraryABI->{"InternalInterfaces"};
    }
    $SkipNameSpaces{$LibVersion} = $LibraryABI->{"SkipNameSpaces"};
    $TargetHeaders{$LibVersion} = $LibraryABI->{"TargetHeaders"};
    foreach my $Path (keys(%{$LibraryABI->{"SkipHeaders"}}))
    {
        my ($CPath, $Type) = classifyPath($Path);
        $SkipHeaders{$LibVersion}{$Type}{$CPath} = $LibraryABI->{"SkipHeaders"}{$Path};
        $SkipHeadersList{$LibVersion}{$Path} = $LibraryABI->{"SkipHeaders"}{$Path};
    }
    read_Headers_DumpInfo($LibraryABI, $LibVersion);
    read_Libs_DumpInfo($LibraryABI, $LibVersion);
    if(not $Descriptor{$LibVersion}{"Libs"})
    { # support for old ABI dumps
        if(cmpVersions($DumpVersion, "2.10.1")<0)
        {
            if(not $TargetHeaders{$LibVersion})
            {
                foreach (keys(%{$Registered_Headers{$LibVersion}})) {
                    $TargetHeaders{$LibVersion}{get_filename($_)}=1;
                }
            }
        }
    }
    $Constants{$LibVersion} = $LibraryABI->{"Constants"};
    $NestedNameSpaces{$LibVersion} = $LibraryABI->{"NameSpaces"};
    if(not $NestedNameSpaces{$LibVersion})
    { # support for old dumps
      # Cannot reconstruct NameSpaces. This may affect design
      # of the compatibility report.
        $NestedNameSpaces{$LibVersion} = {};
    }
    # target system type
    # needed to adopt HTML report
    if(not $DumpSystem)
    { # to use in createSymbolsList(...)
        $OStarget = $LibraryABI->{"Target"};
    }
    # recreate environment
    foreach my $Lib_Name (keys(%{$Library_Symbol{$LibVersion}}))
    {
        foreach my $Interface (keys(%{$Library_Symbol{$LibVersion}{$Lib_Name}}))
        {
            $Symbol_Library{$LibVersion}{$Interface} = $Lib_Name;
            if($Library_Symbol{$LibVersion}{$Lib_Name}{$Interface}<=-1)
            { # data marked as -size in the dump
                $CompleteSignature{$LibVersion}{$Interface}{"Object"} = 1;
            }
            if($COMMON_LANGUAGE{$LibVersion} ne "C++"
            and $Interface=~/\A(_Z|\?)/) {
                setLanguage($LibVersion, "C++");
            }
        }
    }
    my @VFunc = ();
    foreach my $FuncInfoId (keys(%{$SymbolInfo{$LibVersion}}))
    {
        my $MnglName = $SymbolInfo{$LibVersion}{$FuncInfoId}{"MnglName"};
        if(not $MnglName)
        { # C-functions
            next;
        }
        if(not $Symbol_Library{$LibVersion}{$MnglName}
        and not $DepSymbols{$LibVersion}{$MnglName}) {
            push(@VFunc, $MnglName);
        }
    }
    translateSymbols(@VFunc, $LibVersion);
    translateSymbols(keys(%{$Symbol_Library{$LibVersion}}), $LibVersion);
    translateSymbols(keys(%{$DepSymbols{$LibVersion}}), $LibVersion);

    foreach my $TypeDeclId (sort {int($a)<=>int($b)} keys(%{$TypeInfo{$LibVersion}}))
    {
        foreach my $TypeId (sort {int($a)<=>int($b)} keys(%{$TypeInfo{$LibVersion}{$TypeDeclId}}))
        {
            if(defined $TypeInfo{$LibVersion}{$TypeDeclId}{$TypeId}{"BaseClass"})
            { # support for old ABI dumps < 2.0 (ACC 1.22)
                foreach my $BId (keys(%{$TypeInfo{$LibVersion}{$TypeDeclId}{$TypeId}{"BaseClass"}}))
                {
                    if(my $Access = $TypeInfo{$LibVersion}{$TypeDeclId}{$TypeId}{"BaseClass"}{$BId})
                    {
                        if($Access ne "public") {
                            $TypeInfo{$LibVersion}{$TypeDeclId}{$TypeId}{"Base"}{$BId}{"access"} = $Access;
                        }
                    }
                    $TypeInfo{$LibVersion}{$TypeDeclId}{$TypeId}{"Base"}{$BId} = {};
                }
                delete($TypeInfo{$LibVersion}{$TypeDeclId}{$TypeId}{"BaseClass"});
            }
            my %TInfo = %{$TypeInfo{$LibVersion}{$TypeDeclId}{$TypeId}};
            if(defined $TInfo{"Base"})
            {
                foreach (keys(%{$TInfo{"Base"}})) {
                    $Class_SubClasses{$LibVersion}{$_}{$TypeId}=1;
                }
            }
            if($TInfo{"Type"} eq "Typedef")
            {
                my ($BTDid, $BTid) = ($TInfo{"BaseType"}{"TDid"}, $TInfo{"BaseType"}{"Tid"});
                $Typedef_BaseName{$LibVersion}{$TInfo{"Name"}} = $TypeInfo{$LibVersion}{$BTDid}{$BTid}{"Name"};
            }
            if(not $TName_Tid{$LibVersion}{$TInfo{"Name"}})
            { # classes: class (id1), typedef (artificial, id2 > id1)
                $TName_Tid{$LibVersion}{$TInfo{"Name"}} = $TypeId;
            }
        }
    }
    
    $Descriptor{$LibVersion}{"Dump"} = 1;
}

sub read_Machine_DumpInfo($$)
{
    my ($LibraryABI, $LibVersion) = @_;
    if($LibraryABI->{"Arch"}) {
        $CPU_ARCH{$LibVersion} = $LibraryABI->{"Arch"};
    }
    if($LibraryABI->{"WordSize"}) {
        $WORD_SIZE{$LibVersion} = $LibraryABI->{"WordSize"};
    }
    else
    { # support for old dumps
        $WORD_SIZE{$LibVersion} = $LibraryABI->{"SizeOfPointer"};
    }
    if(not $WORD_SIZE{$LibVersion})
    { # support for old dumps (<1.23)
        if(my $Tid = getTypeIdByName("char*", $LibVersion))
        { # size of char*
            $WORD_SIZE{$LibVersion} = get_TypeSize($Tid, $LibVersion);
        }
        else
        {
            my $PSize = 0;
            foreach my $TDid (keys(%{$TypeInfo{$LibVersion}}))
            {
                foreach my $Tid (keys(%{$TypeInfo{$LibVersion}{$TDid}}))
                {
                    if(get_TypeAttr($Tid, $LibVersion, "Type") eq "Pointer")
                    { # any "pointer"-type
                        $PSize = get_TypeSize($Tid, $LibVersion);
                        last;
                    }
                }
                if($PSize) {
                    last;
                }
            }
            if($PSize)
            { # a pointer type size
                $WORD_SIZE{$LibVersion} = $PSize;
            }
            else {
                printMsg("WARNING", "cannot identify a WORD size in the ABI dump (too old format)");
            }
        }
    }
    if($LibraryABI->{"GccVersion"}) {
        $GCC_VERSION{$LibVersion} = $LibraryABI->{"GccVersion"};
    }
}

sub read_Libs_DumpInfo($$)
{
    my ($LibraryABI, $LibVersion) = @_;
    if(keys(%{$Library_Symbol{$LibVersion}})
    and not $DumpAPI) {
        $Descriptor{$LibVersion}{"Libs"} = "OK";
    }
}

sub read_Headers_DumpInfo($$)
{
    my ($LibraryABI, $LibVersion) = @_;
    if(keys(%{$LibraryABI->{"Headers"}})
    and not $DumpAPI) {
        $Descriptor{$LibVersion}{"Headers"} = "OK";
    }
    foreach my $Identity (keys(%{$LibraryABI->{"Headers"}}))
    { # headers info is stored in the old dumps in the different way
        if($UseOldDumps
        and my $Name = $LibraryABI->{"Headers"}{$Identity}{"Name"})
        { # support for old dumps: headers info corrected in 1.22
            $Identity = $Name;
        }
        $Registered_Headers{$LibVersion}{$Identity}{"Identity"} = $Identity;
    }
}

sub find_libs($$$)
{
    my ($Path, $Type, $MaxDepth) = @_;
    # FIXME: correct the search pattern
    return cmd_find($Path, $Type, ".*\\.$LIB_EXT\[0-9.]*", $MaxDepth);
}

sub createDescriptor($$)
{
    my ($LibVersion, $Path) = @_;
    if(not $LibVersion or not $Path
    or not -e $Path) {
        return "";
    }
    if(-d $Path)
    { # directory with headers files and shared objects
        return "
            <version>
                ".$TargetVersion{$LibVersion}."
            </version>

            <headers>
                $Path
            </headers>

            <libs>
                $Path
            </libs>";
    }
    else
    { # files
        if($Path=~/\.xml\Z/i)
        { # standard XML-descriptor
            return readFile($Path);
        }
        elsif(is_header($Path, 2, $LibVersion))
        { # header file
            return "
                <version>
                    ".$TargetVersion{$LibVersion}."
                </version>

                <headers>
                    $Path
                </headers>

                <libs>
                    none
                </libs>";
        }
        elsif(parse_libname($Path, "name", $OStarget))
        { # shared object
            return "
                <version>
                    ".$TargetVersion{$LibVersion}."
                </version>

                <headers>
                    none
                </headers>

                <libs>
                    $Path
                </libs>";
        }
        else
        { # standard XML-descriptor
            return readFile($Path);
        }
    }
}

sub detect_lib_default_paths()
{
    my %LPaths = ();
    if($OSgroup eq "bsd")
    {
        if(my $LdConfig = get_CmdPath("ldconfig")) {
            foreach my $Line (split(/\n/, `$LdConfig -r 2>$TMP_DIR/null`)) {
                if($Line=~/\A[ \t]*\d+:\-l(.+) \=\> (.+)\Z/) {
                    $LPaths{"lib".$1} = $2;
                }
            }
        }
        else {
            printMsg("WARNING", "can't find ldconfig");
        }
    }
    else
    {
        if(my $LdConfig = get_CmdPath("ldconfig"))
        {
            if($SystemRoot and $OSgroup eq "linux")
            { # use host (x86) ldconfig with the target (arm) ld.so.conf
                if(-e $SystemRoot."/etc/ld.so.conf") {
                    $LdConfig .= " -f ".$SystemRoot."/etc/ld.so.conf";
                }
            }
            foreach my $Line (split(/\n/, `$LdConfig -p 2>$TMP_DIR/null`)) {
                if($Line=~/\A[ \t]*([^ \t]+) .* \=\> (.+)\Z/)
                {
                    my ($Name, $Path) = ($1, $2);
                    $Path=~s/[\/]{2,}/\//;
                    $LPaths{$Name} = $Path;
                }
            }
        }
        elsif($OSgroup=~/linux/i) {
            printMsg("WARNING", "can't find ldconfig");
        }
    }
    return \%LPaths;
}

sub detect_bin_default_paths()
{
    my $EnvPaths = $ENV{"PATH"};
    if($OSgroup eq "beos") {
        $EnvPaths.=":".$ENV{"BETOOLS"};
    }
    my $Sep = ($OSgroup eq "windows")?";":":|;";
    foreach my $Path (sort {length($a)<=>length($b)} split(/$Sep/, $EnvPaths))
    {
        $Path = path_format($Path, $OSgroup);
        $Path=~s/[\/\\]+\Z//g;
        next if(not $Path);
        if($SystemRoot
        and $Path=~/\A\Q$SystemRoot\E\//)
        { # do NOT use binaries from target system
            next;
        }
        $DefaultBinPaths{$Path} = 1;
    }
}

sub detect_inc_default_paths()
{
    return () if(not $GCC_PATH);
    my %DPaths = ("Cpp"=>{},"Gcc"=>{},"Inc"=>{});
    writeFile("$TMP_DIR/empty.h", "");
    foreach my $Line (split(/\n/, `$GCC_PATH -v -x c++ -E "$TMP_DIR/empty.h" 2>&1`))
    {# detecting gcc default include paths
        if($Line=~/\A[ \t]*((\/|\w+:\\).+)[ \t]*\Z/)
        {
            my $Path = simplify_path($1);
            $Path=~s/[\/\\]+\Z//g;
            $Path = path_format($Path, $OSgroup);
            if($Path=~/c\+\+|\/g\+\+\//)
            {
                $DPaths{"Cpp"}{$Path}=1;
                if(not defined $MAIN_CPP_DIR
                or get_depth($MAIN_CPP_DIR)>get_depth($Path)) {
                    $MAIN_CPP_DIR = $Path;
                }
            }
            elsif($Path=~/gcc/) {
                $DPaths{"Gcc"}{$Path}=1;
            }
            else
            {
                next if($Path=~/local[\/\\]+include/);
                if($SystemRoot
                and $Path!~/\A\Q$SystemRoot\E(\/|\Z)/)
                { # The GCC include path for user headers is not a part of the system root
                  # The reason: you are not specified the --cross-gcc option or selected a wrong compiler
                  # or it is the internal cross-GCC path like arm-linux-gnueabi/include
                    next;
                }
                $DPaths{"Inc"}{$Path}=1;
            }
        }
    }
    unlink("$TMP_DIR/empty.h");
    return %DPaths;
}

sub detect_default_paths($)
{
    my ($HSearch, $LSearch, $BSearch, $GSearch) = (1, 1, 1, 1);
    my $Search = $_[0];
    if($Search!~/inc/) {
        $HSearch = 0;
    }
    if($Search!~/lib/) {
        $LSearch = 0;
    }
    if($Search!~/bin/) {
        $BSearch = 0;
    }
    if($Search!~/gcc/) {
        $GSearch = 0;
    }
    if(keys(%{$SystemPaths{"include"}}))
    { # <search_headers> section of the XML descriptor
      # do NOT search for systems headers
        $HSearch = 0;
    }
    if(keys(%{$SystemPaths{"lib"}}))
    { # <search_headers> section of the XML descriptor
      # do NOT search for systems headers
        $LSearch = 0;
    }
    foreach my $Type (keys(%{$OS_AddPath{$OSgroup}}))
    { # additional search paths
        next if($Type eq "include" and not $HSearch);
        next if($Type eq "lib" and not $LSearch);
        next if($Type eq "bin" and not $BSearch);
        foreach my $Path (keys(%{$OS_AddPath{$OSgroup}{$Type}}))
        {
            next if(not -d $Path);
            $SystemPaths{$Type}{$Path} = $OS_AddPath{$OSgroup}{$Type}{$Path};
        }
    }
    if($OSgroup ne "windows")
    { # unix-like
        foreach my $Type ("include", "lib", "bin")
        { # automatic detection of system "devel" directories
            next if($Type eq "include" and not $HSearch);
            next if($Type eq "lib" and not $LSearch);
            next if($Type eq "bin" and not $BSearch);
            my ($UsrDir, $RootDir) = ("/usr", "/");
            if($SystemRoot and $Type ne "bin")
            { # 1. search for target headers and libraries
              # 2. use host commands: ldconfig, readelf, etc.
                ($UsrDir, $RootDir) = ("$SystemRoot/usr", $SystemRoot);
            }
            foreach my $Path (cmd_find($RootDir,"d","*$Type*",1)) {
                $SystemPaths{$Type}{$Path} = 1;
            }
            if(-d $RootDir."/".$Type)
            { # if "/lib" is symbolic link
                if($RootDir eq "/") {
                    $SystemPaths{$Type}{"/".$Type} = 1;
                }
                else {
                    $SystemPaths{$Type}{$RootDir."/".$Type} = 1;
                }
            }
            if(-d $UsrDir) {
                foreach my $Path (cmd_find($UsrDir,"d","*$Type*",1)) {
                    $SystemPaths{$Type}{$Path} = 1;
                }
                if(-d $UsrDir."/".$Type)
                { # if "/usr/lib" is symbolic link
                    $SystemPaths{$Type}{$UsrDir."/".$Type} = 1;
                }
            }
        }
    }
    if($BSearch)
    {
        detect_bin_default_paths();
        foreach my $Path (keys(%DefaultBinPaths)) {
            $SystemPaths{"bin"}{$Path} = $DefaultBinPaths{$Path};
        }
    }
    # check environment variables
    if($OSgroup eq "beos")
    {
        foreach (keys(%{$SystemPaths{"bin"}}))
        {
            if($_ eq ".") {
                next;
            }
            foreach my $Path (cmd_find($_, "d", "bin", ""))
            { # search for /boot/develop/abi/x86/gcc4/tools/gcc-4.4.4-haiku-101111/bin/
                $SystemPaths{"bin"}{$Path} = 1;
            }
        }
        if($HSearch)
        {
            foreach my $Path (split(/:|;/, $ENV{"BEINCLUDES"}))
            {
                if(is_abs($Path)) {
                    $DefaultIncPaths{$Path} = 1;
                }
            }
        }
        if($LSearch)
        {
            foreach my $Path (split(/:|;/, $ENV{"BELIBRARIES"}), split(/:|;/, $ENV{"LIBRARY_PATH"}))
            {
                if(is_abs($Path)) {
                    $DefaultLibPaths{$Path} = 1;
                }
            }
        }
    }
    if($LSearch)
    { # using linker to get system paths
        if(my $LPaths = detect_lib_default_paths())
        { # unix-like
            foreach my $Name (keys(%{$LPaths}))
            {
                if($SystemRoot
                and $LPaths->{$Name}!~/\A\Q$SystemRoot\E\//)
                { # wrong ldconfig configuration
                  # check your <sysroot>/etc/ld.so.conf
                    next;
                }
                $DyLib_DefaultPath{$Name} = $LPaths->{$Name};
                $DefaultLibPaths{get_dirname($LPaths->{$Name})} = 1;
            }
        }
        foreach my $Path (keys(%DefaultLibPaths)) {
            $SystemPaths{"lib"}{$Path} = $DefaultLibPaths{$Path};
        }
    }
    if($BSearch)
    {
        if($CrossGcc)
        { # --cross-gcc=arm-linux-gcc
            if(-e $CrossGcc)
            { # absolute or relative path
                $GCC_PATH = get_abs_path($CrossGcc);
            }
            elsif($CrossGcc!~/\// and get_CmdPath($CrossGcc))
            { # command name
                $GCC_PATH = $CrossGcc;
            }
            else {
                exitStatus("Access_Error", "can't access \'$CrossGcc\'");
            }
            if($GCC_PATH=~/\s/) {
                $GCC_PATH = "\"".$GCC_PATH."\"";
            }
        }
    }
    if($GSearch)
    { # GCC path and default include dirs
        if(not $CrossGcc) {
            $GCC_PATH = get_CmdPath("gcc");
        }
        if(not $GCC_PATH) {
            exitStatus("Not_Found", "can't find GCC>=3.0 in PATH");
        }
        if(not $CheckObjectsOnly_Opt)
        {
            if(my $GCC_Ver = get_dumpversion($GCC_PATH))
            {
                my $GccTarget = get_dumpmachine($GCC_PATH);
                printMsg("INFO", "Using GCC $GCC_Ver ($GccTarget)");
                if($GccTarget=~/symbian/)
                {
                    $OStarget = "symbian";
                    $LIB_EXT = $OS_LibExt{$LIB_TYPE}{$OStarget};
                }
            }
            else {
                exitStatus("Error", "something is going wrong with the GCC compiler");
            }
        }
        if(not $NoStdInc)
        { # do NOT search in GCC standard paths
            my %DPaths = detect_inc_default_paths();
            %DefaultCppPaths = %{$DPaths{"Cpp"}};
            %DefaultGccPaths = %{$DPaths{"Gcc"}};
            %DefaultIncPaths = %{$DPaths{"Inc"}};
            foreach my $Path (keys(%DefaultIncPaths)) {
                $SystemPaths{"include"}{$Path} = $DefaultIncPaths{$Path};
            }
        }
    }
    if($HSearch)
    { # user include paths
        if(-d $SystemRoot."/usr/include") {
            $UserIncPath{$SystemRoot."/usr/include"}=1;
        }
    }
}

sub getLIB_EXT($)
{
    my $Target = $_[0];
    if(my $Ext = $OS_LibExt{$LIB_TYPE}{$Target}) {
        return $Ext;
    }
    return $OS_LibExt{$LIB_TYPE}{"default"};
}

sub getAR_EXT($)
{
    my $Target = $_[0];
    if(my $Ext = $OS_Archive{$Target}) {
        return $Ext;
    }
    return $OS_Archive{"default"};
}

sub get_dumpversion($)
{
    my $Cmd = $_[0];
    return "" if(not $Cmd);
    if($Cache{"get_dumpversion"}{$Cmd}) {
        return $Cache{"get_dumpversion"}{$Cmd};
    }
    my $V = `$Cmd -dumpversion 2>$TMP_DIR/null`;
    chomp($V);
    return ($Cache{"get_dumpversion"}{$Cmd} = $V);
}

sub get_dumpmachine($)
{
    my $Cmd = $_[0];
    return "" if(not $Cmd);
    if($Cache{"get_dumpmachine"}{$Cmd}) {
        return $Cache{"get_dumpmachine"}{$Cmd};
    }
    my $Machine = `$Cmd -dumpmachine 2>$TMP_DIR/null`;
    chomp($Machine);
    return ($Cache{"get_dumpmachine"}{$Cmd} = $Machine);
}

sub check_command($)
{
    my $Cmd = $_[0];
    return "" if(not $Cmd);
    my @Options = (
        "--version",
        "-help"
    );
    foreach my $Opt (@Options)
    {
        my $Info = `$Cmd $Opt 2>$TMP_DIR/null`;
        if($Info) {
            return 1;
        }
    }
    return 0;
}

sub check_gcc_version($$)
{
    my ($Cmd, $Req_V) = @_;
    return 0 if(not $Cmd or not $Req_V);
    my $Gcc_V = get_dumpversion($Cmd);
    $Gcc_V=~s/(-|_)[a-z_]+.*\Z//; # remove suffix (like "-haiku-100818")
    if(cmpVersions($Gcc_V, $Req_V)>=0) {
        return $Cmd;
    }
    return "";
}

sub get_depth($)
{
    if(defined $Cache{"get_depth"}{$_[0]}) {
        return $Cache{"get_depth"}{$_[0]}
    }
    return ($Cache{"get_depth"}{$_[0]} = ($_[0]=~tr![\/\\]|\:\:!!));
}

sub find_gcc_cxx_headers($)
{
    my $LibVersion = $_[0];
    return if($Cache{"find_gcc_cxx_headers"});# this function should be called once
    # detecting system header paths
    foreach my $Path (sort {get_depth($b) <=> get_depth($a)} keys(%DefaultGccPaths))
    {
        foreach my $HeaderPath (sort {get_depth($a) <=> get_depth($b)} cmd_find($Path,"f","",""))
        {
            my $FileName = get_filename($HeaderPath);
            next if($DefaultGccHeader{$FileName});
            $DefaultGccHeader{$FileName} = $HeaderPath;
        }
    }
    if($COMMON_LANGUAGE{$LibVersion} eq "C++" and not $STDCXX_TESTING)
    {
        foreach my $CppDir (sort {get_depth($b)<=>get_depth($a)} keys(%DefaultCppPaths))
        {
            my @AllCppHeaders = cmd_find($CppDir,"f","","");
            foreach my $Path (sort {get_depth($a)<=>get_depth($b)} @AllCppHeaders)
            {
                my $FileName = get_filename($Path);
                next if($DefaultCppHeader{$FileName});
                $DefaultCppHeader{$FileName} = $Path;
            }
        }
    }
    $Cache{"find_gcc_cxx_headers"} = 1;
}

sub parse_libname($$$)
{
    my ($Name, $Type, $Target) = @_;
    if($Target eq "symbian") {
        return parse_libname_symbian($Name, $Type);
    }
    elsif($Target eq "windows") {
        return parse_libname_windows($Name, $Type);
    }
    my $Ext = getLIB_EXT($Target);
    if($Name=~/((((lib|).+?)([\-\_][\d\-\.\_]+|))\.$Ext)(\.(.+)|)\Z/)
    { # libSDL-1.2.so.0.7.1
      # libwbxml2.so.0.0.18
        if($Type eq "name")
        { # libSDL-1.2
          # libwbxml2
            return $2;
        }
        elsif($Type eq "name+ext")
        { # libSDL-1.2.so
          # libwbxml2.so
            return $1;
        }
        elsif($Type eq "version")
        {
            if($7 ne "")
            { # 0.7.1
                return $7;
            }
            else
            { # libc-2.5.so (=>2.5 version)
                my $MV = $5;
                $MV=~s/\A[\-\_]+//g;
                return $MV;
            }
        }
        elsif($Type eq "short")
        { # libSDL
          # libwbxml2
            return $3;
        }
        elsif($Type eq "shortest")
        { # SDL
          # wbxml
            return shortest_name($3);
        }
    }
    return "";# error
}

sub parse_libname_symbian($$)
{
    my ($Name, $Type) = @_;
    my $Ext = getLIB_EXT("symbian");
    if($Name=~/(((.+?)(\{.+\}|))\.$Ext)\Z/)
    { # libpthread{00010001}.dso
        if($Type eq "name")
        { # libpthread{00010001}
            return $2;
        }
        elsif($Type eq "name+ext")
        { # libpthread{00010001}.dso
            return $1;
        }
        elsif($Type eq "version")
        { # 00010001
            my $V = $4;
            $V=~s/\{(.+)\}/$1/;
            return $V;
        }
        elsif($Type eq "short")
        { # libpthread
            return $3;
        }
        elsif($Type eq "shortest")
        { # pthread
            return shortest_name($3);
        }
    }
    return "";# error
}

sub parse_libname_windows($$)
{
    my ($Name, $Type) = @_;
    my $Ext = getLIB_EXT("windows");
    if($Name=~/((.+?)\.$Ext)\Z/)
    { # netapi32.dll
        if($Type eq "name")
        { # netapi32
            return $2;
        }
        elsif($Type eq "name+ext")
        { # netapi32.dll
            return $1;
        }
        elsif($Type eq "version")
        { # DLL version embedded
          # at binary-level
            return "";
        }
        elsif($Type eq "short")
        { # netapi32
            return $2;
        }
        elsif($Type eq "shortest")
        { # netapi
            return shortest_name($2);
        }
    }
    return "";# error
}

sub shortest_name($)
{
    my $Name = $_[0];
    # remove prefix
    $Name=~s/\A(lib|open)//;
    # remove suffix
    $Name=~s/[\W\d_]+\Z//i;
    $Name=~s/([a-z]{2,})(lib)\Z/$1/i;
    return $Name;
}

sub getPrefix($)
{
    my $Str = $_[0];
    if($Str=~/\A(Get|get|Set|set)([A-Z]|_)/)
    {# GetError
        return "";
    }
    if($Str=~/\A([_]*[A-Z][a-z]{1,5})[A-Z]/)
    {# XmuValidArea: Xmu
        return $1;
    }
    elsif($Str=~/\A([_]*[a-z]+)[A-Z]/)
    {# snfReadFont: snf
        return $1;
    }
    elsif($Str=~/\A([_]*[A-Z]{2,})[A-Z][a-z]+([A-Z][a-z]+|\Z)/)
    {# XRRTimes: XRR
        return $1;
    }
    elsif($Str=~/\A([_]*[a-z0-9]{2,}_)[a-z]+/i)
    {# alarm_event_add: alarm_
        return $1;
    }
    elsif($Str=~/\A(([a-z])\2{1,})/i)
    {# ffopen
        return $1;
    }
    else {
        return "";
    }
}

sub problem_title($)
{
    if($_[0]==1)  {
        return "1 problem";
    }
    else  {
        return $_[0]." problems";
    }
}

sub warning_title($)
{
    if($_[0]==1)  {
        return "1 warning";
    }
    else  {
        return $_[0]." warnings";
    }
}

sub createSymbolsList($$$$$)
{
    my ($DPath, $SaveTo, $LName, $LVersion, $ArchName) = @_;
    read_ABI_Dump(1, $DPath);
    if(not $CheckObjectsOnly) {
        prepareInterfaces(1);
    }
    my %SymbolHeaderLib = ();
    my $Total = 0;
    # Get List
    foreach my $Symbol (sort keys(%{$CompleteSignature{1}}))
    {
        if(not link_symbol($Symbol, 1, "-Deps"))
        {# skip src only and all external functions
            next;
        }
        if(not symbolFilter($Symbol, 1, "Public"))
        { # skip other symbols
            next;
        }
        my $HeaderName = $CompleteSignature{1}{$Symbol}{"Header"};
        if(not $HeaderName)
        {# skip src only and all external functions
            next;
        }
        my $DyLib = $Symbol_Library{1}{$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}}))
        {
            if($HeaderName and $DyLib) {
                $SYMBOLS_LIST .= "<span class='h_name'>$HeaderName</span>, <span class='lib_name'>$DyLib</span><br/>\n";
            }
            elsif($DyLib) {
                $SYMBOLS_LIST .= "<span class='lib_name'>$DyLib</span><br/>\n";
            }
            elsif($HeaderName) {
                $SYMBOLS_LIST .= "<span class='h_name'>$HeaderName</span><br/>\n";
            }
            my %NS_Symbol = ();
            foreach my $Symbol (keys(%{$SymbolHeaderLib{$HeaderName}{$DyLib}})) {
                $NS_Symbol{get_IntNameSpace($Symbol, 1)}{$Symbol} = 1;
            }
            foreach my $NameSpace (sort keys(%NS_Symbol))
            {
                $SYMBOLS_LIST .= ($NameSpace)?"<span class='ns_title'>namespace</span> <span class='ns'>$NameSpace</span>"."<br/>\n":"";
                my @SortedInterfaces = sort {lc(get_Signature($a, 1)) cmp lc(get_Signature($b, 1))} keys(%{$NS_Symbol{$NameSpace}});
                foreach my $Symbol (@SortedInterfaces)
                {
                    my $SubReport = "";
                    my $Signature = get_Signature($Symbol, 1);
                    if($NameSpace) {
                        $Signature=~s/(\W|\A)\Q$NameSpace\E\:\:(\w)/$1$2/g;
                    }
                    if($Symbol=~/\A(_Z|\?)/)
                    {
                        if($Signature) {
                            $SubReport = insertIDs($ContentSpanStart.highLight_Signature_Italic_Color($Signature).$ContentSpanEnd."<br/>\n".$ContentDivStart."<span class='mangled'>[ symbol: <b>$Symbol</b> ]</span><br/><br/>".$ContentDivEnd."\n");
                        }# report_added
                        else {
                            $SubReport = "<span class='iname'>".$Symbol."</span><br/>\n";
                        }
                    }
                    else
                    {
                        if($Signature) {
                            $SubReport = "<span class='iname'>".highLight_Signature_Italic_Color($Signature)."</span><br/>\n";
                        }
                        else {
                            $SubReport = "<span class='iname'>".$Symbol."</span><br/>\n";
                        }
                    }
                    $SYMBOLS_LIST .= $SubReport;
                }
            }
            $SYMBOLS_LIST .= "<br/>\n";
        }
    }
    # Clear Info
    (%TypeInfo, %SymbolInfo, %Library_Symbol,
    %DepSymbols, %SymVer, %Tid_TDid, %SkipTypes,
    %SkipSymbols, %NestedNameSpaces, %ClassMethods,
    %AllocableClass, %ClassToId, %CompleteSignature,
    %SkipNameSpaces, %Symbol_Library) = ();
    ($Content_Counter, $ContentID) = (0, 0);
    # Print Report
    my $CssStyles = readStyles("SymbolsList.css");
    $SYMBOLS_LIST = "<a name='Top'></a>".$SYMBOLS_LIST."<a style='font-size:11px;' href='#Top'>to the top</a><br/>\n";
    my $Title = "$LName: public symbols";
    my $Keywords = "$LName, API, symbols";
    my $Description = "List of symbols in $LName ($LVersion) on ".showArch($ArchName);
    $SYMBOLS_LIST = composeHTML_Head($Title, $Keywords, $Description, $CssStyles."\n".$JScripts)."
    <body><div>\n$SYMBOLS_LIST</div>
    <br/><br/><hr/>\n".getReportFooter($LName)."
    <div style='height:999px;'></div></body></html>";
    writeFile($SaveTo, $SYMBOLS_LIST);
}

sub readStyles($)
{
    my $Name = $_[0];
    my $Path = $MODULES_DIR."/Internals/Styles/".$Name;
    if(not -f $Path) {
        exitStatus("Module_Error", "can't access \'$Path\'");
    }
    my $Styles = readFile($Path);
    return "<style type=\"text/css\">\n".$Styles."\n</style>";
}

sub is_target_lib($)
{
    my $LName = $_[0];
    if($TargetLibraryName
    and $LName!~/\Q$TargetLibraryName\E/) {
        return 0;
    }
    if(keys(%TargetLibs)
    and not $TargetLibs{$LName}
    and not $TargetLibs{parse_libname($LName, "name+ext", $OStarget)}) {
        return 0;
    }
    return 1;
}

sub is_target_header($)
{ # --header, --headers-list
    if(keys(%{$TargetHeaders{1}})
    or keys(%{$TargetHeaders{2}}))
    {
        if(not $TargetHeaders{1}{$_[0]}
        and not $TargetHeaders{2}{$_[0]})
        {
            return 0;
        }
    }
    return 1;
}

sub checkVersionNum($$)
{
    my ($LibVersion, $Path) = @_;
    if(my $VerNum = $TargetVersion{$LibVersion}) {
        return $VerNum;
    }
    my $UsedAltDescr = 0;
    foreach my $Part (split(/\s*,\s*/, $Path))
    {# try to get version string from file path
        next if($Part=~/\.xml\Z/i);
        next if(isDump($Part));
        if(parse_libname($Part, "version", $OStarget)
        or is_header($Part, 2, $LibVersion) or -d $Part)
        {
            $UsedAltDescr = 1;
            if(my $VerNum = readStringVersion($Part))
            {
                $TargetVersion{$LibVersion} = $VerNum;
                if($DumpAPI) {
                    printMsg("WARNING", "setting version number to $VerNum (use -vnum <num> option to change it)");
                }
                else {
                    printMsg("WARNING", "setting ".($LibVersion==1?"1st":"2nd")." version number to \"$VerNum\" (use -v$LibVersion <num> option to change it)");
                }
                return $TargetVersion{$LibVersion};
            }
        }
    }
    if($UsedAltDescr)
    {
        if($DumpAPI) {
            exitStatus("Error", "version number is not set (use -vnum <num> option)");
        }
        else {
            exitStatus("Error", ($LibVersion==1?"1st":"2nd")." version number is not set (use -v$LibVersion <num> option)");
        }
    }
}

sub readStringVersion($)
{
    my $Str = $_[0];
    return "" if(not $Str);
    $Str=~s/\Q$TargetLibraryName\E//g;
    if($Str=~/(\/|\\|\w|\A)[\-\_]*(\d+[\d\.\-]+\d+|\d+)/)
    {# .../libssh-0.4.0/...
        return $2;
    }
    elsif(my $V = parse_libname($Str, "version", $OStarget)) {
        return $V;
    }
    return "";
}

sub readLibs($)
{
    my $LibVersion = $_[0];
    if($OStarget eq "windows")
    { # dumpbin.exe will crash
        # without VS Environment
        check_win32_env();
    }
    getSymbols($LibVersion);
    translateSymbols(keys(%{$Symbol_Library{$LibVersion}}), $LibVersion);
    translateSymbols(keys(%{$DepSymbols{$LibVersion}}), $LibVersion);
}

sub dump_sorting($)
{
    my $hash = $_[0];
    return [] if(not $hash or not keys(%{$hash}));
    if((keys(%{$hash}))[0]=~/\A\d+\Z/) {
        return [sort {int($a) <=> int($b)} keys(%{$hash})];
    }
    else {
        return [sort {$a cmp $b} keys(%{$hash})];
    }
}

sub printMsg($$)
{
    my ($Type, $Msg) = @_;
    if($Type!~/\AINFO/) {
        $Msg = $Type.": ".$Msg;
    }
    if($Type!~/_C\Z/) {
        $Msg .= "\n";
    }
    if($Quiet)
    { # --quiet option
        appendFile($COMMON_LOG_PATH, $Msg);
    }
    else
    {
        if($Type eq "ERROR") {
            print STDERR $Msg;
        }
        else {
            print $Msg;
        }
    }
}

sub exitStatus($$)
{
    my ($Code, $Msg) = @_;
    printMsg("ERROR", $Msg);
    exit($ERROR_CODE{$Code});
}

sub exitReport()
{ # the tool has run without any errors
    printReport();
    if($COMPILE_ERRORS)
    { # errors in headers may add false positives/negatives
        exit($ERROR_CODE{"Compile_Error"});
    }
    if($RESULT{"Problems"}) {
        exit($ERROR_CODE{"Incompatible"});
    }
    else {
        exit($ERROR_CODE{"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 printReport()
{
    printMsg("INFO", "creating compatibility report ...");
    my $ReportPath = $OutputReportPath;
    if(not $ReportPath) {
        $ReportPath = "compat_reports/$TargetLibraryName/".$Descriptor{1}{"Version"}."_to_".$Descriptor{2}{"Version"}."/abi_compat_report.$ReportFormat";
    }
    createHtmlReport($ReportPath);
    if($ListAffected)
    { # --list-affected
        my $List = "";
        foreach (keys(%{$TotalAffected{"Binary"}}))
        {
            if($StrictCompat and $TotalAffected{"Binary"}{$_} eq "Low")
            { # skip "Low"-severity problems
                next;
            }
            $List .= "$_\n";
        }
        writeFile(get_dirname($ReportPath)."/abi_affected.txt", $List);
    }
    if($RESULT{"Problems"}) {
        printMsg("INFO", "result: INCOMPATIBLE ".$RESULT{"Affected"}."\% (total problems: ".$RESULT{"Problems"}.", warnings: ".$RESULT{"Warnings"}.")");
    }
    else {
        printMsg("INFO", "result: COMPATIBLE (total problems: ".$RESULT{"Problems"}.", warnings: ".$RESULT{"Warnings"}.")");
    }
    if($StdOut) {
        printMsg("INFO", "compatibility report has been generated to stdout");
    }
    else {
        printMsg("INFO", "see detailed report:\n  $ReportPath");
    }
}

sub check_win32_env()
{
    if(not $ENV{"DevEnvDir"}
    or not $ENV{"LIB"}) {
        exitStatus("Error", "can't start without VS environment (vsvars32.bat)");
    }
}

sub create_ABI_Dump()
{
    if(not -e $DumpAPI) {
        exitStatus("Access_Error", "can't access \'$DumpAPI\'");
    }
    # check the archive utilities
    if($OSgroup eq "windows")
    { # using zip
        my $ZipCmd = get_CmdPath("zip");
        if(not $ZipCmd) {
            exitStatus("Not_Found", "can't find \"zip\"");
        }
    }
    else
    { # using tar and gzip
        my $TarCmd = get_CmdPath("tar");
        if(not $TarCmd) {
            exitStatus("Not_Found", "can't find \"tar\"");
        }
        my $GzipCmd = get_CmdPath("gzip");
        if(not $GzipCmd) {
            exitStatus("Not_Found", "can't find \"gzip\"");
        }
    }
    my @DParts = split(/\s*,\s*/, $DumpAPI);
    foreach my $Part (@DParts)
    {
        if(not -e $Part) {
            exitStatus("Access_Error", "can't access \'$Part\'");
        }
    }
    checkVersionNum(1, $DumpAPI);
    foreach my $Part (@DParts)
    {
        if(isDump($Part)) {
            read_ABI_Dump(1, $Part);
        }
        else {
            readDescriptor(1, createDescriptor(1, $Part));
        }
    }
    initLogging(1);
    detect_default_paths("inc|lib|bin|gcc"); # complete analysis
    if(not $CheckHeadersOnly) {
        readLibs(1);
    }
    if($CheckHeadersOnly) {
        setLanguage(1, "C++");
    }
    if(not $CheckObjectsOnly) {
        searchForHeaders(1);
    }
    $WORD_SIZE{1} = detectWordSize();
    if($Descriptor{1}{"Headers"}
    and not $Descriptor{1}{"Dump"}) {
        readHeaders(1);
    }
    if($ExtendedCheck)
    { # --ext option
        addExtension(1);
    }
    formatDump(1);
    if(not keys(%{$SymbolInfo{1}}))
    { # check if created dump is valid
        if(not $ExtendedCheck and not $CheckObjectsOnly)
        {
            if($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");
            }
        }
    }
    my %HeadersInfo = ();
    foreach my $HPath (keys(%{$Registered_Headers{1}}))
    { # headers info stored without paths in the dump
        $HeadersInfo{$Registered_Headers{1}{$HPath}{"Identity"}} = $Registered_Headers{1}{$HPath}{"Pos"};
    }
    printMsg("INFO", "creating library ABI dump ...");
    my %LibraryABI = (
        "TypeInfo" => $TypeInfo{1},
        "SymbolInfo" => $SymbolInfo{1},
        "Symbols" => $Library_Symbol{1},
        "DepSymbols" => $DepSymbols{1},
        "SymbolVersion" => $SymVer{1},
        "LibraryVersion" => $Descriptor{1}{"Version"},
        "LibraryName" => $TargetLibraryName,
        "Language" => $COMMON_LANGUAGE{1},
        "Tid_TDid" => $Tid_TDid{1},
        "SkipTypes" => $SkipTypes{1},
        "SkipSymbols" => $SkipSymbols{1},
        "SkipNameSpaces" => $SkipNameSpaces{1},
        "SkipHeaders" => $SkipHeadersList{1},
        "TargetHeaders" => $TargetHeaders{1},
        "Headers" => \%HeadersInfo,
        "Constants" => $Constants{1},
        "NameSpaces" => $NestedNameSpaces{1},
        "Target" => $OStarget,
        "Arch" => getArch(1),
        "WordSize" => $WORD_SIZE{1},
        "GccVersion" => get_dumpversion($GCC_PATH),
        "ABI_DUMP_VERSION" => $ABI_DUMP_VERSION,
        "ABI_COMPLIANCE_CHECKER_VERSION" => $TOOL_VERSION
    );
    if($ExtendedCheck)
    { # --ext option
        $LibraryABI{"Mode"} = "Extended";
    }
    if($StdOut)
    { # --stdout option
        print STDOUT Dumper(\%LibraryABI);
        printMsg("INFO", "ABI dump has been generated to stdout");
        return;
    }
    else
    { # write to gzipped file
        my $DumpPath = "abi_dumps/$TargetLibraryName/".$TargetLibraryName."_".$Descriptor{1}{"Version"}.".abi.".$AR_EXT;
        if($OutputDumpPath)
        { # user defined path
            $DumpPath = $OutputDumpPath;
        }
        if(not $DumpPath=~s/\Q.$AR_EXT\E\Z//g) {
            exitStatus("Error", "the dump path (-dump-path option) should be the path to a *.$AR_EXT file");
        }
        my ($DDir, $DName) = separate_path($DumpPath);
        my $DPath = $TMP_DIR."/".$DName;
        mkpath($DDir);
        writeFile($DPath, Dumper(\%LibraryABI));
        if(not -s $DPath) {
            exitStatus("Error", "can't create ABI dump because something is going wrong with the Data::Dumper module");
        }
        my $Pkg = createArchive($DPath, $DDir);
        printMsg("INFO", "library ABI has been dumped to:\n  $Pkg");
        printMsg("INFO", "you can transfer this dump everywhere and use instead of the ".$Descriptor{1}{"Version"}." version descriptor");
    }
}

sub quickEmptyReports()
{ # Quick "empty" reports
  # 4 times faster than merging equal dumps
  # NOTE: the dump contains the "LibraryVersion" attribute
  # if you change the version, then your dump will be different
  # OVERCOME: use -v1 and v2 options for comparing dumps
  # and don't change version in the XML descriptor (and dumps)
  # OVERCOME 2: separate meta info from the dumps in ACC 2.0
    if(-s $Descriptor{1}{"Path"} == -s $Descriptor{2}{"Path"})
    {
        my $FilePath1 = unpackDump($Descriptor{1}{"Path"});
        my $FilePath2 = unpackDump($Descriptor{2}{"Path"});
        if($FilePath1 and $FilePath2)
        {
            my $Content = readFile($FilePath1);
            if($Content eq readFile($FilePath2))
            {
                # read a number of headers, libs, symbols and types
                my $ABIdump = eval($Content);
                if(not $ABIdump) {
                    exitStatus("Error", "internal error");
                }
                if(not $ABIdump->{"TypeInfo"})
                {# support for old dumps
                    $ABIdump->{"TypeInfo"} = $ABIdump->{"TypeDescr"};
                }
                if(not $ABIdump->{"SymbolInfo"})
                {# support for old dumps
                    $ABIdump->{"SymbolInfo"} = $ABIdump->{"FuncDescr"};
                }
                read_Headers_DumpInfo($ABIdump, 1);
                read_Libs_DumpInfo($ABIdump, 1);
                read_Machine_DumpInfo($ABIdump, 1);
                read_Machine_DumpInfo($ABIdump, 2);
                %CheckedTypes = %{$ABIdump->{"TypeInfo"}};
                %CheckedSymbols = %{$ABIdump->{"SymbolInfo"}};
                $Descriptor{1}{"Version"} = $TargetVersion{1}?$TargetVersion{1}:$ABIdump->{"LibraryVersion"};
                $Descriptor{2}{"Version"} = $TargetVersion{2}?$TargetVersion{2}:$ABIdump->{"LibraryVersion"};
                exitReport();
            }
        }
    }
}

sub initLogging($)
{
    my $LibVersion = $_[0];
    # create log directory
    my ($LOG_DIR, $LOG_FILE) = ("logs/$TargetLibraryName/".$Descriptor{$LibVersion}{"Version"}, "log.txt");
    if($OutputLogPath{$LibVersion})
    { # user-defined by -log-path option
        ($LOG_DIR, $LOG_FILE) = separate_path($OutputLogPath{$LibVersion});
    }
    if($LogMode ne "n") {
        mkpath($LOG_DIR);
    }
    $LOG_PATH{$LibVersion} = get_abs_path($LOG_DIR)."/".$LOG_FILE;
    resetLogging($LibVersion);
    if($Debug)
    { # debug directory
        $DEBUG_PATH{$LibVersion} = "debug/$TargetLibraryName/".$Descriptor{$LibVersion}{"Version"};
        rmtree($DEBUG_PATH{$LibVersion});
    }
}

sub writeLog($$)
{
    my ($LibVersion, $Msg) = @_;
    if($LogMode ne "n") {
        appendFile($LOG_PATH{$LibVersion}, $Msg);
    }
}

sub resetLogging($)
{
    my $LibVersion = $_[0];
    if($LogMode!~/a|n/)
    { # remove old log
        unlink($LOG_PATH{$LibVersion});
    }
}

sub printErrorLog($)
{
    my $LibVersion = $_[0];
    if($LogMode ne "n") {
        printMsg("ERROR", "see log for details:\n  ".$LOG_PATH{$LibVersion}."\n");
    }
}

sub isDump($)
{
    if(get_filename($_[0])=~/\A(.+)\.abi(\Q.tar.gz\E|\Q.zip\E|)\Z/)
    { # returns a name of package
        return $1;
    }
    return 0;
}

sub compareAPIs()
{
    # read input XML descriptors or ABI dumps
    if(not $Descriptor{1}{"Path"}) {
        exitStatus("Error", "-d1 option is not specified");
    }
    my @DParts1 = split(/\s*,\s*/, $Descriptor{1}{"Path"});
    foreach my $Part (@DParts1)
    {
        if(not -e $Part) {
            exitStatus("Access_Error", "can't access \'$Part\'");
        }
    }
    if(not $Descriptor{2}{"Path"}) {
        exitStatus("Error", "-d2 option is not specified");
    }
    my @DParts2 = split(/\s*,\s*/, $Descriptor{2}{"Path"});
    foreach my $Part (@DParts2)
    {
        if(not -e $Part) {
            exitStatus("Access_Error", "can't access \'$Part\'");
        }
    }
    detect_default_paths("bin"); # to extract dumps
    if($#DParts1==0 and $#DParts2==0
    and isDump($Descriptor{1}{"Path"})
    and isDump($Descriptor{2}{"Path"}))
    { # optimization: equal ABI dumps
        quickEmptyReports();
    }
    checkVersionNum(1, $Descriptor{1}{"Path"});
    checkVersionNum(2, $Descriptor{2}{"Path"});
    printMsg("INFO", "preparation, please wait ...");
    foreach my $Part (@DParts1)
    {
        if(isDump($Part)) {
            read_ABI_Dump(1, $Part);
        }
        else {
            readDescriptor(1, createDescriptor(1, $Part));
        }
    }
    foreach my $Part (@DParts2)
    {
        if(isDump($Part)) {
            read_ABI_Dump(2, $Part);
        }
        else {
            readDescriptor(2, createDescriptor(2, $Part));
        }
    }
    initLogging(1);
    initLogging(2);
    # check consistency
    if(not $Descriptor{1}{"Headers"}
    and not $Descriptor{1}{"Libs"}) {
        exitStatus("Error", "descriptor d1 does not contain both header files and libraries info");
    }
    if(not $Descriptor{2}{"Headers"}
    and not $Descriptor{2}{"Libs"}) {
        exitStatus("Error", "descriptor d2 does not contain both header files and libraries info");
    }
    if($Descriptor{1}{"Headers"} and not $Descriptor{1}{"Libs"}
    and not $Descriptor{2}{"Headers"} and $Descriptor{2}{"Libs"}) {
        exitStatus("Error", "can't compare headers with $SLIB_TYPE libraries");
    }
    elsif(not $Descriptor{1}{"Headers"} and $Descriptor{1}{"Libs"}
    and $Descriptor{2}{"Headers"} and not $Descriptor{2}{"Libs"}) {
        exitStatus("Error", "can't compare $SLIB_TYPE libraries with headers");
    }
    if(not $Descriptor{1}{"Headers"}) {
        if($CheckHeadersOnly_Opt) {
            exitStatus("Error", "can't find header files info in descriptor d1");
        }
    }
    if(not $Descriptor{2}{"Headers"}) {
        if($CheckHeadersOnly_Opt) {
            exitStatus("Error", "can't find header files info in descriptor d2");
        }
    }
    if(not $Descriptor{1}{"Headers"}
    or not $Descriptor{2}{"Headers"}) {
        if(not $CheckObjectsOnly_Opt) {
            printMsg("WARNING", "comparing $SLIB_TYPE libraries only");
            $CheckObjectsOnly = 1;
        }
    }
    if(not $Descriptor{1}{"Libs"}) {
        if($CheckObjectsOnly_Opt) {
            exitStatus("Error", "can't find $SLIB_TYPE libraries info in descriptor d1");
        }
    }
    if(not $Descriptor{2}{"Libs"}) {
        if($CheckObjectsOnly_Opt) {
            exitStatus("Error", "can't find $SLIB_TYPE libraries info in descriptor d2");
        }
    }
    if(not $Descriptor{1}{"Libs"}
    or not $Descriptor{2}{"Libs"})
    { # comparing standalone header files
      # comparing ABI dumps created with --headers-only
        if(not $CheckHeadersOnly_Opt)
        {
            printMsg("WARNING", "checking headers only");
            $CheckHeadersOnly = 1;
        }
    }
    if($UseDumps)
    { # --use-dumps
      # parallel processing
        my $pid = fork();
        if($pid)
        { # dump on two CPU cores
            my @PARAMS = ("-dump", $Descriptor{1}{"Path"}, "-l", $TargetLibraryName);
            if($RelativeDirectory{1}) {
                @PARAMS = (@PARAMS, "-relpath", $RelativeDirectory{1});
            }
            if($OutputLogPath{1}) {
                @PARAMS = (@PARAMS, "-log-path", $OutputLogPath{1});
            }
            if($CrossGcc) {
                @PARAMS = (@PARAMS, "-cross-gcc", $CrossGcc);
            }
            if($Debug) {
                @PARAMS = (@PARAMS, "-debug");
            }
            if($Quiet) {
                @PARAMS = (@PARAMS, "-quiet");
            }
            if($ExtendedCheck) {
                @PARAMS = (@PARAMS, "-extended");
            }
            if($UserLang) {
                @PARAMS = (@PARAMS, "-lang", $UserLang);
            }
            if($TargetVersion{1}) {
                @PARAMS = (@PARAMS, "-vnum", $TargetVersion{1});
            }
            if($LogMode eq "n") {
                @PARAMS = (@PARAMS, "-logging-mode", "n");
            }
            elsif($Quiet) {
                @PARAMS = (@PARAMS, "-logging-mode", "a");
            }
            system("perl", $0, @PARAMS);
            if($?) {
                exit(1);
            }
        }
        else
        { # child
            my @PARAMS = ("-dump", $Descriptor{2}{"Path"}, "-l", $TargetLibraryName);
            if($RelativeDirectory{2}) {
                @PARAMS = (@PARAMS, "-relpath", $RelativeDirectory{2});
            }
            if($OutputLogPath{2}) {
                @PARAMS = (@PARAMS, "-log-path", $OutputLogPath{2});
            }
            if($CrossGcc) {
                @PARAMS = (@PARAMS, "-cross-gcc", $CrossGcc);
            }
            if($Debug) {
                @PARAMS = (@PARAMS, "-debug");
            }
            if($Quiet) {
                @PARAMS = (@PARAMS, "-quiet");
            }
            if($ExtendedCheck) {
                @PARAMS = (@PARAMS, "-extended");
            }
            if($UserLang) {
                @PARAMS = (@PARAMS, "-lang", $UserLang);
            }
            if($TargetVersion{2}) {
                @PARAMS = (@PARAMS, "-vnum", $TargetVersion{2});
            }
            if($LogMode eq "n") {
                @PARAMS = (@PARAMS, "-logging-mode", "n");
            }
            elsif($Quiet) {
                @PARAMS = (@PARAMS, "-logging-mode", "a");
            }
            system("perl", $0, @PARAMS);
            if($?) {
                exit(1);
            }
            else {
                exit(0);
            }
        }
        waitpid($pid, 0);
        my @CMP_PARAMS = ("-l", $TargetLibraryName);
        @CMP_PARAMS = (@CMP_PARAMS, "-d1", "abi_dumps/$TargetLibraryName/".$TargetLibraryName."_".$Descriptor{1}{"Version"}.".abi.$AR_EXT");
        @CMP_PARAMS = (@CMP_PARAMS, "-d2", "abi_dumps/$TargetLibraryName/".$TargetLibraryName."_".$Descriptor{2}{"Version"}.".abi.$AR_EXT");
        if($TargetLibraryFName ne $TargetLibraryName) {
            @CMP_PARAMS = (@CMP_PARAMS, "-l-full", $TargetLibraryFName);
        }
        if($ShowRetVal) {
            @CMP_PARAMS = (@CMP_PARAMS, "-show-retval");
        }
        if($CrossGcc) {
            @CMP_PARAMS = (@CMP_PARAMS, "-cross-gcc", $CrossGcc);
        }
        if($Quiet) {
            @CMP_PARAMS = (@CMP_PARAMS, "-quiet");
        }
        if($LogMode eq "n") {
            @CMP_PARAMS = (@CMP_PARAMS, "-logging-mode", "n");
        }
        elsif($Quiet) {
            @CMP_PARAMS = (@CMP_PARAMS, "-logging-mode", "a");
        }
        if($ReportFormat) {
            @CMP_PARAMS = (@CMP_PARAMS, "-report-format", $ReportFormat);
        }
        system("perl", $0, @CMP_PARAMS);
        exit($?>>8);
    }
    if(not $Descriptor{1}{"Dump"}
    or not $Descriptor{2}{"Dump"})
    { # need GCC toolchain to analyze
      # header files and libraries
        detect_default_paths("inc|lib|gcc");
    }
    if(not $Descriptor{1}{"Dump"})
    {
        if(not $CheckHeadersOnly) {
            readLibs(1);
        }
        if($CheckHeadersOnly) {
            setLanguage(1, "C++");
        }
        if(not $CheckObjectsOnly) {
            searchForHeaders(1);
        }
        $WORD_SIZE{1} = detectWordSize();
    }
    if(not $Descriptor{2}{"Dump"})
    {
        if(not $CheckHeadersOnly) {
            readLibs(2);
        }
        if($CheckHeadersOnly) {
            setLanguage(2, "C++");
        }
        if(not $CheckObjectsOnly) {
            searchForHeaders(2);
        }
        $WORD_SIZE{2} = detectWordSize();
    }
    if($WORD_SIZE{1} ne $WORD_SIZE{2})
    { # support for old ABI dumps
      # try to synch different WORD sizes
        if($UsedDump{1}{"V"}
        and cmpVersions($UsedDump{1}{"V"}, "2.1")<0)
        {
            $WORD_SIZE{1} = $WORD_SIZE{2};
            printMsg("WARNING", "set WORD size to ".$WORD_SIZE{2}." bytes");
        }
        elsif($UsedDump{2}{"V"}
        and cmpVersions($UsedDump{2}{"V"}, "2.1")<0)
        {
            $WORD_SIZE{2} = $WORD_SIZE{1};
            printMsg("WARNING", "set WORD size to ".$WORD_SIZE{1}." bytes");
        }
    }
    elsif(not $WORD_SIZE{1}
    and not $WORD_SIZE{2})
    { # support for old ABI dumps
        $WORD_SIZE{1} = 4;
        $WORD_SIZE{2} = 4;
    }
    if($Descriptor{1}{"Dump"})
    { # support for old ABI dumps
        prepareTypes(1);
    }
    if($Descriptor{2}{"Dump"})
    { # support for old ABI dumps
        prepareTypes(2);
    }
    if($AppPath and not keys(%{$Symbol_Library{1}})) {
        printMsg("WARNING", "the application ".get_filename($AppPath)." has no symbols imported from the $SLIB_TYPE libraries");
    }
    # started to process input data
    readRules("Binary");
    readRules("Source");
    if(not $CheckHeadersOnly)
    {# added/removed in libs
        detectAdded();
        detectRemoved();
    }
    if(not $CheckObjectsOnly)
    {
        if($Descriptor{1}{"Headers"}
        and not $Descriptor{1}{"Dump"}) {
            readHeaders(1);
        }
        if($Descriptor{2}{"Headers"}
        and not $Descriptor{2}{"Dump"}) {
            readHeaders(2);
        }
        printMsg("INFO", "comparing headers ...");
        prepareInterfaces(1);
        prepareInterfaces(2);
        %SymbolInfo=();
        if($CheckHeadersOnly)
        { # added/removed in headers
            detectAdded_H();
            detectRemoved_H();
        }
        mergeSignatures();
        if(keys(%CheckedSymbols)) {
            mergeConstants();
        }
    }
    if($CheckHeadersOnly)
    { # added/removed in headers
        mergeHeaders();
    }
    else
    {
        printMsg("INFO", "comparing libraries ...");
        mergeLibs();
        if($CheckImpl) {
            mergeImpl();
        }
    }
}

sub optimize_set(@)
{
    my %Included = ();
    foreach my $Path (@_)
    {
        detect_header_includes($Path, 1);
        foreach my $Include (keys(%{$Header_Includes{1}{$Path}})) {
            $Included{get_filename($Include)}{$Include}=1;
        }
    }
    my @Res = ();
    foreach my $Path (@_)
    {
        my $Add = 1;
        foreach my $Inc (keys(%{$Included{get_filename($Path)}}))
        {
            if($Path=~/\/\Q$Inc\E\Z/)
            {
                $Add = 0;
                last;
            }
        }
        if($Add) {
            push(@Res, $Path);
        }
    }
    return @Res;
}

sub writeOpts()
{
    my %Opts = (
    "OStarget"=>$OStarget,
    "Debug"=>$Debug,
    "Quiet"=>$Quiet,
    "LogMode"=>$LogMode,
    "CheckHeadersOnly"=>$CheckHeadersOnly,
    
    "SystemRoot"=>$SystemRoot,
    "MODULES_DIR"=>$MODULES_DIR,
    "GCC_PATH"=>$GCC_PATH,
    "TargetSysInfo"=>$TargetSysInfo,
    "CrossPrefix"=>$CrossPrefix,
    "TargetLibraryName"=>$TargetLibraryName,
    "CrossGcc"=>$CrossGcc,
    "UseStaticLibs"=>$UseStaticLibs,
    "NoStdInc"=>$NoStdInc
    );
    return \%Opts;
}

sub get_CoreError($)
{
    my %CODE_ERROR = reverse(%ERROR_CODE);
    return $CODE_ERROR{$_[0]};
}

sub scenario()
{
    if($StdOut)
    { # enable quiet mode
        $Quiet = 1;
    }
    if($UserLang)
    { # --lang=C++
        $UserLang = uc($UserLang);
        $COMMON_LANGUAGE{1}=$UserLang;
        $COMMON_LANGUAGE{2}=$UserLang;
    }
    if($LoggingPath)
    {
        $OutputLogPath{1} = $LoggingPath;
        $OutputLogPath{2} = $LoggingPath;
        if($Quiet) {
            $COMMON_LOG_PATH = $LoggingPath;
        }
    }
    if($ReportFormat)
    { # validate
        if($ReportFormat!~/\A(xml|html)\Z/) {
            exitStatus("Error", "unknown format \'$ReportFormat\'");
        }
    }
    else
    { # default: HTML
        $ReportFormat = "html";
    }
    if($Quiet and $LogMode!~/a|n/)
    { # --quiet log
        if(-f $COMMON_LOG_PATH) {
            unlink($COMMON_LOG_PATH);
        }
    }
    if($TestTool and $UseDumps)
    { # --test && --use-dumps == --test-dump
        $TestDump = 1;
    }
    if($Help) {
        HELP_MESSAGE();
        exit(0);
    }
    if($InfoMsg) {
        INFO_MESSAGE();
        exit(0);
    }
    if($ShowVersion) {
        printMsg("INFO", "ABI Compliance Checker (ACC) $TOOL_VERSION\nCopyright (C) 2012 ROSA Laboratory\nLicense: LGPL or GPL <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($DumpVersion) {
        printMsg("INFO", $TOOL_VERSION);
        exit(0);
    }
    if($ExtendedCheck) {
        $CheckHeadersOnly = 1;
    }
    if($SystemRoot_Opt)
    { # user defined root
        if(not -e $SystemRoot_Opt) {
            exitStatus("Access_Error", "can't access \'$SystemRoot\'");
        }
        $SystemRoot = $SystemRoot_Opt;
        $SystemRoot=~s/[\/]+\Z//g;
        if($SystemRoot) {
            $SystemRoot = get_abs_path($SystemRoot);
        }
    }
    $Data::Dumper::Sortkeys = 1;
    # FIXME: can't pass \&dump_sorting - cause a segfault sometimes
    # $Data::Dumper::Sortkeys = \&dump_sorting;
    if($TargetLibsPath)
    {
        if(not -f $TargetLibsPath) {
            exitStatus("Access_Error", "can't access file \'$TargetLibsPath\'");
        }
        foreach my $Lib (split(/\s*\n\s*/, readFile($TargetLibsPath))) {
            $TargetLibs{$Lib} = 1;
        }
    }
    if($TargetHeadersPath)
    { # --headers-list
        if(not -f $TargetHeadersPath) {
            exitStatus("Access_Error", "can't access file \'$TargetHeadersPath\'");
        }
        foreach my $Header (split(/\s*\n\s*/, readFile($TargetHeadersPath)))
        {
            $TargetHeaders{1}{$Header} = 1;
            $TargetHeaders{2}{$Header} = 1;
        }
    }
    if($TargetHeader)
    { # --header
        $TargetHeaders{1}{$TargetHeader} = 1;
        $TargetHeaders{2}{$TargetHeader} = 1;
    }
    if($TestTool
    or $TestDump)
    { # --test, --test-dump
        detect_default_paths("bin|gcc"); # to compile libs
        loadModule("RegTests");
        testTool($TestDump, $Debug, $Quiet, $ExtendedCheck, $LogMode, $ReportFormat, $LIB_EXT, $GCC_PATH);
        exit(0);
    }
    if($DumpSystem)
    { # --dump-system
        loadModule("SysCheck");
        if($DumpSystem=~/\.xml\Z/)
        { # system XML descriptor
            if(not -f $DumpSystem) {
                exitStatus("Access_Error", "can't access file \'$DumpSystem\'");
            }
            my $Ret = readSystemDescriptor(readFile($DumpSystem));
            foreach (@{$Ret->{"Tools"}}) {
                $SystemPaths{"bin"}{$_} = 1;
                $TargetTools{$_}=1;
            }
            if($Ret->{"CrossPrefix"}) {
                $CrossPrefix = $Ret->{"CrossPrefix"};
            }
        }
        elsif($SystemRoot_Opt)
        { # -sysroot "/" option
          # default target: /usr/lib, /usr/include
          # search libs: /usr/lib and /lib
            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'");
            }
            readSystemDescriptor("
                <name>
                    $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 \"/\"");
        }
        detect_default_paths("bin|gcc"); # to check symbols
        if($OStarget eq "windows")
        { # to run dumpbin.exe
          # and undname.exe
            check_win32_env();
        }
        dumpSystem(writeOpts());
        exit(0);
    }
    if($CmpSystems)
    { # --cmp-systems
        detect_default_paths("bin"); # to extract dumps
        loadModule("SysCheck");
        cmpSystems($Descriptor{1}{"Path"}, $Descriptor{2}{"Path"}, writeOpts());
        exit(0);
    }
    if($GenerateTemplate) {
        generateTemplate();
        exit(0);
    }
    if(not $TargetLibraryName) {
        exitStatus("Error", "library name is not selected (option -l <name>)");
    }
    else
    { # validate library name
        if($TargetLibraryName=~/[\*\/\\]/) {
            exitStatus("Error", "\"\\\", \"\/\" and \"*\" symbols are not allowed in the library name");
        }
    }
    if(not $TargetLibraryFName) {
        $TargetLibraryFName = $TargetLibraryName;
    }
    if($CheckHeadersOnly_Opt and $CheckObjectsOnly_Opt) {
        exitStatus("Error", "you can't specify both -headers-only and -objects-only options at the same time");
    }
    if($SymbolsListPath)
    {
        if(not -f $SymbolsListPath) {
            exitStatus("Access_Error", "can't access file \'$SymbolsListPath\'");
        }
        foreach my $Interface (split(/\s*\n\s*/, readFile($SymbolsListPath))) {
            $SymbolsList{$Interface} = 1;
        }
    }
    if($SkipHeadersPath)
    {
        if(not -f $SkipHeadersPath) {
            exitStatus("Access_Error", "can't access file \'$SkipHeadersPath\'");
        }
        foreach my $Path (split(/\s*\n\s*/, readFile($SkipHeadersPath)))
        {
            my ($CPath, $Type) = classifyPath($Path);
            $SkipHeaders{1}{$Type}{$CPath} = 1;
            $SkipHeadersList{1}{$Path} = 1;
            # register for both versions
            $SkipHeaders{2}{$Type}{$CPath} = 1;
            $SkipHeadersList{2}{$Path} = 1;
        }
    }
    if($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+)//) {
                        $AddIntParams{$Interface}{$1}=$2;
                    }
                }
                else {
                    my $Num = 0;
                    foreach my $Name (split(/;/, $Line)) {
                        $AddIntParams{$Interface}{$Num++}=$Name;
                    }
                }
            }
        }
    }
    if($AppPath)
    {
        if(not -f $AppPath) {
            exitStatus("Access_Error", "can't access file \'$AppPath\'");
        }
        foreach my $Interface (getSymbols_App($AppPath)) {
            $SymbolsList_App{$Interface} = 1;
        }
    }
    if($DumpAPI)
    { # --dump-abi
      # make an API dump
        create_ABI_Dump();
        exit($COMPILE_ERRORS);
    }
    # default: compare APIs
    #  -d1 <path>
    #  -d2 <path>
    compareAPIs();
    exitReport();
}

scenario();
