1#! /usr/bin/env perl
2# -*- mode: Perl -*-
3# Copyright 2016-2023 The OpenSSL Project Authors. All Rights Reserved.
4#
5# Licensed under the Apache License 2.0 (the "License").  You may not use
6# this file except in compliance with the License.  You can obtain a copy
7# in the file LICENSE in the source distribution or at
8# https://www.openssl.org/source/license.html
9
10use strict;
11use File::Spec::Functions qw(devnull);
12use IPC::Cmd;
13use OpenSSL::Test qw(:DEFAULT srctop_file srctop_dir bldtop_dir bldtop_file);
14use OpenSSL::Test::Utils;
15
16BEGIN {
17    setup("test_symbol_presence");
18}
19
20use lib srctop_dir('Configurations');
21use lib bldtop_dir('.');
22use platform;
23
24plan skip_all => "Test is disabled on NonStop" if config('target') =~ m|^nonstop|;
25# MacOS arranges symbol names differently
26plan skip_all => "Test is disabled on MacOS" if config('target') =~ m|^darwin|;
27plan skip_all => "This is unsupported on platforms that don't have 'nm'"
28    unless IPC::Cmd::can_run('nm');
29
30note
31    "NOTE: developer test!  It's possible that it won't run on your\n",
32    "platform, and that's perfectly fine.  This is mainly for developers\n",
33    "on Unix to check that our shared libraries are consistent with the\n",
34    "ordinals (util/*.num in the source tree), and that our static libraries\n",
35    "don't share symbols, something that should be a good enough check for\n",
36    "the other platforms as well.\n";
37
38my %stlibname;
39my %shlibname;
40my %stlibpath;
41my %shlibpath;
42my %defpath;
43foreach (qw(crypto ssl)) {
44    $stlibname{$_} = platform->staticlib("lib$_");
45    $stlibpath{$_} = bldtop_file($stlibname{$_});
46    $shlibname{$_} = platform->sharedlib("lib$_") unless disabled('shared');
47    $shlibpath{$_} = bldtop_file($shlibname{$_})  unless disabled('shared');
48}
49
50my $testcount
51    =  1                        # Check for static library symbols duplicates
52    ;
53$testcount
54    += (scalar keys %shlibpath) # Check for missing symbols in shared lib
55    unless disabled('shared');
56
57plan tests => $testcount;
58
59######################################################################
60# Collect symbols
61# [3 tests per library]
62
63my %stsymbols;                  # Static library symbols
64my %shsymbols;                  # Shared library symbols
65my %defsymbols;                 # Symbols taken from ordinals
66foreach (sort keys %stlibname) {
67    my $stlib_cmd = "nm -Pg $stlibpath{$_} 2> /dev/null";
68    my $shlib_cmd = "nm -DPg $shlibpath{$_} 2> /dev/null";
69    my @stlib_lines;
70    my @shlib_lines;
71    *OSTDERR = *STDERR;
72    *OSTDOUT = *STDOUT;
73    open STDERR, ">", devnull();
74    open STDOUT, ">", devnull();
75    @stlib_lines = map { s|\R$||; $_ } `$stlib_cmd`;
76    if ($? != 0) {
77        note "running '$stlib_cmd' => $?";
78        @stlib_lines = ();
79    }
80    unless (disabled('shared')) {
81        @shlib_lines = map { s|\R$||; $_ } `$shlib_cmd`;
82        if ($? != 0) {
83            note "running '$shlib_cmd' => $?";
84            @shlib_lines = ();
85        }
86    }
87    close STDERR;
88    close STDOUT;
89    *STDERR = *OSTDERR;
90    *STDOUT = *OSTDOUT;
91
92    my $bldtop = bldtop_dir();
93    my @def_lines;
94    unless (disabled('shared')) {
95        indir $bldtop => sub {
96            my $mkdefpath = srctop_file("util", "mkdef.pl");
97            my $def_path = srctop_file("util", "lib$_.num");
98            my $def_cmd = "$^X $mkdefpath --ordinals $def_path --name $_ --OS linux 2> /dev/null";
99            @def_lines = map { s|\R$||; $_ } `$def_cmd`;
100            if ($? != 0) {
101                note "running 'cd $bldtop; $def_cmd' => $?";
102                @def_lines = ();
103            }
104        }, create => 0, cleanup => 0;
105    }
106
107    note "Number of lines in \@stlib_lines before massaging: ", scalar @stlib_lines;
108    unless (disabled('shared')) {
109        note "Number of lines in \@shlib_lines before massaging: ", scalar @shlib_lines;
110        note "Number of lines in \@def_lines before massaging: ", scalar @def_lines;
111    }
112
113    # Massage the nm output to only contain defined symbols
114    my @arrays = ( \@stlib_lines );
115    push @arrays, \@shlib_lines unless disabled('shared');
116    foreach (@arrays) {
117        my %commons;
118        foreach (@$_) {
119            if (m|^(.*) C .*|) {
120                $commons{$1}++;
121            }
122        }
123        foreach (sort keys %commons) {
124            note "Common symbol: $_";
125        }
126
127        @$_ =
128            sort
129            ( map {
130                  # Drop the first space and everything following it
131                  s| .*||;
132                  # Drop OpenSSL dynamic version information if there is any
133                  s|\@\@.+$||;
134                  # Return the result
135                  $_
136              }
137              # Drop any symbol starting with a double underscore, they
138              # are reserved for the compiler / system ABI and are none
139              # of our business
140              grep !m|^__|,
141              # Only look at external definitions
142              grep m|.* [BDST] .*|,
143              @$_ ),
144            keys %commons;
145    }
146
147    # Massage the mkdef.pl output to only contain global symbols
148    # The output we got is in Unix .map format, which has a global
149    # and a local section.  We're only interested in the global
150    # section.
151    my $in_global = 0;
152    unless (disabled('shared')) {
153        @def_lines =
154            sort
155            map { s|;||; s|\s+||g; $_ }
156            grep { $in_global = 1 if m|global:|;
157                   $in_global = 0 if m|local:|;
158                   $in_global = 0 if m|\}|;
159                   $in_global && m|;|; } @def_lines;
160    }
161
162    note "Number of lines in \@stlib_lines after massaging: ", scalar @stlib_lines;
163    unless (disabled('shared')) {
164
165        note "Number of lines in \@shlib_lines after massaging: ", scalar @shlib_lines;
166        note "Number of lines in \@def_lines after massaging: ", scalar @def_lines;
167    }
168
169    $stsymbols{$_} = [ @stlib_lines ];
170    unless (disabled('shared')) {
171        $shsymbols{$_} = [ @shlib_lines ];
172        $defsymbols{$_} = [ @def_lines ];
173    }
174}
175
176######################################################################
177# Check that there are no duplicate symbols in all our static libraries
178# combined
179# [1 test]
180
181my %symbols;
182foreach (sort keys %stlibname) {
183    foreach (@{$stsymbols{$_}}) {
184        $symbols{$_}++;
185    }
186}
187my @duplicates = sort grep { $symbols{$_} > 1 } keys %symbols;
188if (@duplicates) {
189    note "Duplicates:";
190    note join('\n', @duplicates);
191}
192ok(scalar @duplicates == 0, "checking no duplicate symbols in static libraries");
193
194######################################################################
195# Check that the exported symbols in our shared libraries are consistent
196# with our ordinals files.
197# [1 test per library]
198
199unless (disabled('shared')) {
200    foreach (sort keys %stlibname) {
201        # Maintain lists of symbols that are missing in the shared library,
202        # or that are extra.
203        my @missing = ();
204        my @extra = ();
205
206        my @sh_symbols = ( @{$shsymbols{$_}} );
207        my @def_symbols = ( @{$defsymbols{$_}} );
208
209        while (scalar @sh_symbols || scalar @def_symbols) {
210            my $sh_first = $sh_symbols[0];
211            my $def_first = $def_symbols[0];
212
213            if (!defined($sh_first)) {
214                push @missing, shift @def_symbols;
215            } elsif (!defined($def_first)) {
216                push @extra, shift @sh_symbols;
217            } elsif ($sh_first gt $def_first) {
218                push @missing, shift @def_symbols;
219            } elsif ($sh_first lt $def_first) {
220                push @extra, shift @sh_symbols;
221            } else {
222                shift @def_symbols;
223                shift @sh_symbols;
224            }
225        }
226
227        if (scalar @missing) {
228            note "The following symbols are missing in $_:";
229            foreach (@missing) {
230                note "  $_";
231            }
232        }
233        if (scalar @extra) {
234            note "The following symbols are extra in $_:";
235            foreach (@extra) {
236                note "  $_";
237            }
238        }
239        ok(scalar @missing == 0,
240           "check that there are no missing symbols in $_");
241    }
242}
243