#!/usr/bin/perl

use strict;
use warnings;

sub cmd2arr($\@) {
  my ($cmd, $arr_r) = @_;
  print STDERR "[Action: '$cmd']\n";
  open(CMD, $cmd.'|') || die "can't execute '$cmd' (Reason: $!)";
  my $line;
  while (defined($line=<CMD>)) {
    chomp($line);
    push @$arr_r, $line;
  }
  close(CMD) || die "error $! reported by command '$cmd'";
}

my $COMMON_NM_OPTIONS = "--demangle --format=posix";

sub list_undefined($\@) {
  my ($source, $arr_r) = @_;
  my $cmd = "nm $COMMON_NM_OPTIONS --undefined-only $source";
  cmd2arr($cmd,@$arr_r);
}

sub list_defined($\@) {
  my ($source, $arr_r) = @_;
  my $cmd = "nm $COMMON_NM_OPTIONS --defined-only --extern $source";
  cmd2arr($cmd,@$arr_r);
}

sub die_usage($) {
  my ($msg) = @_;

  print STDERR "Usage: filter_defined_symbols.pl OBJECT1 OBJECT2 [ ... OBJECTN ]\n";
  print STDERR "Test whether OBJECT2 ... OBJECTN define all symbols which are required by (=undefined in) OBJECT1\n";
  print STDERR "List all symbols which are required by (=undefined in) OBJECT1 and\n";
  print STDERR "are not defined in OBJECT2 ... OBJECTN\n";

  die $msg."\n";
}

sub eliminate_defined(\@\@\@) {
  my ($undefined_r, $defined_r, $notresolved_r) = @_;

  my %undefined = map { if (/ U /o) { $` => $_; } else { ; } } @$undefined_r;
  die "nothing undefined (probably broken)" if not scalar(%undefined);

  my %defined = ();
  foreach (@$defined_r) {
    if (/ [TB] /o) {
      my $sym = $`;
      if (exists $undefined{$sym}) {
        $defined{$sym} = 1;
      }
    }
  }

  foreach (keys %undefined) {
    if (not exists $defined{$_}) {
      push @$notresolved_r, $undefined{$_};
    }
  }

}

sub filter_defined_symbols() {
  my $args = scalar(@ARGV);
  if ($args < 2) {
    die_usage("Missing arguments");
  }
  else {
    my $OBJECT1 = shift @ARGV;
    my $OBJECT2 = shift @ARGV;

    my @undefined = (); # undefined symbols from OBJECT1
    my @defined = ();   # defined symbols from OBJECT2

    list_undefined($OBJECT1, @undefined);
    list_defined($OBJECT2, @defined);

    my @notresolved = ();

    eliminate_defined(@undefined, @defined, @notresolved);

    while (scalar(@ARGV)) {
      my $OBJECTN = shift @ARGV;

      if (not scalar(@notresolved)) {
        die "no notresolved symbols left (before considering '$OBJECTN')";
      }

      @undefined = @notresolved;
      @defined = ();

      list_defined($OBJECTN, @defined);

      @notresolved = ();
      eliminate_defined(@undefined, @defined, @notresolved);
    }

    foreach (sort @notresolved) {
      print $_."\n";
    }
  }
}

filter_defined_symbols();
