::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 getAffectedSymbols($$$$)
{
my ($Level, $Target_TypeName, $Kinds_Locations, $Syms) = @_;
my $LIMIT = 1000;
if($#{$Syms}>=10000)
{ # reduce size of the report
$LIMIT = 10;
}
my %SProblems = ();
foreach my $Symbol (@{$Syms})
{
if(keys(%SProblems)>$LIMIT) {
last;
}
if(($Symbol=~/C2E|D2E|D0E/))
{ # duplicated problems for C2 constructors, D2 and D0 destructors
next;
}
my ($SN, $SS, $SV) = separate_symbol($Symbol);
if($Level eq "Source")
{ # remove symbol version
$Symbol=$SN;
}
my ($MinPath_Length, $ProblemLocation_Last) = (-1, "");
my $Severity_Max = 0;
my $Signature = get_Signature($Symbol, 1);
foreach my $Kind (keys(%{$CompatProblems{$Level}{$Symbol}}))
{
foreach my $Location (keys(%{$CompatProblems{$Level}{$Symbol}{$Kind}}))
{
if(not defined $Kinds_Locations->{$Kind}
or not $Kinds_Locations->{$Kind}{$Location}) {
next;
}
if($SV and defined $CompatProblems{$Level}{$SN}
and defined $CompatProblems{$Level}{$SN}{$Kind}{$Location})
{ # duplicated problems for versioned symbols
next;
}
my $Type_Name = $CompatProblems{$Level}{$Symbol}{$Kind}{$Location}{"Type_Name"};
next if($Type_Name ne $Target_TypeName);
my $Position = $CompatProblems{$Level}{$Symbol}{$Kind}{$Location}{"Param_Pos"};
my $Param_Name = $CompatProblems{$Level}{$Symbol}{$Kind}{$Location}{"Param_Name"};
my $Severity = getProblemSeverity($Level, $Kind);
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==-1 or ($Path_Length<=$MinPath_Length and $Severity_Val{$Severity}>$Severity_Max)
or (cmp_locations($ProblemLocation, $ProblemLocation_Last) and $Severity_Val{$Severity}==$Severity_Max))
{
$MinPath_Length = $Path_Length;
$Severity_Max = $Severity_Val{$Severity};
$ProblemLocation_Last = $ProblemLocation;
%{$SProblems{$Symbol}} = (
"Descr"=>getAffectDescription($Level, $Symbol, $Kind, $Location),
"Severity_Max"=>$Severity_Max,
"Signature"=>$Signature,
"Position"=>$Position,
"Param_Name"=>$Param_Name,
"Location"=>$Location
);
}
}
}
}
my @Symbols = keys(%SProblems);
@Symbols = sort {lc($tr_name{$a}?$tr_name{$a}:$a) cmp lc($tr_name{$b}?$tr_name{$b}:$b)} @Symbols;
@Symbols = sort {$SProblems{$b}{"Severity_Max"}<=>$SProblems{$a}{"Severity_Max"}} @Symbols;
if($#Symbols+1>$LIMIT)
{ # remove last element
pop(@Symbols);
}
my $Affected = "";
if($ReportFormat eq "xml")
{ # XML
$Affected .= " \n";
foreach my $Symbol (@Symbols)
{
my $Param_Name = $SProblems{$Symbol}{"Param_Name"};
my $Description = $SProblems{$Symbol}{"Descr"};
my $Location = $SProblems{$Symbol}{"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 .= " \n";
$Affected .= " ".xmlSpecChars($Description)."\n";
$Affected .= " \n";
}
$Affected .= " \n";
}
else
{ # HTML
foreach my $Symbol (@Symbols)
{
my $Description = $SProblems{$Symbol}{"Descr"};
my $Signature = $SProblems{$Symbol}{"Signature"};
my $Pos = $SProblems{$Symbol}{"Position"};
$Affected .= "".highLight_Signature_PPos_Italic($Signature, $Pos, 1, 0, 0)."
".htmlSpecChars($Description)."
\n";
}
if(keys(%SProblems)>$LIMIT) {
$Affected .= "and others ...
";
}
$Affected = "".$Affected."
";
if($Affected)
{
$Affected = $ContentDivStart.$Affected.$ContentDivEnd;
$Affected = $ContentSpanStart_Affected."[+] affected symbols (".(keys(%SProblems)>$LIMIT?">".$LIMIT:keys(%SProblems)).")".$ContentSpanEnd.$Affected;
}
}
return $Affected;
}
sub cmp_locations($$)
{
my ($L1, $L2) = @_;
if($L2=~/\b(retval|this)\b/
and $L1!~/\b(retval|this)\b/ and $L1!~/\-\>/) {
return 1;
}
if($L2=~/\b(retval|this)\b/ and $L2=~/\-\>/
and $L1!~/\b(retval|this)\b/ and $L1=~/\-\>/) {
return 1;
}
return 0;
}
sub getAffectDescription($$$$)
{
my ($Level, $Symbol, $Kind, $Location) = @_;
my %Problem = %{$CompatProblems{$Level}{$Symbol}{$Kind}{$Location}};
my $PPos = showPos($Problem{"Param_Pos"});
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}{$Symbol}{"Constructor"}?"constructor":"method";
my $ClassName = $TypeInfo{1}{$CompleteSignature{1}{$Symbol}{"Class"}}{"Name"};
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(my $Init = $Problem{"InitialType_Type"})
{
if($Init eq "Pointer") {
push(@Sentence, "(pointer)");
}
elsif($Init 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(my $Init = $Problem{"InitialType_Type"})
{
if($Init eq "Pointer") {
push(@Sentence, "(pointer)");
}
elsif($Init eq "Ref") {
push(@Sentence, "(reference)");
}
}
}
if($Location eq "this") {
push(@Sentence, "has base type \'".$Problem{"Type_Name"}."\'.");
}
elsif(defined $Problem{"Start_Type_Name"}
and $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($ExtendedSymbols{$Symbol}) {
push(@Sentence, " This is a symbol from an external library that may use the \'$TargetLibraryName\' library and change the 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 $Type = $Info->{"Param"}{$Pos}{"type"};
my $TypeName = $TypeInfo{$LibVersion}{$Type}{"Name"};
foreach my $Typedef (keys(%ChangedTypedef))
{
if(my $Base = $Typedef_BaseName{$LibVersion}{$Typedef}) {
$TypeName=~s/\b\Q$Typedef\E\b/$Base/g;
}
}
$Report .= " \n";
$Report .= " ".$Name."\n";
$Report .= " ".xmlSpecChars($TypeName)."\n";
$Report .= " \n";
}
if(my $Return = $Info->{"Return"})
{
my $RTName = $TypeInfo{$LibVersion}{$Return}{"Name"};
$Report .= " \n";
$Report .= " ".xmlSpecChars($RTName)."\n";
$Report .= " \n";
}
return $Report;
}
sub get_Report_SymbolsInfo($)
{
my $Level = $_[0];
my $Report = "\n";
foreach my $Symbol (sort keys(%{$CompatProblems{$Level}}))
{
my ($SN, $SS, $SV) = separate_symbol($Symbol);
if($SV and defined $CompatProblems{$Level}{$SN}) {
next;
}
$Report .= " \n";
my ($S1, $P1, $S2, $P2) = ();
if(not $AddedInt{$Level}{$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{$Level}{$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 .= " \n";
$Report .= $P1;
$Report .= " \n";
}
if($S2 and $S2 ne $S1)
{
$Report .= " \n";
$Report .= $P2;
$Report .= " \n";
}
$Report .= " \n";
}
$Report .= "\n";
return $Report;
}
sub writeReport($$)
{
my ($Level, $Report) = @_;
if($ReportFormat eq "xml") {
$Report = "\n".$Report;
}
if($StdOut)
{ # --stdout option
print STDOUT $Report;
}
else
{
my $RPath = getReportPath($Level);
mkpath(get_dirname($RPath));
open(REPORT, ">", $RPath) || die ("can't open file \'$RPath\': $!\n");
print REPORT $Report;
close(REPORT);
if($Browse or $OpenReport)
{ # open in browser
openReport($RPath);
if($JoinReport or $DoubleReport)
{
if($Level eq "Binary")
{ # wait to open a browser
sleep(1);
}
}
}
}
}
sub openReport($)
{
my $Path = $_[0];
my $Cmd = "";
if($Browse)
{ # user-defined browser
$Cmd = $Browse." \"$Path\"";
}
if(not $Cmd)
{ # default browser
if($OSgroup eq "macos") {
$Cmd = "open \"$Path\"";
}
elsif($OSgroup eq "windows") {
$Cmd = "start ".path_format($Path, $OSgroup);
}
else
{ # linux, freebsd, solaris
my @Browsers = (
"x-www-browser",
"sensible-browser",
"firefox",
"opera",
"xdg-open",
"lynx",
"links"
);
foreach my $Br (@Browsers)
{
if($Br = get_CmdPath($Br))
{
$Cmd = $Br." \"$Path\"";
last;
}
}
}
}
if($Cmd)
{
if($Debug) {
printMsg("INFO", "running $Cmd");
}
if($OSgroup ne "windows"
and $OSgroup ne "macos")
{
if($Cmd!~/lynx|links/) {
$Cmd .= " >\"/dev/null\" 2>&1 &";
}
}
system($Cmd);
}
else {
printMsg("ERROR", "cannot open report in browser");
}
}
sub getReport($)
{
my $Level = $_[0];
if($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) = get_Summary($Level);
$Report .= $Summary."\n";
$Report .= get_Report_Added($Level).get_Report_Removed($Level);
$Report .= get_Report_Problems("High", $Level).get_Report_Problems("Medium", $Level).get_Report_Problems("Low", $Level).get_Report_Problems("Safe", $Level);
$Report .= get_Report_SymbolsInfo($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 = $TargetLibraryFName.": ".$Descriptor{1}{"Version"}." to ".$Descriptor{2}{"Version"}." compatibility report";
my $Keywords = $TargetLibraryFName.", compatibility, API, report";
my $Description = "Compatibility report for the $TargetLibraryFName $TargetComponent between ".$Descriptor{1}{"Version"}." and ".$Descriptor{2}{"Version"}." versions";
my ($BSummary, $BMetaData) = get_Summary("Binary");
my ($SSummary, $SMetaData) = get_Summary("Source");
my $Report = "\n\n".composeHTML_Head($Title, $Keywords, $Description, $CssStyles, $JScripts)."";
$Report .= get_Report_Header("Join")."
";
$Report .= "\n$BSummary\n".get_Report_Added("Binary").get_Report_Removed("Binary").get_Report_Problems("High", "Binary").get_Report_Problems("Medium", "Binary").get_Report_Problems("Low", "Binary").get_Report_Problems("Safe", "Binary").get_SourceInfo()."
";
$Report .= "\n$SSummary\n".get_Report_Added("Source").get_Report_Removed("Source").get_Report_Problems("High", "Source").get_Report_Problems("Medium", "Source").get_Report_Problems("Low", "Source").get_Report_Problems("Safe", "Source").get_SourceInfo()."
";
$Report .= getReportFooter($TargetLibraryFName, not $JoinReport);
$Report .= "\n\n