diff options
Diffstat (limited to 'usr/src/tools/scripts/interface_cmp.pl')
| -rw-r--r-- | usr/src/tools/scripts/interface_cmp.pl | 659 |
1 files changed, 659 insertions, 0 deletions
diff --git a/usr/src/tools/scripts/interface_cmp.pl b/usr/src/tools/scripts/interface_cmp.pl new file mode 100644 index 0000000000..e53f19f99a --- /dev/null +++ b/usr/src/tools/scripts/interface_cmp.pl @@ -0,0 +1,659 @@ +#!/usr/bin/perl -w +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# interface_cmp audits two interface definition files (as created by +# interface_check) against one another, and confirms that: +# +# o All versioned libraries that were present in the previous interface +# are present in the new interface +# +# o for each non-private interface in a library confirm that no symbols +# have been removed and that no symbols have been added to it between +# the two revisions +# +# Return codes: +# +# 0 All interfaces in the new release are identical in old release. +# 1 Something is different refer to the error messages. + + +use strict; + +use POSIX qw(getenv); +use Getopt::Std; +use File::Basename; + +#### Define all global variables (required for strict) +use vars qw($Prog); +use vars qw(%opt); +use vars qw(%old_hash %old_alias %new_hash %new_alias); + +# Exception Arrays: +# +# The ADDSYM and DELSYM exceptions are maintained on the @AddSymList +# and @DelSymList arrays, respectively. Each array element is a reference +# to a subarray of triples: +# (sym_re, ver_re, obj_re) +# where each item in the tripple is a regular expression, used to +# match a particular symbol/version/object combination. +# +# The EMPTY_TOPVERSION exceptions are maintained on the @EmptyTopVerList +# array. Each array element is a reference to a subarray of pairs: +# (ver_re, obj_re) +# where each item in the pair is a regular expression, used to +# match a particular version/object combination. +# +use vars qw(@AddSymList @DelSymList @EmptyTopVerList); + + +## LoadExceptions +# +# Locate the exceptions file and process its contents. We can't use +# onbld_elfmod::LoadExceptionsToEXRE() for this, because our exceptions +# need to support more than a single regular expression. +# +# exit: +# @AddSymList, @DelSymList, and @EmptyTopVerList have been updated +# +# note: +# We expand strings of the form MACH(dir) to match the given +# directory as well as any 64-bit architecture subdirectory that +# might be present (i.e. amd64, sparcv9). +# +sub LoadExceptions { + my $file; + my $Line; + my $LineNum = 0; + my $err = 0; + + # Locate the exception file + FILE: { + # If -e is specified, that file must be used + if ($opt{e}) { + $file = $opt{e}; + last FILE; + } + + # If this is an activated workspace, use the exception + # file found in the exceptions_list directory. + if (defined($ENV{CODEMGR_WS})) { + $file = "$ENV{CODEMGR_WS}/exception_lists/interface_cmp"; + last FILE if (-f $file); + } + + # As a final backstop, the SUNWonbld package provides a + # copy of the exception file. This can be useful if we + # are being used with an older workspace. + # + # This script is installed in the SUNWonbld bin directory, + # while the exception file is in etc/exception_lists. Find + # it relative to the script location given by $0. + $file = dirname($0) . "/../etc/exception_lists/interface_cmp"; + last FILE if (-f $file); + + # No exception file was found. + return; + } + + open (EFILE, $file) || + die "$Prog: unable to open exceptions file: $file"; + while ($Line = onbld_elfmod::GetLine(\*EFILE, \$LineNum)) { + + # Expand MACH() + $Line =~ s/MACH\(([^)]+)\)/$1(\/amd64|\/sparcv9)?/g; + + if ($Line =~ /^DELSYM\s+/) { + my ($item, $sym_re, $ver_re, $obj_re) = + split(/\s+/, $Line, 4); + push @DelSymList, [ $sym_re, $ver_re, $obj_re ]; + next; + } + + if ($Line =~ /^ADDSYM\s+/) { + my ($item, $sym_re, $ver_re, $obj_re) = + split(/\s+/, $Line, 4); + push @AddSymList, [ $sym_re, $ver_re, $obj_re ]; + next; + } + + if ($Line =~ /^EMPTY_TOPVERSION\s+/) { + my ($item, $ver_re, $obj_re) = split(/\s+/, $Line, 3); + push @EmptyTopVerList, [ $ver_re, $obj_re ]; + next; + } + + $err++; + printf(STDERR "$file: Unrecognized option: ". + "line $LineNum: $Line\n"); + } + close EFILE; + + exit 1 if ($err != 0); +} + +## ExSym(SymList, sym, ver, obj) +# +# Compare a given symbol/version/object combination against the +# exceptions found in the given list. +# +# entry: +# SymList - Reference to @AddSymList, or @DelSymList. +# sym, ver, obj - Combination to be compared against exception list +# +# exit: +# Returns True (1) if there is a match, and False (0) otherwise. +# +sub ExSym { + my ($SymList, $sym, $ver, $obj) = @_; + + foreach my $ex (@$SymList) { + return 1 if ($obj =~ /$$ex[2]/) && ($ver =~ /$$ex[1]/) && + ($sym =~ /$$ex[0]/); + } + + return 0; +} + +## ExTopVer(ver, obj) +# +# Compare a given version/object combination against the pairs found +# in @EmptyTopVerList. +# +# entry: +# ver, obj - Combination to be compared against empty top version list +# +# exit: +# Returns True (1) if there is a match, and False (0) otherwise. +# +sub ExTopVer { + my ($ver, $obj) = @_; + + foreach my $ex (@EmptyTopVerList) { + return 1 if ($obj =~ /$$ex[1]/) && ($ver =~ /$$ex[0]/); + } + + return 0; +} + +## ExpandInheritance(objhashref) +# +# For each version contained in the specified object hash reference, +# add the inherited symbols. +# +sub ExpandInheritance { + my $obj = $_[0]; + + # Versions to process. Typically, inheriting versions come before + # the versions they inherit. Processing the list in reverse order + # maximizes the odds that a needed sub-version will have already + # have been processed. + my @vers = reverse(@{$obj->{'VERSION_NAMES'}}); + + # Versions to process in the next pass + my @next_vers = (); + + # Hash, indexed by version name, that reflects whether the version + # has been expanded yet or not. + my %done = (); + + while (scalar(@vers) > 0) { + foreach my $name (@vers) { + my $i; + my $defer = 0; + my $cur_version = $obj->{'VERSION_INFO'}{$name}; + my ($top, $direct, $total, $symhash, $inheritarr) = + @{$cur_version}; + + # In order to expand this version, all the inherited + # versions must already have been done. If not, put + # this version on @next_vers for the next pass. + my $num = scalar(@$inheritarr); + for ($i = 0; $i < $num; $i++) { + if (!$done{$inheritarr->[$i]}) { + $defer = 1; + push @next_vers, $name; + last; + } + } + next if ($defer); + + # Add all the symbols from the inherited versions + # to this one. + for ($i = 0; $i < $num; $i++) { + my $i_version = + $obj->{'VERSION_INFO'}{$inheritarr->[$i]}; + my $i_symhash = $i_version->[3]; + + foreach my $sym (keys %$i_symhash) { + if (!defined($cur_version->[3]{$sym})) { + $cur_version->[2]++; + $cur_version->[3]{$sym} = 'INHERIT'; + } + } + } + + $done{$name} = 1; + } + + @vers = @next_vers; + @next_vers = (); + } +} + +## ReadInterface(file, alias) +# +# Read the interface description file, as produced by interface_check, and +# return a hash describing it. +# +# entry: +# file - Interface file to read. +# alias - Refence to hash to be filled in with any aliases +# that are seen in the file. The alias name is the key, +# and the object is the value. +# +# exit: +# The hash referenced by alias has been updated. +# +# The return value is a hash that encapsulates the interface +# information. This hash returned uses the object names as the +# key. Each key references a sub-hash that contains information +# for that object: +# +# CLASS -> ELFCLASS +# TYPE -> ELF type +# VERSION_NAMES -> Reference to array [1..n] of version names, in the +# order they come from the input file. +# VERSION_INFO -> Reference to hash indexed by version name, yielding +# a reference to an array containing information about +# that version. +# +# The arrays referenced via VERSION_INFO are of the form: +# +# (top, new, total, symhashref, inheritarrref) +# +# where: +# top - 1 if version is a TOP_VERSION, 0 for a regular VERSION +# new - Number of symbols defined explicitly by version +# total - Number of symbols included in version, both new, +# and via inheritance. +# symhashref - Reference to hash indexed by symbol names, and +# yielding true (1). +# inheritarrref - Reference to array of names of versions +# inherited by this one. +# +sub ReadInterface { + my ($file, $alias) = @_; + my %main_hash = (); + my $Line; + my $LineNum = 0; + my $obj_name; + my $obj_hash; + my $sym_ok = 0; + my $cur_version; + + open(FILE, $file) || die "$Prog: Unable to open: $file"; + + # Until we see an OBJECT line, nothing else is valid. To + # simplify the error handling, use a simple initial loop to + # read the file up to that point + while ($Line = onbld_elfmod::GetLine(\*FILE, \$LineNum)) { + if ($Line =~ s/^OBJECT\s+//i) { + $obj_name = $Line; + $main_hash{$obj_name} = {}; + $obj_hash = $main_hash{$obj_name}; + last; + } + die "$file: OBJECT expected on line $LineNum: $Line\n"; + } + + # Read the remainder of the file + while ($Line = onbld_elfmod::GetLine(\*FILE, \$LineNum)) { + # Items are parsed in order of decreasing frequency + + if ($Line =~ + /^SYMBOL\s+([^\s]+)$/i) { + my $sym = $1; + + die "$file: SYMBOL not expected on line $LineNum: $Line\n" + if !$sym_ok; + + $cur_version->[1]++; + $cur_version->[2]++; + $cur_version->[3]{$sym} = 'NEW'; + next; + } + + if ($Line =~ /^((TOP_)?VERSION)\s+([^\s]+)(\s+\{(.*)\})?\s*$/i) { + my ($top, $name, $inherit) = ($2, $3, $5); + + $top = defined($top) ? 1 : 0; + + my @inheritarr = defined($inherit) ? + split /[,{\s]+/, $inherit : (); + + $cur_version = [ $top, 0, 0, {}, \@inheritarr ]; + $obj_hash->{'VERSION_INFO'}{$name} = $cur_version; + + push @{$obj_hash->{'VERSION_NAMES'}}, $name; + $sym_ok = 1; + next; + } + + if ($Line =~ /^OBJECT\s+([^\s]+)$/i) { + my $prev_obj_hash = $obj_hash; + $obj_name = $1; + $main_hash{$obj_name} = {}; + $obj_hash = $main_hash{$obj_name}; + + # Expand the versions for the object just processed + ExpandInheritance($prev_obj_hash); + next; + } + + if ($Line =~ /^CLASS\s+([^\s]+)$/i) { + $obj_hash->{'CLASS'} = $1; + next; + } + + if ($Line =~ /^TYPE\s+([^\s]+)$/i) { + $obj_hash->{'TYPE'} = $1; + next; + } + + if ($Line =~ /^ALIAS\s+([^\s]+)$/i) { + $$alias{$1} = $obj_name; + next; + } + + die "$file: unrecognized item on line $LineNum: $Line\n"; + } + close FILE; + + # Expand the versions for the final object from the file + ExpandInheritance($obj_hash); + + return %main_hash; +} + +## PrintInterface(main_hash, alias) +# +# Dump the contents of main_hash and alias to stdout in the same format +# used by interface_check to produce the input interface file. This output +# should diff cleanly against the original (ignoring the header comments). +# +sub PrintInterface { + my ($main_hash, $alias_hash) = @_; + + foreach my $obj (sort keys %$main_hash) { + print "OBJECT\t$obj\n"; + print "CLASS\t$main_hash->{$obj}{'CLASS'}\n"; + print "TYPE\t$main_hash->{$obj}{'TYPE'}\n"; + + # This is inefficient, but good enough for debugging + # Look at all the aliases and print those that belong + # to this object. + foreach my $alias (sort keys %$alias_hash) { + print "ALIAS\t$alias\n" + if ($obj eq $alias_hash->{$alias}); + } + + next if !defined($main_hash->{$obj}{'VERSION_NAMES'}); + + my $num = scalar(@{$main_hash->{$obj}{'VERSION_NAMES'}}); + my $i; + for ($i = 0; $i < $num; $i++) { + my $name = $main_hash->{$obj}{'VERSION_NAMES'}[$i]; + my ($top, $direct, $total, $symhash, $inheritarr) = + @{$main_hash->{$obj}{'VERSION_INFO'}{$name}}; + + $top = $top ? "TOP_" : ''; + + my $inherit = (scalar(@$inheritarr) > 0) ? + "\t{" . join(', ', @{$inheritarr}) . "}" : ''; + + print "${top}VERSION\t$name$inherit\n"; + + foreach my $sym (sort keys %$symhash) { + print "\t$symhash->{$sym}\t$sym\n"; + } + } + } +} + +## compare() +# +# Compare the old interface definition contained in (%old_hash, %old_alias) +# with the new interface contained in (%new_hash, %new_alias). +# +sub compare { + foreach my $old_obj (sort keys %old_hash) { + my $new_obj = $old_obj; + my $Ttl = 0; + + # If the object does not exist in the new interface, + # then see if there's an alias for it. Failing that, + # we simply ignore the object. + if (!defined($new_hash{$new_obj})) { + next if !defined($new_alias{$new_obj}); + $new_obj = $new_alias{$new_obj}; + } + + my $old = $old_hash{$old_obj}; + my $new = $new_hash{$new_obj}; + + # Every version in the old object must exist in the new object, + # and there must be exactly the same symbols in each. + my $num = scalar(@{$old->{'VERSION_NAMES'}}); + for (my $i = 0; $i < $num; $i++) { + my $name = $old->{'VERSION_NAMES'}[$i]; + + # New object must have this version + if (!defined($new->{'VERSION_INFO'}{$name})) { + onbld_elfmod::OutMsg2(\*STDOUT, \$Ttl, $old_obj, + $new_obj, "$name: deleted version"); + next; + } + + my ($old_top, $old_direct, $old_total, $old_symhash) = + @{$old->{'VERSION_INFO'}{$name}}; + my ($new_top, $new_direct, $new_total, $new_symhash) = + @{$new->{'VERSION_INFO'}{$name}}; + + # If this is an empty top version, and the old object + # has the EMPTY_TOPVERSION exception set, then we + # skip it as if it were not present. + next if $old_top && ($old_direct == 0) && + ExTopVer($name, $old_obj); + + # We check that every symbol in the old object is + # in the new one to detect deleted symbols. We then + # check that every symbol in the new object is also + # in the old object, to find added symbols. If the + # "deleted" check is clean, and the two objects have + # the same number of symbols in their versions, then we + # can skip the "added" test, because we know that + # there is no room for an addition to have happened. + # Since most objects satisfy these constraints, we + # end up doing roughly half the number of comparisons + # that would otherwise be needed. + my $check_added_syms = + ($old_total == $new_total) ? 0: 1; + + # Every symbol in the old version must be in the new one + foreach my $sym (sort keys %$old_symhash) { + if (!defined($new_symhash->{$sym})) { + onbld_elfmod::OutMsg2(\*STDOUT, + \$Ttl, $old_obj, $new_obj, + "$name: deleted interface: $sym") + if !ExSym(\@DelSymList, + $sym, $name, $new_obj); + $check_added_syms = 1; + } + } + + # Do the "added" check, unless we can optimize it away. + # Every symbol in the new version must be in the old one. + if ($check_added_syms) { + foreach my $sym (sort keys %$new_symhash) { + if (!defined($old_symhash->{$sym})) { + next if ExSym(\@AddSymList, + $sym, $name, $new_obj); + onbld_elfmod::OutMsg2(\*STDOUT, + \$Ttl, $old_obj, $new_obj, + "$name: added interface: $sym"); + } + } + } + + # We want to ensure that version numbers in an + # inheritance chain don't go up by more than 1 in + # any given release. If the version names are in the + # standard SUNW_x.y[.z] format, we can compare the + # two top versions and see if this has happened. + # + # For a given SUNW_x.y[.z], valid sucessors would + # be SUNW_x.(y+1) or SUNW_x.y.(z+1), where z is + # assumed to be 0 if not present. + # + # This check only makes sense when the new interface + # is a direct decendent of the old one, as specified + # via the -d option. If the two interfaces are more + # than one release apart, we should not do this test. + if ($opt{d} && $old_top && !$new_top && + ($name =~ /^SUNW_(\d+)\.(\d+)(\.(\d+))?/)) { + my $iname1 = "SUNW_$1." . ($2 + 1); + my $iname2; + if (defined($4)) { + $iname2 = "SUNW_$1.$2." . ($4 + 1); + } else { + $iname2 = "SUNW_$1.$2.1"; + } + + if (defined($new->{'VERSION_INFO'}{$iname1}) || + defined($new->{'VERSION_INFO'}{$iname2})) { + my $i_top = + $new->{'VERSION_INFO'}{$iname1}[0] || + $new->{'VERSION_INFO'}{$iname2}[0]; + if (!$i_top) { + onbld_elfmod::OutMsg2(\*STDOUT, + \$Ttl, $old_obj, $new_obj, + "$name: inconsistant " . + "version increment: " . + "expect $iname1 or $iname2 ". + "to replace top version"); + } + } else { + onbld_elfmod::OutMsg2(\*STDOUT, + \$Ttl, $old_obj, $new_obj, + "$name: expected superseding " . + "top version to $name not " . + "present: $iname1 or $iname2"); + } + } + } + + + # Empty versions in the established interface description + # are usually the result of fixing a versioning mistake + # at some point in the past. These versions are part of + # the public record, and cannot be changed now. However, if + # comparing two interface descriptions from the same gate, + # flag any empty versions in the new interface description + # that are not present in the old one. These have yet to + # become part of the official interface, and should be removed + # before they do. + next if !$opt{d}; + + $num = scalar(@{$new->{'VERSION_NAMES'}}); + for (my $i = 0; $i < $num; $i++) { + my $name = $new->{'VERSION_NAMES'}[$i]; + + # If old object has this version, skip it + next if defined($old->{'VERSION_INFO'}{$name}); + + # If explicitly whitelisted, skip it + next if ExTopVer($name, $new_obj); + + my ($new_top, $new_direct, $new_total, $new_symhash) = + @{$new->{'VERSION_INFO'}{$name}}; + + if ($new_direct == 0) { + onbld_elfmod::OutMsg2(\*STDOUT, + \$Ttl, $old_obj, $new_obj, + "$name: invalid empty new version"); + } + } + } + +} + + + +# ----------------------------------------------------------------------------- + +# Establish a program name for any error diagnostics. +chomp($Prog = `basename $0`); + +# The onbld_elfmod package is maintained in the same directory as this +# script, and is installed in ../lib/perl. Use the local one if present, +# and the installed one otherwise. +my $moddir = dirname($0); +$moddir = "$moddir/../lib/perl" if ! -f "$moddir/onbld_elfmod.pm"; +require "$moddir/onbld_elfmod.pm"; + +# Check that we have arguments. Normally, 2 plain arguments are required, +# but if -t is present, only one is allowed. +if ((getopts('de:ot', \%opt) == 0) || (scalar(@ARGV) != ($opt{t} ? 1 : 2))) { + print "usage: $Prog [-dot] [-e exfile] old new\n"; + print "\t[-d]\t\tnew is a direct decendent of old\n"; + print "\t[-e exfile]\texceptions file\n"; + print "\t[-o]\t\tproduce one-liner output (prefixed with pathname)\n"; + print "\t[-t]\tParse old, and recreate to stdout\n"; + exit 1; +} + +# Locate and process the exceptions file +LoadExceptions(); + +%old_alias = (); +%old_hash = ReadInterface($ARGV[0], \%old_alias); + +# If -t is present, only one argument is allowed --- we parse it, and then +# print the same information back to stderr in the same format as the original. +# This is useful for debugging, to verify that the parsing is correct. +if ($opt{t}) { + PrintInterface(\%old_hash, \%old_alias); + exit 0; +} + +%new_alias = (); +%new_hash = ReadInterface($ARGV[1], \%new_alias); + +compare(); + +exit 0; |
