return 0;
}
return 1;
}
sub showArch($)
{
my $Arch = $_[0];
if($Arch eq "arm"
or $Arch eq "mips") {
return uc($Arch);
}
return $Arch;
}
sub getReportTitle($)
{
my $Level = $_[0];
my $ArchInfo = " on ".showArch($In::ABI{1}{"Arch"})."";
if($In::ABI{1}{"Arch"} ne $In::ABI{2}{"Arch"}
or $Level eq "Source")
{ # don't show architecture in the header
$ArchInfo = "";
}
my $Title = "";
if($Level eq "Source") {
$Title .= "Source compatibility";
}
elsif($Level eq "Binary") {
$Title .= "Binary compatibility";
}
else {
$Title .= "API compatibility";
}
my $V1 = $In::Desc{1}{"Version"};
my $V2 = $In::Desc{2}{"Version"};
if($UsedDump{1}{"DWARF"} and $UsedDump{2}{"DWARF"})
{
my $M1 = $In::ABI{1}{"LibraryName"};
my $M2 = $In::ABI{2}{"LibraryName"};
my $M1S = $M1;
my $M2S = $M2;
$M1S=~s/(\.so|\.ko)\..+/$1/ig;
$M2S=~s/(\.so|\.ko)\..+/$1/ig;
if($M1S eq $M2S
and $V1 ne "X" and $V2 ne "Y")
{
$Title .= " report for the $M1S ".$In::Opt{"TargetComponent"};
$Title .= " between ".$V1." and ".$V2." versions";
}
else
{
$Title .= " report between $M1 (".$V1.")";
$Title .= " and $M2 (".$V2.") objects";
}
}
else
{
$Title .= " report for the ".$In::Opt{"TargetTitle"}." ".$In::Opt{"TargetComponent"};
$Title .= " between ".$V1." and ".$V2." versions";
}
$Title .= $ArchInfo;
if($In::Opt{"AppPath"}) {
$Title .= " (relating to the portability of application ".getFilename($In::Opt{"AppPath"}).")";
}
$Title = "".$Title."
\n";
return $Title;
}
sub getCheckedHeaders($)
{
my $LVer = $_[0];
my @Headers = ();
foreach my $Path (keys(%{$In::ABI{$LVer}{"Headers"}}))
{
my $Name = getFilename($Path);
if(not isTargetHeader($Name, $LVer)) {
next;
}
if(skipHeader($Name, $LVer)) {
next;
}
push(@Headers, $Path);
}
return @Headers;
}
sub getSourceInfo()
{
my ($CheckedHeaders, $CheckedSources, $CheckedLibs) = ("", "");
if(my @Headers = getCheckedHeaders(1))
{
$CheckedHeaders = "";
if($In::Opt{"OldStyle"}) {
$CheckedHeaders .= "Header Files (".($#Headers+1).")
";
}
else {
$CheckedHeaders .= "Header Files ".($#Headers+1)."
";
}
$CheckedHeaders .= "
\n";
$CheckedHeaders .= "\n";
foreach my $Path (sort {lc($a) cmp lc($b)} @Headers) {
$CheckedHeaders .= getFilename($Path)."
\n";
}
$CheckedHeaders .= "
\n";
$CheckedHeaders .= "
$TOP_REF
\n";
}
if(my @Sources = keys(%{$In::ABI{1}{"Sources"}}))
{
$CheckedSources = "";
if($In::Opt{"OldStyle"}) {
$CheckedSources .= "Source Files (".($#Sources+1).")
";
}
else {
$CheckedSources .= "Source Files ".($#Sources+1)."
";
}
$CheckedSources .= "
\n";
$CheckedSources .= "\n";
foreach my $Path (sort {lc($a) cmp lc($b)} @Sources) {
$CheckedSources .= getFilename($Path)."
\n";
}
$CheckedSources .= "
\n";
$CheckedSources .= "
$TOP_REF
\n";
}
if(not $In::Opt{"CheckHeadersOnly"})
{
$CheckedLibs = "";
if($In::Opt{"OldStyle"}) {
$CheckedLibs .= "".getObjTitle()." (".keys(%{$In::ABI{1}{"Symbols"}}).")
";
}
else {
$CheckedLibs .= "".getObjTitle()." ".keys(%{$In::ABI{1}{"Symbols"}})."
";
}
$CheckedLibs .= "
\n";
$CheckedLibs .= "\n";
foreach my $Library (sort {lc($a) cmp lc($b)} keys(%{$In::ABI{1}{"Symbols"}})) {
$CheckedLibs .= $Library."
\n";
}
$CheckedLibs .= "
\n";
$CheckedLibs .= "
$TOP_REF
\n";
}
return $CheckedHeaders.$CheckedSources.$CheckedLibs;
}
sub getObjTitle()
{
if(defined $UsedDump{1}{"DWARF"}) {
return "Objects";
}
return "Libraries";
}
sub getTypeProblemsCount($$)
{
my ($TargetSeverity, $Level) = @_;
my $Count = 0;
foreach my $Type_Name (sort keys(%{$TypeChanges{$Level}}))
{
my %Kinds_Target = ();
foreach my $Kind (keys(%{$TypeChanges{$Level}{$Type_Name}}))
{
if($CompatRules{$Level}{$Kind}{"Severity"} ne $TargetSeverity) {
next;
}
foreach my $Loc (keys(%{$TypeChanges{$Level}{$Type_Name}{$Kind}}))
{
my $Target = $TypeChanges{$Level}{$Type_Name}{$Kind}{$Loc}{"Target"};
if($Kinds_Target{$Kind}{$Target}) {
next;
}
$Kinds_Target{$Kind}{$Target} = 1;
$Count += 1;
}
}
}
return $Count;
}
sub getSummary($)
{
my $Level = $_[0];
my ($Added, $Removed, $I_Problems_High, $I_Problems_Medium, $I_Problems_Low, $T_Problems_High,
$C_Problems_Low, $T_Problems_Medium, $T_Problems_Low, $I_Other, $T_Other, $C_Other) = (0,0,0,0,0,0,0,0,0,0,0,0);
%{$RESULT{$Level}} = (
"Problems"=>0,
"Warnings"=>0,
"Affected"=>0);
# check rules
foreach my $Symbol (sort keys(%{$CompatProblems{$Level}}))
{
foreach my $Kind (keys(%{$CompatProblems{$Level}{$Symbol}}))
{
if(not defined $CompatRules{$Level}{$Kind})
{ # unknown rule
if(not $UnknownRules{$Level}{$Kind})
{ # only one warning
printMsg("WARNING", "unknown rule \"$Kind\" (\"$Level\")");
$UnknownRules{$Level}{$Kind}=1;
}
delete($CompatProblems{$Level}{$Symbol}{$Kind});
}
}
}
foreach my $Constant (sort keys(%{$CompatProblems_Constants{$Level}}))
{
foreach my $Kind (keys(%{$CompatProblems_Constants{$Level}{$Constant}}))
{
if(not defined $CompatRules{$Level}{$Kind})
{ # unknown rule
if(not $UnknownRules{$Level}{$Kind})
{ # only one warning
printMsg("WARNING", "unknown rule \"$Kind\" (\"$Level\")");
$UnknownRules{$Level}{$Kind}=1;
}
delete($CompatProblems_Constants{$Level}{$Constant}{$Kind});
}
}
}
foreach my $Symbol (sort keys(%{$CompatProblems{$Level}}))
{
foreach my $Kind (sort keys(%{$CompatProblems{$Level}{$Symbol}}))
{
if($CompatRules{$Level}{$Kind}{"Kind"} eq "Symbols")
{
my $Severity = $CompatRules{$Level}{$Kind}{"Severity"};
foreach my $Loc (sort keys(%{$CompatProblems{$Level}{$Symbol}{$Kind}}))
{
if($Kind eq "Added_Symbol") {
$Added += 1;
}
elsif($Kind eq "Removed_Symbol")
{
$Removed += 1;
$TotalAffected{$Level}{$Symbol} = $Severity;
}
else
{
if($Severity eq "Safe") {
$I_Other += 1;
}
elsif($Severity eq "High") {
$I_Problems_High += 1;
}
elsif($Severity eq "Medium") {
$I_Problems_Medium += 1;
}
elsif($Severity eq "Low") {
$I_Problems_Low += 1;
}
if(($Severity ne "Low" or $In::Opt{"StrictCompat"})
and $Severity ne "Safe") {
$TotalAffected{$Level}{$Symbol} = $Severity;
}
}
}
}
}
}
my %MethodTypeIndex = ();
foreach my $Symbol (sort keys(%{$CompatProblems{$Level}}))
{
foreach my $Kind (sort keys(%{$CompatProblems{$Level}{$Symbol}}))
{
if($CompatRules{$Level}{$Kind}{"Kind"} eq "Types")
{
my $Severity = $CompatRules{$Level}{$Kind}{"Severity"};
if(($Severity ne "Low" or $In::Opt{"StrictCompat"})
and $Severity ne "Safe")
{
if(my $Sev = $TotalAffected{$Level}{$Symbol})
{
if($Severity_Val{$Severity}>$Severity_Val{$Sev}) {
$TotalAffected{$Level}{$Symbol} = $Severity;
}
}
else {
$TotalAffected{$Level}{$Symbol} = $Severity;
}
}
my $LSK = $CompatProblems{$Level}{$Symbol}{$Kind};
my (@Locs1, @Locs2) = ();
foreach my $Loc (sort keys(%{$LSK}))
{
if(index($Loc, "retval")==0 or index($Loc, "this")==0) {
push(@Locs2, $Loc);
}
else {
push(@Locs1, $Loc);
}
}
foreach my $Loc (@Locs1, @Locs2)
{
my $Type = $LSK->{$Loc}{"Type_Name"};
my $Target = $LSK->{$Loc}{"Target"};
if(defined $MethodTypeIndex{$Symbol}{$Type}{$Kind}{$Target})
{ # one location for one type and target
next;
}
$MethodTypeIndex{$Symbol}{$Type}{$Kind}{$Target} = 1;
$TypeChanges{$Level}{$Type}{$Kind}{$Loc} = $LSK->{$Loc};
$TypeProblemsIndex{$Level}{$Type}{$Kind}{$Loc}{$Symbol} = 1;
}
}
}
}
# clean memory
%MethodTypeIndex = ();
$T_Problems_High = getTypeProblemsCount("High", $Level);
$T_Problems_Medium = getTypeProblemsCount("Medium", $Level);
$T_Problems_Low = getTypeProblemsCount("Low", $Level);
$T_Other = getTypeProblemsCount("Safe", $Level);
# changed and removed public symbols
my $SCount = keys(%{$CheckedSymbols{$Level}});
if($In::Opt{"ExtendedCheck"})
{ # don't count external_func_0 for constants
$SCount-=1;
}
if($SCount)
{
my %Weight = (
"High" => 100,
"Medium" => 50,
"Low" => 25
);
foreach (keys(%{$TotalAffected{$Level}})) {
$RESULT{$Level}{"Affected"}+=$Weight{$TotalAffected{$Level}{$_}};
}
$RESULT{$Level}{"Affected"} = $RESULT{$Level}{"Affected"}/$SCount;
}
else {
$RESULT{$Level}{"Affected"} = 0;
}
$RESULT{$Level}{"Affected"} = showNum($RESULT{$Level}{"Affected"});
if($RESULT{$Level}{"Affected"}>=100) {
$RESULT{$Level}{"Affected"} = 100;
}
$RESULT{$Level}{"Problems"} += $Removed;
$RESULT{$Level}{"Problems"} += $T_Problems_High + $I_Problems_High;
$RESULT{$Level}{"Problems"} += $T_Problems_Medium + $I_Problems_Medium;
if($In::Opt{"StrictCompat"}) {
$RESULT{$Level}{"Problems"} += $T_Problems_Low + $I_Problems_Low;
}
else {
$RESULT{$Level}{"Warnings"} += $T_Problems_Low + $I_Problems_Low;
}
foreach my $Constant (keys(%{$CompatProblems_Constants{$Level}}))
{
foreach my $Kind (keys(%{$CompatProblems_Constants{$Level}{$Constant}}))
{
my $Severity = $CompatRules{$Level}{$Kind}{"Severity"};
if($Severity eq "Safe")
{
$C_Other+=1;
}
elsif($Severity eq "Low")
{
$C_Problems_Low+=1;
}
}
}
if($C_Problems_Low)
{
if($In::Opt{"StrictCompat"}) {
$RESULT{$Level}{"Problems"} += $C_Problems_Low;
}
else {
$RESULT{$Level}{"Warnings"} += $C_Problems_Low;
}
}
if($RESULT{$Level}{"Problems"}
and $RESULT{$Level}{"Affected"}) {
$RESULT{$Level}{"Verdict"} = "incompatible";
}
else {
$RESULT{$Level}{"Verdict"} = "compatible";
}
my $TotalTypes = keys(%{$CheckedTypes{$Level}});
if(not $TotalTypes)
{ # list all the types
$TotalTypes = keys(%{$TName_Tid{1}});
}
my $TotalSymbols = keys(%{$CheckedSymbols{$Level}});
if($In::Opt{"ExtendedCheck"}) {
$TotalSymbols -= keys(%ExtendedSymbols);
}
my $AnyChanged = ($Added or $Removed or $I_Problems_High or $I_Problems_Medium or $I_Problems_Low or $T_Problems_High or
$C_Problems_Low or $T_Problems_Medium or $T_Problems_Low or $I_Other or $T_Other or $C_Other);
my ($Arch1, $Arch2) = ($In::ABI{1}{"Arch"}, $In::ABI{2}{"Arch"});
my ($GccV1, $GccV2) = ($In::ABI{1}{"GccVersion"}, $In::ABI{2}{"GccVersion"});
my ($ClangV1, $ClangV2) = ($In::ABI{1}{"ClangVersion"}, $In::ABI{2}{"ClangVersion"});
my ($TestInfo, $TestResults, $Problem_Summary) = ();
if($In::Opt{"ReportFormat"} eq "xml")
{ # XML
# test info
$TestInfo .= " ".$In::Opt{"TargetLib"}."\n";
$TestInfo .= " \n";
$TestInfo .= " ".$In::Desc{1}{"Version"}."\n";
$TestInfo .= " $Arch1\n";
if($GccV1) {
$TestInfo .= " $GccV1\n";
}
elsif($ClangV1) {
$TestInfo .= " $ClangV1\n";
}
$TestInfo .= " \n";
$TestInfo .= " \n";
$TestInfo .= " ".$In::Desc{2}{"Version"}."\n";
$TestInfo .= " $Arch2\n";
if($GccV2) {
$TestInfo .= " $GccV2\n";
}
elsif($ClangV2) {
$TestInfo .= " $ClangV2\n";
}
$TestInfo .= " \n";
$TestInfo = "\n".$TestInfo."\n\n";
# test results
if(my @Headers = keys(%{$In::ABI{1}{"Headers"}}))
{
$TestResults .= " \n";
foreach my $Name (sort {lc($a) cmp lc($b)} @Headers) {
$TestResults .= " ".getFilename($Name)."\n";
}
$TestResults .= " \n";
}
if(my @Sources = keys(%{$In::ABI{1}{"Sources"}}))
{
$TestResults .= " \n";
foreach my $Name (sort {lc($a) cmp lc($b)} @Sources) {
$TestResults .= " ".getFilename($Name)."\n";
}
$TestResults .= " \n";
}
$TestResults .= " \n";
foreach my $Library (sort {lc($a) cmp lc($b)} keys(%{$In::ABI{1}{"Symbols"}}))
{
$TestResults .= " $Library\n";
}
$TestResults .= " \n";
$TestResults .= " ".$TotalSymbols."\n";
$TestResults .= " ".$TotalTypes."\n";
$TestResults .= " ".$RESULT{$Level}{"Verdict"}."\n";
$TestResults .= " ".$RESULT{$Level}{"Affected"}."\n";
$TestResults = "\n".$TestResults."\n\n";
# problem summary
$Problem_Summary .= " ".$Added."\n";
$Problem_Summary .= " ".$Removed."\n";
$Problem_Summary .= " \n";
$Problem_Summary .= " $T_Problems_High\n";
$Problem_Summary .= " $T_Problems_Medium\n";
$Problem_Summary .= " $T_Problems_Low\n";
$Problem_Summary .= " $T_Other\n";
$Problem_Summary .= " \n";
$Problem_Summary .= " \n";
$Problem_Summary .= " $I_Problems_High\n";
$Problem_Summary .= " $I_Problems_Medium\n";
$Problem_Summary .= " $I_Problems_Low\n";
$Problem_Summary .= " $I_Other\n";
$Problem_Summary .= " \n";
$Problem_Summary .= " \n";
$Problem_Summary .= " $C_Problems_Low\n";
$Problem_Summary .= " \n";
$Problem_Summary = "\n".$Problem_Summary."\n\n";
return ($TestInfo.$TestResults.$Problem_Summary, "", $AnyChanged);
}
else
{ # HTML
# test info
$TestInfo = "Test Info
\n";
$TestInfo .= "\n";
if($In::Opt{"TargetComponent"} eq "library") {
$TestInfo .= "Library Name | ".$In::Opt{"TargetTitle"}." |
\n";
}
else {
$TestInfo .= "Module Name | ".$In::Opt{"TargetTitle"}." |
\n";
}
my (@VInf1, @VInf2, $AddTestInfo) = ();
# CPU arch
if($Arch1 eq $Arch2)
{ # go to the separate section
$AddTestInfo .= "Arch | ".showArch($Arch1)." |
\n";
}
else
{ # go to the version number
push(@VInf1, showArch($Arch1));
push(@VInf2, showArch($Arch2));
}
if($Level eq "Binary"
and $In::Opt{"Target"} ne "windows")
{
if($GccV1 and $GccV2)
{ # GCC version
if($GccV1 eq $GccV2)
{ # go to the separate section
$AddTestInfo .= "GCC Version | $GccV1 |
\n";
}
else
{ # go to the version number
push(@VInf1, "gcc ".$GccV1);
push(@VInf2, "gcc ".$GccV2);
}
}
elsif($ClangV1 and $ClangV2)
{ # Clang version
if($ClangV1 eq $ClangV2)
{ # go to the separate section
$AddTestInfo .= "Clang Version | $ClangV1 |
\n";
}
else
{ # go to the version number
push(@VInf1, "clang ".$ClangV1);
push(@VInf2, "clang ".$ClangV2);
}
}
elsif($GccV1 and $ClangV2)
{
push(@VInf1, "gcc ".$GccV1);
push(@VInf2, "clang ".$ClangV2);
}
elsif($ClangV1 and $GccV2)
{
push(@VInf1, "clang ".$ClangV1);
push(@VInf2, "gcc ".$GccV2);
}
}
# show long version names with GCC version and CPU architecture name (if different)
$TestInfo .= "Version #1 | ".$In::Desc{1}{"Version"}.(@VInf1?" (".join(", ", reverse(@VInf1)).")":"")." |
\n";
$TestInfo .= "Version #2 | ".$In::Desc{2}{"Version"}.(@VInf2?" (".join(", ", reverse(@VInf2)).")":"")." |
\n";
$TestInfo .= $AddTestInfo;
if($In::Opt{"ExtendedCheck"}) {
$TestInfo .= "Mode | Extended |
\n";
}
if($In::Opt{"JoinReport"})
{
if($Level eq "Binary") {
$TestInfo .= "Subject | Binary Compatibility |
\n"; # Run-time
}
elsif($Level eq "Source") {
$TestInfo .= "Subject | Source Compatibility |
\n"; # Build-time
}
}
$TestInfo .= "
\n";
# test results
$TestResults = "Test Results
\n";
$TestResults .= "";
if(my @Headers = getCheckedHeaders(1))
{
my $Headers_Link = "".($#Headers + 1)."";
$TestResults .= "Total Header Files | ".$Headers_Link." |
\n";
}
if(my @Sources = keys(%{$In::ABI{1}{"Sources"}}))
{
my $Src_Link = "".($#Sources + 1)."";
$TestResults .= "Total Source Files | ".$Src_Link." |
\n";
}
if(not $In::Opt{"ExtendedCheck"})
{
my $Libs_Link = "0";
$Libs_Link = "".keys(%{$In::ABI{1}{"Symbols"}})."" if(keys(%{$In::ABI{1}{"Symbols"}})>0);
$TestResults .= "Total ".getObjTitle()." | ".($In::Opt{"CheckHeadersOnly"}?"0 (not analyzed)":$Libs_Link)." |
\n";
}
$TestResults .= "Total Symbols / Types | ".$TotalSymbols." / ".$TotalTypes." |
\n";
my $META_DATA = "verdict:".$RESULT{$Level}{"Verdict"}.";";
if($In::Opt{"JoinReport"}) {
$META_DATA = "kind:".lc($Level).";".$META_DATA;
}
my $BC_Rate = showNum(100 - $RESULT{$Level}{"Affected"});
$TestResults .= "Compatibility | \n";
if($RESULT{$Level}{"Verdict"} eq "incompatible")
{
my $Cl = "incompatible";
if($BC_Rate>=90) {
$Cl = "warning";
}
elsif($BC_Rate>=80) {
$Cl = "almost_compatible";
}
$TestResults .= "".$BC_Rate."% | \n";
}
else {
$TestResults .= "100% | \n";
}
$TestResults .= "
\n";
$TestResults .= "
\n";
$META_DATA .= "affected:".$RESULT{$Level}{"Affected"}.";";# in percents
# problem summary
$Problem_Summary = "Problem Summary
\n";
$Problem_Summary .= "";
$Problem_Summary .= " | Severity | Count |
";
my $Added_Link = "0";
if($Added>0)
{
if($In::Opt{"JoinReport"}) {
$Added_Link = "$Added";
}
else {
$Added_Link = "$Added";
}
}
$META_DATA .= "added:$Added;";
$Problem_Summary .= "Added Symbols | - | $Added_Link |
\n";
my $Removed_Link = "0";
if($Removed>0)
{
if($In::Opt{"JoinReport"}) {
$Removed_Link = "$Removed"
}
else {
$Removed_Link = "$Removed"
}
}
$META_DATA .= "removed:$Removed;";
$Problem_Summary .= "Removed Symbols | ";
$Problem_Summary .= "High | $Removed_Link |
\n";
my $TH_Link = "0";
$TH_Link = "$T_Problems_High" if($T_Problems_High>0);
$META_DATA .= "type_problems_high:$T_Problems_High;";
$Problem_Summary .= "Problems with Data Types | ";
$Problem_Summary .= "High | $TH_Link |
\n";
my $TM_Link = "0";
$TM_Link = "$T_Problems_Medium" if($T_Problems_Medium>0);
$META_DATA .= "type_problems_medium:$T_Problems_Medium;";
$Problem_Summary .= "Medium | $TM_Link |
\n";
my $TL_Link = "0";
$TL_Link = "$T_Problems_Low" if($T_Problems_Low>0);
$META_DATA .= "type_problems_low:$T_Problems_Low;";
$Problem_Summary .= "Low | $TL_Link |
\n";
my $IH_Link = "0";
$IH_Link = "$I_Problems_High" if($I_Problems_High>0);
$META_DATA .= "interface_problems_high:$I_Problems_High;";
$Problem_Summary .= "Problems with Symbols | ";
$Problem_Summary .= "High | $IH_Link |
\n";
my $IM_Link = "0";
$IM_Link = "$I_Problems_Medium" if($I_Problems_Medium>0);
$META_DATA .= "interface_problems_medium:$I_Problems_Medium;";
$Problem_Summary .= "Medium | $IM_Link |
\n";
my $IL_Link = "0";
$IL_Link = "$I_Problems_Low" if($I_Problems_Low>0);
$META_DATA .= "interface_problems_low:$I_Problems_Low;";
$Problem_Summary .= "Low | $IL_Link |
\n";
my $ChangedConstants_Link = "0";
if(keys(%{$CheckedSymbols{$Level}}) and $C_Problems_Low) {
$ChangedConstants_Link = "$C_Problems_Low";
}
$META_DATA .= "changed_constants:$C_Problems_Low;";
$Problem_Summary .= "Problems with Constants | Low | $ChangedConstants_Link |
\n";
# Safe Changes
if($T_Other)
{
my $TS_Link = "$T_Other";
$Problem_Summary .= "Other Changes in Data Types | - | $TS_Link |
\n";
$META_DATA .= "type_changes_other:$T_Other;";
}
if($I_Other)
{
my $IS_Link = "$I_Other";
$Problem_Summary .= "Other Changes in Symbols | - | $IS_Link |
\n";
$META_DATA .= "interface_changes_other:$I_Other;";
}
if($C_Other)
{
my $CS_Link = "$C_Other";
$Problem_Summary .= "Other Changes in Constants | - | $CS_Link |
\n";
$META_DATA .= "constant_changes_other:$C_Other;";
}
$META_DATA .= "tool_version:$TOOL_VERSION";
$Problem_Summary .= "
\n";
return ($TestInfo.$TestResults.$Problem_Summary, $META_DATA, $AnyChanged);
}
}
sub getStyle($$$)
{
my ($Subj, $Act, $Num) = @_;
my %Style = (
"Added"=>"new",
"Removed"=>"failed",
"Safe"=>"passed",
"Low"=>"warning",
"Medium"=>"failed",
"High"=>"failed"
);
if($Num>0) {
return " class='".$Style{$Act}."'";
}
return "";
}
sub getReportChangedConstants($$)
{
my ($TargetSeverity, $Level) = @_;
my $CHANGED_CONSTANTS = "";
my %ReportMap = ();
foreach my $Constant (keys(%{$CompatProblems_Constants{$Level}}))
{
my $Header = $Constants{1}{$Constant}{"Header"};
if(not $Header) {
$Header = $Constants{1}{$Constant}{"Source"};
}
if(not $Header)
{ # added
$Header = $Constants{2}{$Constant}{"Header"};
if(not $Header) {
$Header = $Constants{2}{$Constant}{"Source"}
}
}
foreach my $Kind (sort {lc($a) cmp lc($b)} keys(%{$CompatProblems_Constants{$Level}{$Constant}}))
{
if(not defined $CompatRules{$Level}{$Kind}) {
next;
}
if($TargetSeverity ne $CompatRules{$Level}{$Kind}{"Severity"}) {
next;
}
$ReportMap{$Header}{$Constant}{$Kind} = 1;
}
}
if($In::Opt{"ReportFormat"} eq "xml")
{ # XML
foreach my $HeaderName (sort {lc($a) cmp lc($b)} keys(%ReportMap))
{
$CHANGED_CONSTANTS .= " \n";
foreach my $Constant (sort {lc($a) cmp lc($b)} keys(%{$ReportMap{$HeaderName}}))
{
$CHANGED_CONSTANTS .= " \n";
foreach my $Kind (sort {lc($a) cmp lc($b)} keys(%{$ReportMap{$HeaderName}{$Constant}}))
{
my $Change = $CompatRules{$Level}{$Kind}{"Change"};
my $Effect = $CompatRules{$Level}{$Kind}{"Effect"};
my $Overcome = $CompatRules{$Level}{$Kind}{"Overcome"};
$CHANGED_CONSTANTS .= " \n";
$CHANGED_CONSTANTS .= " $Change\n";
$CHANGED_CONSTANTS .= " $Effect\n";
if($Overcome) {
$CHANGED_CONSTANTS .= " $Overcome\n";
}
$CHANGED_CONSTANTS .= " \n";
}
$CHANGED_CONSTANTS .= " \n";
}
$CHANGED_CONSTANTS .= " \n";
}
$CHANGED_CONSTANTS = "\n".$CHANGED_CONSTANTS."\n\n";
}
else
{ # HTML
my $ProblemsNum = 0;
foreach my $HeaderName (sort {lc($a) cmp lc($b)} keys(%ReportMap))
{
$CHANGED_CONSTANTS .= "$HeaderName
\n";
foreach my $Constant (sort {lc($a) cmp lc($b)} keys(%{$ReportMap{$HeaderName}}))
{
my $Report = "";
foreach my $Kind (sort {lc($a) cmp lc($b)} keys(%{$ReportMap{$HeaderName}{$Constant}}))
{
my $Change = applyMacroses($Level, $Kind, $CompatRules{$Level}{$Kind}{"Change"}, $CompatProblems_Constants{$Level}{$Constant}{$Kind});
my $Effect = $CompatRules{$Level}{$Kind}{"Effect"};
$Report .= "\n1 | \n".$Change." | \n$Effect | \n
\n";
$ProblemsNum += 1;
}
if($Report)
{
$Report = $ContentDivStart."\n\n | \nChange | \nEffect | \n
\n".$Report."
\n
\n$ContentDivEnd\n";
$Report = $ContentSpanStart."[+] ".$Constant.$ContentSpanEnd."
\n".$Report;
$Report = insertIDs($Report);
}
$CHANGED_CONSTANTS .= $Report;
}
$CHANGED_CONSTANTS .= "
\n";
}
if($CHANGED_CONSTANTS)
{
my $Title = "Problems with Constants, $TargetSeverity Severity";
if($TargetSeverity eq "Safe")
{ # Safe Changes
$Title = "Other Changes in Constants";
}
if($In::Opt{"OldStyle"}) {
$CHANGED_CONSTANTS = "$Title ($ProblemsNum)
\n".$CHANGED_CONSTANTS;
}
else {
$CHANGED_CONSTANTS = "$Title $ProblemsNum
\n".$CHANGED_CONSTANTS;
}
$CHANGED_CONSTANTS = "\n".$CHANGED_CONSTANTS.$TOP_REF."
\n";
}
}
return $CHANGED_CONSTANTS;
}
sub getTitle($$$)
{
my ($Header, $Library, $NameSpace) = @_;
my $Title = "";
if($Header and $Library)
{
$Title .= "$Header";
$Title .= ", $Library
\n";
}
elsif($Library) {
$Title .= "$Library
\n";
}
elsif($Header) {
$Title .= "$Header
\n";
}
if($NameSpace) {
$Title .= "namespace $NameSpace
\n";
}
return $Title;
}
sub getReportAdded($)
{
my $Level = $_[0];
my $ADDED_INTERFACES = "";
my %ReportMap = ();
foreach my $Symbol (sort keys(%{$CompatProblems{$Level}}))
{
foreach my $Kind (sort keys(%{$CompatProblems{$Level}{$Symbol}}))
{
if($Kind eq "Added_Symbol")
{
my $HeaderName = $CompSign{2}{$Symbol}{"Header"};
if(not $HeaderName) {
$HeaderName = $CompSign{2}{$Symbol}{"Source"};
}
my $DyLib = $In::ABI{2}{"SymLib"}{$Symbol};
if($Level eq "Source" and $In::Opt{"ReportFormat"} eq "html")
{ # do not show library name in the HTML report
$DyLib = "";
}
$ReportMap{$HeaderName}{$DyLib}{$Symbol} = 1;
}
}
}
if($In::Opt{"ReportFormat"} eq "xml")
{ # XML
foreach my $HeaderName (sort {lc($a) cmp lc($b)} keys(%ReportMap))
{
$ADDED_INTERFACES .= " \n";
foreach my $DyLib (sort {lc($a) cmp lc($b)} keys(%{$ReportMap{$HeaderName}}))
{
$ADDED_INTERFACES .= " \n";
foreach my $Symbol (keys(%{$ReportMap{$HeaderName}{$DyLib}})) {
$ADDED_INTERFACES .= " $Symbol\n";
}
$ADDED_INTERFACES .= " \n";
}
$ADDED_INTERFACES .= " \n";
}
$ADDED_INTERFACES = "\n".$ADDED_INTERFACES."\n\n";
}
else
{ # HTML
my $Added_Number = 0;
foreach my $HeaderName (sort {lc($a) cmp lc($b)} keys(%ReportMap))
{
foreach my $DyLib (sort {lc($a) cmp lc($b)} keys(%{$ReportMap{$HeaderName}}))
{
my %NameSpaceSymbols = ();
foreach my $Symbol (keys(%{$ReportMap{$HeaderName}{$DyLib}})) {
$NameSpaceSymbols{selectSymbolNs($Symbol, 2)}{$Symbol} = 1;
}
foreach my $NameSpace (sort keys(%NameSpaceSymbols))
{
$ADDED_INTERFACES .= getTitle($HeaderName, $DyLib, $NameSpace);
my @SortedInterfaces = sort {lc($CompSign{2}{$a}{"Unmangled"}) cmp lc($CompSign{2}{$b}{"Unmangled"})} sort {lc($a) cmp lc($b)} keys(%{$NameSpaceSymbols{$NameSpace}});
foreach my $Symbol (@SortedInterfaces)
{
$Added_Number += 1;
my $Signature = highLight_ItalicColor($Symbol, 2);
if($NameSpace) {
$Signature = cutNs($Signature, $NameSpace);
}
if($Symbol=~/\A(_Z|\?)/) {
$ADDED_INTERFACES .= insertIDs($ContentSpanStart.$Signature.$ContentSpanEnd."
\n".$ContentDivStart."$Symbol\n
\n
\n".$ContentDivEnd."\n");
}
else {
$ADDED_INTERFACES .= "".$Signature."
\n";
}
}
$ADDED_INTERFACES .= "
\n";
}
}
}
if($ADDED_INTERFACES)
{
my $Anchor = "";
if($In::Opt{"JoinReport"}) {
$Anchor = "";
}
if($In::Opt{"OldStyle"}) {
$ADDED_INTERFACES = "Added Symbols ($Added_Number)
\n".$ADDED_INTERFACES;
}
else {
$ADDED_INTERFACES = "Added Symbols $Added_Number
\n".$ADDED_INTERFACES;
}
$ADDED_INTERFACES = $Anchor.$ADDED_INTERFACES.$TOP_REF."
\n";
}
}
return $ADDED_INTERFACES;
}
sub getReportRemoved($)
{
my $Level = $_[0];
my $REMOVED_INTERFACES = "";
my %ReportMap = ();
foreach my $Symbol (sort keys(%{$CompatProblems{$Level}}))
{
foreach my $Kind (sort keys(%{$CompatProblems{$Level}{$Symbol}}))
{
if($Kind eq "Removed_Symbol")
{
my $HeaderName = $CompSign{1}{$Symbol}{"Header"};
if(not $HeaderName) {
$HeaderName = $CompSign{1}{$Symbol}{"Source"};
}
my $DyLib = $In::ABI{1}{"SymLib"}{$Symbol};
if($Level eq "Source" and $In::Opt{"ReportFormat"} eq "html")
{ # do not show library name in HTML report
$DyLib = "";
}
$ReportMap{$HeaderName}{$DyLib}{$Symbol} = 1;
}
}
}
if($In::Opt{"ReportFormat"} eq "xml")
{ # XML
foreach my $HeaderName (sort {lc($a) cmp lc($b)} keys(%ReportMap))
{
$REMOVED_INTERFACES .= " \n";
foreach my $DyLib (sort {lc($a) cmp lc($b)} keys(%{$ReportMap{$HeaderName}}))
{
$REMOVED_INTERFACES .= " \n";
foreach my $Symbol (keys(%{$ReportMap{$HeaderName}{$DyLib}})) {
$REMOVED_INTERFACES .= " $Symbol\n";
}
$REMOVED_INTERFACES .= " \n";
}
$REMOVED_INTERFACES .= " \n";
}
$REMOVED_INTERFACES = "\n".$REMOVED_INTERFACES."\n\n";
}
else
{ # HTML
my $Removed_Number = 0;
foreach my $HeaderName (sort {lc($a) cmp lc($b)} keys(%ReportMap))
{
foreach my $DyLib (sort {lc($a) cmp lc($b)} keys(%{$ReportMap{$HeaderName}}))
{
my %NameSpaceSymbols = ();
foreach my $Interface (keys(%{$ReportMap{$HeaderName}{$DyLib}})) {
$NameSpaceSymbols{selectSymbolNs($Interface, 1)}{$Interface} = 1;
}
foreach my $NameSpace (sort keys(%NameSpaceSymbols))
{
$REMOVED_INTERFACES .= getTitle($HeaderName, $DyLib, $NameSpace);
my @SortedInterfaces = sort {lc($CompSign{1}{$a}{"Unmangled"}) cmp lc($CompSign{1}{$b}{"Unmangled"})} sort {lc($a) cmp lc($b)} keys(%{$NameSpaceSymbols{$NameSpace}});
foreach my $Symbol (@SortedInterfaces)
{
$Removed_Number += 1;
my $Signature = highLight_ItalicColor($Symbol, 1);
if($NameSpace) {
$Signature = cutNs($Signature, $NameSpace);
}
if($Symbol=~/\A(_Z|\?)/) {
$REMOVED_INTERFACES .= insertIDs($ContentSpanStart.$Signature.$ContentSpanEnd."
\n".$ContentDivStart."$Symbol\n
\n
\n".$ContentDivEnd."\n");
}
else {
$REMOVED_INTERFACES .= "".$Signature."
\n";
}
}
}
$REMOVED_INTERFACES .= "
\n";
}
}
if($REMOVED_INTERFACES)
{
my $Anchor = "";
if($In::Opt{"JoinReport"}) {
$Anchor = "";
}
if($In::Opt{"OldStyle"}) {
$REMOVED_INTERFACES = "Removed Symbols ($Removed_Number)
\n".$REMOVED_INTERFACES;
}
else {
$REMOVED_INTERFACES = "Removed Symbols $Removed_Number
\n".$REMOVED_INTERFACES;
}
$REMOVED_INTERFACES = $Anchor.$REMOVED_INTERFACES.$TOP_REF."
\n";
}
}
return $REMOVED_INTERFACES;
}
sub getXmlParams($$)
{
my ($Content, $Problem) = @_;
my %XMLparams = ();
foreach my $Attr (sort {$b cmp $a} keys(%{$Problem}))
{
my $Macro = "\@".lc($Attr);
if($Content=~/\Q$Macro\E/)
{
my $Value = $Problem->{$Attr};
if($Attr eq "Param_Pos") {
$Value = showPos($Value);
}
$XMLparams{lc($Attr)} = $Value;
}
}
my @PString = ();
foreach my $P (sort {$b cmp $a} keys(%XMLparams)) {
push(@PString, $P."=\"".xmlSpecChars($XMLparams{$P})."\"");
}
if(@PString) {
return " ".join(" ", @PString);
}
return "";
}
sub addMarkup($)
{
my $Content = $_[0];
# auto-markup
$Content=~s/\n[ ]*//; # spaces
$Content=~s!(\@\w+\s*\(\@\w+\))!$1!g; # @old_type (@old_size)
$Content=~s!(... \(\w+\))!$1!g; # ... (va_list)
$Content=~s!(.+?)!$1!g;
$Content=~s!([2-9]\))!
$1!g; # 1), 2), ...
if($Content=~/\ANOTE:/)
{ # notes
$Content=~s!(NOTE):!$1:!g;
}
else {
$Content=~s!(NOTE):!
$1:!g;
}
$Content=~s! (out)-! $1-!g; # out-parameters
my @Keywords = (
"void",
"const",
"static",
"restrict",
"volatile",
"register",
"virtual"
);
my $MKeys = join("|", @Keywords);
foreach (@Keywords) {
$MKeys .= "|non-".$_;
}
$Content=~s!(added\s*|to\s*|from\s*|became\s*)($MKeys)([^\w-]|\Z)!$1$2$3!ig; # intrinsic types, modifiers
# Markdown
$Content=~s!\*\*([\w\-]+?)\*\*!$1!ig;
$Content=~s!\*([\w\-]+?)\*!$1!ig;
return $Content;
}
sub applyMacroses($$$$)
{
my ($Level, $Kind, $Content, $Problem) = @_;
$Problem->{"Word_Size"} = $In::ABI{2}{"WordSize"};
$Content = addMarkup($Content);
# macros
foreach my $Attr (sort {$b cmp $a} keys(%{$Problem}))
{
my $Macro = "\@".lc($Attr);
my $Value = $Problem->{$Attr};
if(not defined $Value
or $Value eq "") {
next;
}
if(index($Content, $Macro)==-1) {
next;
}
if($Attr eq "Param_Pos") {
$Value = showPos($Value);
}
if($Value=~/\s/) {
$Value = "".specChars($Value)."";
}
elsif($Value=~/\A\d+\Z/
and ($Attr eq "Old_Size" or $Attr eq "New_Size"))
{ # bits to bytes
if($Value % $BYTE)
{ # bits
if($Value==1) {
$Value = "".$Value." bit";
}
else {
$Value = "".$Value." bits";
}
}
else
{ # bytes
$Value /= $BYTE;
if($Value==1) {
$Value = "".$Value." byte";
}
else {
$Value = "".$Value." bytes";
}
}
}
else
{
my $Fmt = "Class|Name|Qual|HTML|Desc";
if($Kind!~/Overridden/) {
$Fmt = "Name|Qual|HTML|Desc";
}
my $V1 = (defined $CompSign{1}{$Value} and defined $CompSign{1}{$Value}{"ShortName"});
my $V2 = (defined $CompSign{2}{$Value} and defined $CompSign{2}{$Value}{"ShortName"});
if($Kind!~/Symbol_Became|Symbol_Changed|Method_Became/
and ($V1 or $V2))
{ # symbols
if($V1) {
$Value = blackName(getSignature($Value, 1, $Fmt));
}
else {
$Value = blackName(getSignature($Value, 2, $Fmt));
}
}
else
{
$Value = "".specChars($Value)."";
}
}
$Content=~s/\Q$Macro\E/$Value/g;
}
if($Content=~/(\A|[^\@\w])\@\w/)
{
if(not $IncompleteRules{$Level}{$Kind})
{ # only one warning
printMsg("WARNING", "incomplete rule \"$Kind\" (\"$Level\")");
$IncompleteRules{$Level}{$Kind} = 1;
}
}
return $Content;
}
sub getReportSymbolProblems($$)
{
my ($TargetSeverity, $Level) = @_;
my $INTERFACE_PROBLEMS = "";
my (%ReportMap, %SymbolChanges) = ();
foreach my $Symbol (sort keys(%{$CompatProblems{$Level}}))
{
my ($SN, $SS, $SV) = symbolParts($Symbol);
if($SV and defined $CompatProblems{$Level}{$SN}) {
next;
}
if(not defined $CompSign{1}{$Symbol})
{ # added symbols
next;
}
my $HeaderName = $CompSign{1}{$Symbol}{"Header"};
if(not $HeaderName) {
$HeaderName = $CompSign{1}{$Symbol}{"Source"};
}
my $DyLib = $In::ABI{1}{"SymLib"}{$Symbol};
if(not $DyLib and my $VSym = $In::ABI{1}{"SymbolVersion"}{$Symbol})
{ # Symbol with Version
$DyLib = $In::ABI{1}{"SymLib"}{$VSym};
}
if(not $DyLib)
{ # const global data
$DyLib = "";
}
if($Level eq "Source" and $In::Opt{"ReportFormat"} eq "html")
{ # do not show library name in HTML report
$DyLib = "";
}
foreach my $Kind (sort keys(%{$CompatProblems{$Level}{$Symbol}}))
{
if($CompatRules{$Level}{$Kind}{"Kind"} eq "Symbols"
and $Kind ne "Added_Symbol" and $Kind ne "Removed_Symbol")
{
my $Severity = $CompatRules{$Level}{$Kind}{"Severity"};
if($Severity eq $TargetSeverity)
{
$SymbolChanges{$Symbol}{$Kind} = $CompatProblems{$Level}{$Symbol}{$Kind};
$ReportMap{$HeaderName}{$DyLib}{$Symbol} = 1;
}
}
}
}
if($In::Opt{"ReportFormat"} eq "xml")
{ # XML
foreach my $HeaderName (sort {lc($a) cmp lc($b)} keys(%ReportMap))
{
$INTERFACE_PROBLEMS .= " \n";
foreach my $DyLib (sort {lc($a) cmp lc($b)} keys(%{$ReportMap{$HeaderName}}))
{
$INTERFACE_PROBLEMS .= " \n";
my @SortedInterfaces = sort {lc($CompSign{1}{$a}{"Unmangled"}) cmp lc($CompSign{1}{$b}{"Unmangled"})} sort {lc($a) cmp lc($b)} keys(%{$ReportMap{$HeaderName}{$DyLib}});
foreach my $Symbol (@SortedInterfaces)
{
$INTERFACE_PROBLEMS .= " \n";
foreach my $Kind (sort keys(%{$SymbolChanges{$Symbol}}))
{
foreach my $Loc (sort keys(%{$SymbolChanges{$Symbol}{$Kind}}))
{
my $ProblemAttr = $SymbolChanges{$Symbol}{$Kind}{$Loc};
$INTERFACE_PROBLEMS .= " \n";
my $Change = $CompatRules{$Level}{$Kind}{"Change"};
$INTERFACE_PROBLEMS .= " $Change\n";
my $Effect = $CompatRules{$Level}{$Kind}{"Effect"};
$INTERFACE_PROBLEMS .= " $Effect\n";
if(my $Overcome = $CompatRules{$Level}{$Kind}{"Overcome"}) {
$INTERFACE_PROBLEMS .= " $Overcome\n";
}
$INTERFACE_PROBLEMS .= " \n";
}
}
$INTERFACE_PROBLEMS .= " \n";
}
$INTERFACE_PROBLEMS .= " \n";
}
$INTERFACE_PROBLEMS .= " \n";
}
$INTERFACE_PROBLEMS = "\n".$INTERFACE_PROBLEMS."\n\n";
}
else
{ # HTML
my $ProblemsNum = 0;
foreach my $HeaderName (sort {lc($a) cmp lc($b)} keys(%ReportMap))
{
foreach my $DyLib (sort {lc($a) cmp lc($b)} keys(%{$ReportMap{$HeaderName}}))
{
my (%NameSpaceSymbols, %NewSignature) = ();
foreach my $Symbol (keys(%{$ReportMap{$HeaderName}{$DyLib}})) {
$NameSpaceSymbols{selectSymbolNs($Symbol, 1)}{$Symbol} = 1;
}
foreach my $NameSpace (sort keys(%NameSpaceSymbols))
{
$INTERFACE_PROBLEMS .= getTitle($HeaderName, $DyLib, $NameSpace);
my @SortedInterfaces = sort {lc($CompSign{1}{$a}{"Unmangled"}) cmp lc($CompSign{1}{$b}{"Unmangled"})} sort {lc($a) cmp lc($b)} keys(%{$NameSpaceSymbols{$NameSpace}});
foreach my $Symbol (@SortedInterfaces)
{
my $SYMBOL_REPORT = "";
my $ProblemNum = 1;
foreach my $Kind (sort keys(%{$SymbolChanges{$Symbol}}))
{
foreach my $Loc (sort keys(%{$SymbolChanges{$Symbol}{$Kind}}))
{
my $ProblemAttr = $SymbolChanges{$Symbol}{$Kind}{$Loc};
if(my $NSign = $ProblemAttr->{"New_Signature"}) {
$NewSignature{$Symbol} = $NSign;
}
if(my $Change = applyMacroses($Level, $Kind, $CompatRules{$Level}{$Kind}{"Change"}, $ProblemAttr))
{
my $Effect = applyMacroses($Level, $Kind, $CompatRules{$Level}{$Kind}{"Effect"}, $ProblemAttr);
$SYMBOL_REPORT .= "\n$ProblemNum | \n".$Change." | \n".$Effect." | \n
\n";
$ProblemNum += 1;
$ProblemsNum += 1;
}
}
}
$ProblemNum -= 1;
if($SYMBOL_REPORT)
{
my $ShowSymbol = highLight_ItalicColor($Symbol, 1);
if($NameSpace)
{
$SYMBOL_REPORT = cutNs($SYMBOL_REPORT, $NameSpace);
$ShowSymbol = cutNs($ShowSymbol, $NameSpace);
}
$INTERFACE_PROBLEMS .= $ContentSpanStart."[+] ".$ShowSymbol;
if($In::Opt{"OldStyle"}) {
$INTERFACE_PROBLEMS .= " ($ProblemNum)";
}
else {
$INTERFACE_PROBLEMS .= " $ProblemNum ";
}
$INTERFACE_PROBLEMS .= $ContentSpanEnd."
\n";
$INTERFACE_PROBLEMS .= $ContentDivStart."\n";
if(my $NSign = $NewSignature{$Symbol})
{ # argument list changed to
$NSign = highLight_ItalicColor($NSign, 2);
if($NameSpace) {
$NSign = cutNs($NSign, $NameSpace);
}
$INTERFACE_PROBLEMS .= "\n⇣\n
\n".$NSign."
\n";
}
if($Symbol=~/\A(_Z|\?)/) {
$INTERFACE_PROBLEMS .= "$Symbol
\n";
}
$INTERFACE_PROBLEMS .= "\n\n | \nChange | \nEffect | \n
\n$SYMBOL_REPORT
\n
\n";
$INTERFACE_PROBLEMS .= $ContentDivEnd;
}
}
$INTERFACE_PROBLEMS .= "
\n";
}
}
}
if($INTERFACE_PROBLEMS)
{
$INTERFACE_PROBLEMS = insertIDs($INTERFACE_PROBLEMS);
my $Title = "Problems with Symbols, $TargetSeverity Severity";
if($TargetSeverity eq "Safe")
{ # Safe Changes
$Title = "Other Changes in Symbols";
}
if($In::Opt{"OldStyle"}) {
$INTERFACE_PROBLEMS = "$Title ($ProblemsNum)
\n".$INTERFACE_PROBLEMS;
}
else {
$INTERFACE_PROBLEMS = "$Title $ProblemsNum
\n".$INTERFACE_PROBLEMS;
}
$INTERFACE_PROBLEMS = "\n".$INTERFACE_PROBLEMS.$TOP_REF."
\n";
}
}
return $INTERFACE_PROBLEMS;
}
sub cutNs($$)
{
my ($N, $Ns) = @_;
$N=~s/\b\Q$Ns\E:://g;
return $N;
}
sub getReportTypeProblems($$)
{
my ($TargetSeverity, $Level) = @_;
my $TYPE_PROBLEMS = "";
my %ReportMap = ();
my %TypeChanges_Sev = ();
foreach my $TypeName (keys(%{$TypeChanges{$Level}}))
{
my $Tid = $TName_Tid{1}{$TypeName};
my $HeaderName = $TypeInfo{1}{$Tid}{"Header"};
if(not $HeaderName) {
$HeaderName = $TypeInfo{1}{$Tid}{"Source"};
}
foreach my $Kind (keys(%{$TypeChanges{$Level}{$TypeName}}))
{
if($CompatRules{$Level}{$Kind}{"Severity"} ne $TargetSeverity) {
next;
}
foreach my $Loc (keys(%{$TypeChanges{$Level}{$TypeName}{$Kind}}))
{
$ReportMap{$HeaderName}{$TypeName} = 1;
$TypeChanges_Sev{$TypeName}{$Kind}{$Loc} = $TypeChanges{$Level}{$TypeName}{$Kind}{$Loc};
}
}
}
if($In::Opt{"ReportFormat"} eq "xml")
{ # XML
foreach my $HeaderName (sort {lc($a) cmp lc($b)} keys(%ReportMap))
{
$TYPE_PROBLEMS .= " \n";
foreach my $TypeName (keys(%{$ReportMap{$HeaderName}}))
{
my (%Kinds_Locations, %Kinds_Target) = ();
$TYPE_PROBLEMS .= " \n";
foreach my $Kind (sort {$b=~/Size/ <=> $a=~/Size/} sort keys(%{$TypeChanges_Sev{$TypeName}}))
{
foreach my $Loc (sort {cmpLocations($b, $a)} sort keys(%{$TypeChanges_Sev{$TypeName}{$Kind}}))
{
$Kinds_Locations{$Kind}{$Loc} = 1;
my $Target = $TypeChanges_Sev{$TypeName}{$Kind}{$Loc}{"Target"};
if($Kinds_Target{$Kind}{$Target}) {
next;
}
$Kinds_Target{$Kind}{$Target} = 1;
my $ProblemAttr = $TypeChanges_Sev{$TypeName}{$Kind}{$Loc};
$TYPE_PROBLEMS .= " \n";
my $Change = $CompatRules{$Level}{$Kind}{"Change"};
$TYPE_PROBLEMS .= " $Change\n";
my $Effect = $CompatRules{$Level}{$Kind}{"Effect"};
$TYPE_PROBLEMS .= " $Effect\n";
if(my $Overcome = $CompatRules{$Level}{$Kind}{"Overcome"}) {
$TYPE_PROBLEMS .= " $Overcome\n";
}
$TYPE_PROBLEMS .= " \n";
}
}
$TYPE_PROBLEMS .= getAffectedSymbols($Level, $TypeName, \%Kinds_Locations);
if($Level eq "Binary" and grep {$_=~/Virtual|Base_Class/} keys(%Kinds_Locations)) {
$TYPE_PROBLEMS .= showVTables($TypeName);
}
$TYPE_PROBLEMS .= " \n";
}
$TYPE_PROBLEMS .= " \n";
}
$TYPE_PROBLEMS = "\n".$TYPE_PROBLEMS."\n\n";
}
else
{ # HTML
my $ProblemsNum = 0;
foreach my $HeaderName (sort {lc($a) cmp lc($b)} keys(%ReportMap))
{
my (%NameSpace_Type) = ();
foreach my $TypeName (keys(%{$ReportMap{$HeaderName}})) {
$NameSpace_Type{selectTypeNs($TypeName, 1)}{$TypeName} = 1;
}
foreach my $NameSpace (sort keys(%NameSpace_Type))
{
$TYPE_PROBLEMS .= getTitle($HeaderName, "", $NameSpace);
my @SortedTypes = sort {lc(showType($a, 0, 1)) cmp lc(showType($b, 0, 1))} keys(%{$NameSpace_Type{$NameSpace}});
foreach my $TypeName (@SortedTypes)
{
my $ProblemNum = 1;
my $TYPE_REPORT = "";
my (%Kinds_Locations, %Kinds_Target) = ();
foreach my $Kind (sort {(index($b, "Size")!=-1) cmp (index($a, "Size")!=-1)} sort keys(%{$TypeChanges_Sev{$TypeName}}))
{
foreach my $Loc (sort {cmpLocations($b, $a)} sort keys(%{$TypeChanges_Sev{$TypeName}{$Kind}}))
{
$Kinds_Locations{$Kind}{$Loc} = 1;
my $Target = $TypeChanges_Sev{$TypeName}{$Kind}{$Loc}{"Target"};
if($Kinds_Target{$Kind}{$Target}) {
next;
}
$Kinds_Target{$Kind}{$Target} = 1;
my $ProblemAttr = $TypeChanges_Sev{$TypeName}{$Kind}{$Loc};
if(my $Change = applyMacroses($Level, $Kind, $CompatRules{$Level}{$Kind}{"Change"}, $ProblemAttr))
{
my $Effect = applyMacroses($Level, $Kind, $CompatRules{$Level}{$Kind}{"Effect"}, $ProblemAttr);
$TYPE_REPORT .= "\n$ProblemNum | \n".$Change." | \n$Effect | \n
\n";
$ProblemNum += 1;
$ProblemsNum += 1;
}
}
}
$ProblemNum -= 1;
if($TYPE_REPORT)
{
my $Affected = getAffectedSymbols($Level, $TypeName, \%Kinds_Locations);
my $ShowVTables = "";
if($Level eq "Binary" and grep {$_=~/Virtual|Base_Class/} keys(%Kinds_Locations)) {
$ShowVTables = showVTables($TypeName);
}
my $ShowType = showType($TypeName, 1, 1);
if($NameSpace)
{
$TYPE_REPORT = cutNs($TYPE_REPORT, $NameSpace);
$ShowType = cutNs($ShowType, $NameSpace);
$Affected = cutNs($Affected, $NameSpace);
$ShowVTables = cutNs($ShowVTables, $NameSpace);
}
$TYPE_PROBLEMS .= $ContentSpanStart."[+] ".$ShowType;
if($In::Opt{"OldStyle"}) {
$TYPE_PROBLEMS .= " ($ProblemNum)";
}
else {
$TYPE_PROBLEMS .= " $ProblemNum ";
}
$TYPE_PROBLEMS .= $ContentSpanEnd;
$TYPE_PROBLEMS .= "
\n".$ContentDivStart."\n";
$TYPE_PROBLEMS .= " | \nChange | \n";
$TYPE_PROBLEMS .= "Effect |
".$TYPE_REPORT."
\n";
$TYPE_PROBLEMS .= $ShowVTables.$Affected."
".$ContentDivEnd."\n";
}
}
$TYPE_PROBLEMS .= "
\n";
}
}
if($TYPE_PROBLEMS)
{
$TYPE_PROBLEMS = insertIDs($TYPE_PROBLEMS);
my $Title = "Problems with Data Types, $TargetSeverity Severity";
if($TargetSeverity eq "Safe")
{ # Safe Changes
$Title = "Other Changes in Data Types";
}
if($In::Opt{"OldStyle"}) {
$TYPE_PROBLEMS = "$Title ($ProblemsNum)
\n".$TYPE_PROBLEMS;
}
else {
$TYPE_PROBLEMS = "$Title $ProblemsNum
\n".$TYPE_PROBLEMS;
}
$TYPE_PROBLEMS = "\n".$TYPE_PROBLEMS.$TOP_REF."
\n";
}
}
return $TYPE_PROBLEMS;
}
sub showType($$$)
{
my ($Name, $Html, $LVer) = @_;
my $TType = $TypeInfo{$LVer}{$TName_Tid{$LVer}{$Name}}{"Type"};
$TType = lc($TType);
if($TType=~/struct|union|enum/) {
$Name=~s/\A\Q$TType\E //g;
}
if($Html) {
$Name = "".$TType." ".specChars($Name);
}
else {
$Name = $TType." ".$Name;
}
return $Name;
}
sub getAnchor($$$)
{
my ($Kind, $Level, $Severity) = @_;
if($In::Opt{"JoinReport"})
{
if($Severity eq "Safe") {
return "Other_".$Level."_Changes_In_".$Kind."s";
}
else {
return $Kind."_".$Level."_Problems_".$Severity;
}
}
else
{
if($Severity eq "Safe") {
return "Other_Changes_In_".$Kind."s";
}
else {
return $Kind."_Problems_".$Severity;
}
}
}
sub showVTables($)
{
my $TypeName = $_[0];
my $TypeId1 = $TName_Tid{1}{$TypeName};
my %Type1 = getType($TypeId1, 1);
if(defined $Type1{"VTable"}
and keys(%{$Type1{"VTable"}}))
{
my $TypeId2 = $TName_Tid{2}{$TypeName};
my %Type2 = getType($TypeId2, 2);
if(defined $Type2{"VTable"}
and keys(%{$Type2{"VTable"}}))
{
my %Indexes = map {$_=>1} (keys(%{$Type1{"VTable"}}), keys(%{$Type2{"VTable"}}));
my %Entries = ();
foreach my $Index (sort {$a<=>$b} (keys(%Indexes)))
{
$Entries{$Index}{"E1"} = simpleVEntry($Type1{"VTable"}{$Index});
$Entries{$Index}{"E2"} = simpleVEntry($Type2{"VTable"}{$Index});
}
my $VTABLES = "";
if($In::Opt{"ReportFormat"} eq "xml")
{ # XML
$VTABLES .= " \n";
foreach my $Index (sort {$a<=>$b} (keys(%Entries)))
{
$VTABLES .= " \n";
$VTABLES .= " ".xmlSpecChars($Entries{$Index}{"E1"})."\n";
$VTABLES .= " ".xmlSpecChars($Entries{$Index}{"E2"})."\n";
$VTABLES .= " \n";
}
$VTABLES .= " \n\n";
}
else
{ # HTML
$VTABLES .= "";
$VTABLES .= "Offset | ";
$VTABLES .= "Virtual Table (Old) - ".(keys(%{$Type1{"VTable"}}))." entries | ";
$VTABLES .= "Virtual Table (New) - ".(keys(%{$Type2{"VTable"}}))." entries |
";
foreach my $Index (sort {$a<=>$b} (keys(%Entries)))
{
my ($Color1, $Color2) = ("", "");
my $E1 = $Entries{$Index}{"E1"};
my $E2 = $Entries{$Index}{"E2"};
if($E1 ne $E2
and $E1!~/ 0x/
and $E2!~/ 0x/)
{
if($Entries{$Index}{"E1"})
{
$Color1 = " class='failed'";
$Color2 = " class='failed'";
}
else {
$Color2 = " class='warning'";
}
}
$VTABLES .= "".$Index." | \n";
$VTABLES .= "".specChars($Entries{$Index}{"E1"})." | \n";
$VTABLES .= "".specChars($Entries{$Index}{"E2"})." |
\n";
}
$VTABLES .= "
\n";
$VTABLES = $ContentDivStart.$VTABLES.$ContentDivEnd;
$VTABLES = $ContentSpanStart_Info."[+] show v-table (old and new)".$ContentSpanEnd."
\n".$VTABLES;
}
return $VTABLES;
}
}
return "";
}
sub simpleVEntry($)
{
my $VEntry = $_[0];
if(not defined $VEntry
or $VEntry eq "") {
return "";
}
$VEntry=~s/ \[.+?\]\Z//; # support for ABI Dumper
$VEntry=~s/\A(.+)::(_ZThn.+)\Z/$2/; # thunks
$VEntry=~s/_ZTI\w+/typeinfo/g; # typeinfo
if($VEntry=~/\A_ZThn.+\Z/) {
$VEntry = "non-virtual thunk";
}
$VEntry=~s/\A\(int \(\*\)\(...\)\)\s*([a-z_])/$1/i;
# support for old GCC versions
$VEntry=~s/\A0u\Z/(int (*)(...))0/;
$VEntry=~s/\A4294967268u\Z/(int (*)(...))-0x000000004/;
$VEntry=~s/\A&_Z\Z/& _Z/;
$VEntry=~s/([^:]+)::\~([^:]+)\Z/~$1/; # destructors
return $VEntry;
}
sub adjustParamPos($$$)
{
my ($Pos, $Symbol, $LVer) = @_;
if(defined $CompSign{$LVer}{$Symbol})
{
if(not $CompSign{$LVer}{$Symbol}{"Static"}
and $CompSign{$LVer}{$Symbol}{"Class"})
{
return $Pos-1;
}
return $Pos;
}
return undef;
}
sub getParamPos($$$)
{
my ($Name, $Symbol, $LVer) = @_;
if(defined $CompSign{$LVer}{$Symbol}
and defined $CompSign{$LVer}{$Symbol}{"Param"})
{
my $Info = $CompSign{$LVer}{$Symbol};
foreach (keys(%{$Info->{"Param"}}))
{
if($Info->{"Param"}{$_}{"name"} eq $Name)
{
return $_;
}
}
}
return undef;
}
sub getParamName($)
{
my $Loc = $_[0];
$Loc=~s/\->.*//g;
return $Loc;
}
sub getAffectedSymbols($$$)
{
my ($Level, $Target_TypeName, $Kinds_Locations) = @_;
my $LIMIT = 10;
if(defined $In::Opt{"AffectLimit"}) {
$LIMIT = $In::Opt{"AffectLimit"};
}
my %SymSel = ();
foreach my $Kind (sort keys(%{$Kinds_Locations}))
{
my @Locs = sort {(index($a, "retval")!=-1) cmp (index($b, "retval")!=-1)} sort {length($a)<=>length($b)} sort keys(%{$Kinds_Locations->{$Kind}});
foreach my $Loc (@Locs)
{
foreach my $Symbol (keys(%{$TypeProblemsIndex{$Level}{$Target_TypeName}{$Kind}{$Loc}}))
{
if(index($Symbol, "_Z")==0
and $Symbol=~/(C4|C2|D4|D2|D0)[EI]/)
{ # duplicated problems for C2/C4 constructors, D2/D4 and D0 destructors
next;
}
if(index($Symbol, "\@")!=-1
or index($Symbol, "\$")!=-1)
{
my ($SN, $SS, $SV) = symbolParts($Symbol);
if($Level eq "Source")
{ # remove symbol version
$Symbol = $SN;
}
if($SV and defined $CompatProblems{$Level}{$SN})
{ # duplicated problems for versioned symbols
next;
}
}
if(not defined $SymSel{$Symbol})
{
$SymSel{$Symbol}{"Kind"} = $Kind;
$SymSel{$Symbol}{"Loc"} = $Loc;
}
}
}
}
my $Affected = "";
my $SNum = 0;
if($In::Opt{"ReportFormat"} eq "xml")
{ # XML
$Affected .= " \n";
foreach my $Symbol (sort {lc($a) cmp lc($b)} keys(%SymSel))
{
my $Kind = $SymSel{$Symbol}{"Kind"};
my $Loc = $SymSel{$Symbol}{"Loc"};
my $PName = getParamName($Loc);
my $Des = getAffectDesc($Level, $Symbol, $Kind, $Loc);
my $Target = "";
if($PName)
{
$Target .= " param=\"$PName\"";
$Des=~s/parameter $PName /parameter \@param /;
}
elsif($Loc=~/\Aretval(\-|\Z)/i) {
$Target .= " affected=\"retval\"";
}
elsif($Loc=~/\Athis(\-|\Z)/i) {
$Target .= " affected=\"this\"";
}
if($Des=~s/\AField ([^\s]+) /Field \@field /) {
$Target .= " field=\"$1\"";
}
$Affected .= " \n";
$Affected .= " ".xmlSpecChars($Des)."\n";
$Affected .= " \n";
if(++$SNum>=$LIMIT) {
last;
}
}
$Affected .= " \n";
}
else
{ # HTML
foreach my $Symbol (sort {lc($a) cmp lc($b)} keys(%SymSel))
{
my $Kind = $SymSel{$Symbol}{"Kind"};
my $Loc = $SymSel{$Symbol}{"Loc"};
my $Des = getAffectDesc($Level, $Symbol, $Kind, $Loc);
my $PName = getParamName($Loc);
my $Pos = adjustParamPos(getParamPos($PName, $Symbol, 1), $Symbol, 1);
$Affected .= "".getSignature($Symbol, 1, "Class|Name|Param|HTML|Italic|Target=".$Pos)."
\n";
$Affected .= "".specChars($Des)."
\n";
if(++$SNum>=$LIMIT) {
last;
}
}
my $Total = keys(%SymSel);
if($Total>$LIMIT) {
$Affected .= " ...\n
\n"; # and others ...
}
$Affected = "".$Affected."
\n";
if($Affected)
{
my $Per = showNum($Total*100/keys(%{$CheckedSymbols{$Level}}));
$Affected = $ContentDivStart.$Affected.$ContentDivEnd;
$Affected = $ContentSpanStart_Affected."[+] affected symbols: $Total ($Per\%)".$ContentSpanEnd.$Affected;
}
}
return $Affected;
}
sub cmpLocations($$)
{
my ($L1, $L2) = @_;
if((index($L2, "retval")==0 or index($L2, "this")==0)
and (index($L1, "retval")!=0 and index($L1, "this")!=0))
{
if(index($L1, "->")==-1) {
return 1;
}
elsif(index($L2, "->")!=-1) {
return 1;
}
}
return 0;
}
sub getAffectDesc($$$$)
{
my ($Level, $Symbol, $Kind, $Loc) = @_;
if($ExtendedSymbols{$Symbol}) {
return "This is a symbol from an external library that may use subject library and change the ABI after recompiling.";
}
my $PAttr = $CompatProblems{$Level}{$Symbol}{$Kind}{$Loc};
$Loc=~s/\A(.*)\-\>(.+?)\Z/$1/; # without the latest affected field
my @Sentence = ();
if($Kind eq "Overridden_Virtual_Method"
or $Kind eq "Overridden_Virtual_Method_B") {
push(@Sentence, "The method '".$PAttr->{"New_Value"}."' will be called instead of this method.");
}
elsif($CompatRules{$Level}{$Kind}{"Kind"} eq "Types")
{
my %SymInfo = %{$CompSign{1}{$Symbol}};
if($Loc eq "this" or $Kind=~/(\A|_)Virtual(_|\Z)/)
{
my $MType = "method";
if($SymInfo{"Constructor"}) {
$MType = "constructor";
}
elsif($SymInfo{"Destructor"}) {
$MType = "destructor";
}
my $ClassName = $TypeInfo{1}{$SymInfo{"Class"}}{"Name"};
if($ClassName eq $PAttr->{"Type_Name"}) {
push(@Sentence, "This $MType is from \'".$PAttr->{"Type_Name"}."\' class.");
}
else {
push(@Sentence, "This $MType is from derived class \'".$ClassName."\'.");
}
}
else
{
my $TypeID = undef;
if($Loc=~/retval/)
{ # return value
if(index($Loc, "->")!=-1) {
push(@Sentence, "Field \'".$Loc."\' in the return value");
}
else {
push(@Sentence, "Return value");
}
$TypeID = $SymInfo{"Return"};
}
elsif($Loc=~/this/)
{ # "this" pointer
if(index($Loc, "->")!=-1) {
push(@Sentence, "Field \'".$Loc."\' in the object of this method");
}
else {
push(@Sentence, "\'this\' pointer");
}
$TypeID = $SymInfo{"Class"};
}
else
{ # parameters
my $PName = getParamName($Loc);
my $PPos = getParamPos($PName, $Symbol, 1);
if(index($Loc, "->")!=-1) {
push(@Sentence, "Field \'".$Loc."\' in ".showPos(adjustParamPos($PPos, $Symbol, 1))." parameter");
}
else {
push(@Sentence, showPos(adjustParamPos($PPos, $Symbol, 1))." parameter");
}
if($PName) {
push(@Sentence, "\'".$PName."\'");
}
$TypeID = $SymInfo{"Param"}{$PPos}{"type"};
}
if($Loc!~/this/)
{
if(my %PureType = getPureType($TypeID, 1))
{
if($PureType{"Type"} eq "Pointer") {
push(@Sentence, "(pointer)");
}
elsif($PureType{"Type"} eq "Ref") {
push(@Sentence, "(reference)");
}
}
}
if($Loc eq "this") {
push(@Sentence, "has base type \'".$PAttr->{"Type_Name"}."\'.");
}
else
{
my $Loc_T = $Loc;
$Loc_T=~s/\A\w+(\->|\Z)//; # location in type
my $TypeID_Problem = $TypeID;
if($Loc_T) {
$TypeID_Problem = getFieldType($Loc_T, $TypeID, 1);
}
if($TypeInfo{1}{$TypeID_Problem}{"Name"} eq $PAttr->{"Type_Name"}) {
push(@Sentence, "is of type \'".$PAttr->{"Type_Name"}."\'.");
}
else {
push(@Sentence, "has base type \'".$PAttr->{"Type_Name"}."\'.");
}
}
}
}
my $Sent = join(" ", @Sentence);
$Sent=~s/->/./g;
if($In::Opt{"ReportFormat"} eq "xml") {
$Sent=~s/'//g;
}
return $Sent;
}
sub getFieldType($$$)
{
my ($Loc, $TypeId, $LVer) = @_;
my @Fields = split(/\->/, $Loc);
foreach my $Name (@Fields)
{
my %Info = getBaseType($TypeId, $LVer);
foreach my $Pos (keys(%{$Info{"Memb"}}))
{
if($Info{"Memb"}{$Pos}{"name"} eq $Name)
{
$TypeId = $Info{"Memb"}{$Pos}{"type"};
last;
}
}
}
return $TypeId;
}
sub writeReport($$)
{
my ($Level, $Report) = @_;
if($In::Opt{"ReportFormat"} eq "xml") {
$Report = "\n".$Report;
}
if($In::Opt{"StdOut"})
{ # --stdout option
print STDOUT $Report;
}
else
{
my $RPath = getReportPath($Level);
mkpath(getDirname($RPath));
open(REPORT, ">", $RPath) || die ("can't open file \'$RPath\': $!\n");
print REPORT $Report;
close(REPORT);
}
}
sub getReport($)
{
my $Level = $_[0];
if($In::Opt{"ReportFormat"} eq "xml")
{ # XML
if($Level eq "Join")
{
my $Report = "\n";
$Report .= getReport("Binary");
$Report .= getReport("Source");
$Report .= "\n";
return $Report;
}
else
{
my $Report = "\n\n";
my ($Summary, $MetaData, $AnyChanged) = getSummary($Level);
$Report .= $Summary."\n";
$Report .= getReportProblems_All($Level);
$Report .= "\n";
return $Report;
}
}
else
{ # HTML
my $CssStyles = readModule("Styles", "Report.css");
my $JScripts = readModule("Scripts", "Sections.js");
if($Level eq "Join")
{
$CssStyles .= "\n".readModule("Styles", "Tabs.css");
$JScripts .= "\n".readModule("Scripts", "Tabs.js");
my $Title = $In::Opt{"TargetTitle"}.": ".$In::Desc{1}{"Version"}." to ".$In::Desc{2}{"Version"}." compatibility report";
my $Keywords = $In::Opt{"TargetTitle"}.", compatibility, API, ABI, report";
my $Des = "API/ABI compatibility report for the ".$In::Opt{"TargetTitle"}." ".$In::Opt{"TargetComponent"}." between ".$In::Desc{1}{"Version"}." and ".$In::Desc{2}{"Version"}." versions";
my ($BSummary, $BMetaData, $BAnyChanged) = getSummary("Binary");
my ($SSummary, $SMetaData, $SAnyChanged) = getSummary("Source");
my $Report = "\n\n".composeHTML_Head($Title, $Keywords, $Des, $CssStyles, $JScripts, ($BAnyChanged or $SAnyChanged))."";
$Report .= getReportTitle("Join")."
";
$Report .= "\n$BSummary\n".getReportProblems_All("Binary").getSourceInfo()."
";
$Report .= "\n$SSummary\n".getReportProblems_All("Source").getSourceInfo()."
";
$Report .= getReportFooter();
$Report .= "\n