]> git.saurik.com Git - apple/objc4.git/blob - test/verify-exports.pl
objc4-680.tar.gz
[apple/objc4.git] / test / verify-exports.pl
1 #!/usr/bin/perl
2
3 # verify-exports.pl
4 # Check exports in a library vs. declarations in header files.
5 # usage: verify-exports.pl /path/to/dylib /glob/path/to/headers decl-prefix [-arch <arch>] [/path/to/project~dst]
6 # example: verify-exports.pl /usr/lib/libobjc.A.dylib '/usr/{local/,}include/objc/*' OBJC_EXPORT -arch x86_64 /tmp/objc-test.roots/objc-test~dst
7
8 # requirements:
9 # - every export must have an @interface or specially-marked declaration
10 # - every @interface or specially-marked declaration must have an availability macro
11 # - no C++ exports allowed
12
13 use strict;
14 use File::Basename;
15 use File::Glob ':glob';
16
17 my $bad = 0;
18
19 $0 = basename($0, ".pl");
20 my $usage = "/path/to/dylib /glob/path/to/headers decl-prefix [-arch <arch>] [-sdk sdkname] [/path/to/project~dst]";
21
22 my $lib_arg = shift || die "$usage";
23 die "$usage" unless ($lib_arg =~ /^\//);
24 my $headers_arg = shift || die "$usage";
25 my $export_arg = shift || die "$usage";
26
27 my $arch = "x86_64";
28 if ($ARGV[0] eq "-arch") {
29 shift;
30 $arch = shift || die "$0: -arch requires an architecture";
31 }
32 my $sdk = "system";
33 if ($ARGV[0] eq "-sdk") {
34 shift;
35 $sdk = shift || die "$0: -sdk requires an SDK name";
36 }
37
38 my $root = shift || "";
39
40
41 # Collect symbols from dylib.
42 my $lib_path = "$root$lib_arg";
43 die "$0: file not found: $lib_path\n" unless -e $lib_path;
44
45 my %symbols;
46 my @symbollines = `nm -arch $arch '$lib_path'`;
47 die "$0: nm failed: (arch $arch) $lib_path\n" if ($?);
48 for my $line (@symbollines) {
49 chomp $line;
50 (my $type, my $name) = ($line =~ /^[[:xdigit:]]*\s+(.) (.*)$/);
51 if ($type =~ /^[A-TV-Z]$/) {
52 $symbols{$name} = 1;
53 } else {
54 # undefined (U) or non-external - ignore
55 }
56 }
57
58 # Complain about C++ exports
59 for my $symbol (keys %symbols) {
60 if ($symbol =~ /^__Z/) {
61 print "BAD: C++ export '$symbol'\n"; $bad++;
62 }
63 }
64
65
66 # Translate arch to unifdef(1) parameters: archnames, __LP64__, __OBJC2__
67 my @archnames = ("x86_64", "i386", "arm", "armv6", "armv7");
68 my %archOBJC1 = (i386 => 1);
69 my %archLP64 = (x86_64 => 1);
70 my @archparams;
71
72 my $OBJC1 = ($archOBJC1{$arch} && $sdk !~ /^iphonesimulator/);
73
74 if ($OBJC1) {
75 push @archparams, "-U__OBJC2__";
76 } else {
77 push @archparams, "-D__OBJC2__=1";
78 }
79
80 if ($archLP64{$arch}) { push @archparams, "-D__LP64__=1"; }
81 else { push @archparams, "-U__LP64__"; }
82
83 for my $archname (@archnames) {
84 if ($archname eq $arch) {
85 push @archparams, "-D__${archname}__=1";
86 push @archparams, "-D__$archname=1";
87 } else {
88 push @archparams, "-U__${archname}__";
89 push @archparams, "-U__$archname";
90 }
91 }
92
93 # TargetConditionals.h
94 # fixme iphone and simulator
95 push @archparams, "-DTARGET_OS_WIN32=0";
96 push @archparams, "-DTARGET_OS_EMBEDDED=0";
97 push @archparams, "-DTARGET_OS_IPHONE=0";
98 push @archparams, "-DTARGET_OS_MAC=1";
99
100 # Gather declarations from header files
101 # A C declaration starts with $export_arg and ends with ';'
102 # A class declaration is @interface plus the line before it.
103 my $unifdef_cmd = "/usr/bin/unifdef " . join(" ", @archparams);
104 my @cdecls;
105 my @classdecls;
106 for my $header_path(bsd_glob("$root$headers_arg",GLOB_BRACE)) {
107 my $header;
108 # feed through unifdef(1) first to strip decls from other archs
109 # fixme strip other SDKs as well
110 open($header, "$unifdef_cmd < '$header_path' |");
111 my $header_contents = join("", <$header>);
112
113 # C decls
114 push @cdecls, ($header_contents =~ /^\s*$export_arg\s+([^;]*)/msg);
115
116 # ObjC classes, but not categories.
117 # fixme ivars
118 push @classdecls, ($header_contents =~ /^([^\n]*\n\s*\@interface\s+[^(\n]+\n)/mg);
119 }
120
121 # Find name and availability of C declarations
122 my %declarations;
123 for my $cdecl (@cdecls) {
124 $cdecl =~ s/\n/ /mg; # strip newlines
125
126 # Pull availability macro off the end:
127 # __OSX_AVAILABLE_*(*)
128 # AVAILABLE_MAC_OS_X_VERSION_*
129 # OBJC2_UNAVAILABLE
130 # OBJC_HASH_AVAILABILITY
131 # OBJC_MAP_AVAILABILITY
132 # UNAVAILABLE_ATTRIBUTE
133 # (DEPRECATED_ATTRIBUTE is not good enough. Be specific.)
134 my $avail = undef;
135 my $cdecl2;
136 ($cdecl2, $avail) = ($cdecl =~ /^(.*)\s+(__OSX_AVAILABLE_\w+\([a-zA-Z0-9_, ]+\))\s*$/) if (!defined $avail);
137 ($cdecl2, $avail) = ($cdecl =~ /^(.*)\s+(AVAILABLE_MAC_OS_X_VERSION_\w+)\s*$/) if (!defined $avail);
138 ($cdecl2, $avail) = ($cdecl =~ /^(.*)\s+(OBJC2_UNAVAILABLE)\s*$/) if (!defined $avail);
139 ($cdecl2, $avail) = ($cdecl =~ /^(.*)\s+(OBJC_GC_UNAVAILABLE)\s*$/) if (!defined $avail);
140 ($cdecl2, $avail) = ($cdecl =~ /^(.*)\s+(OBJC_ARC_UNAVAILABLE)\s*$/) if (!defined $avail);
141 ($cdecl2, $avail) = ($cdecl =~ /^(.*)\s+(OBJC_HASH_AVAILABILITY)\s*$/) if (!defined $avail);
142 ($cdecl2, $avail) = ($cdecl =~ /^(.*)\s+(OBJC_MAP_AVAILABILITY)\s*$/) if (!defined $avail);
143 ($cdecl2, $avail) = ($cdecl =~ /^(.*)\s+(UNAVAILABLE_ATTRIBUTE)\s*$/) if (!defined $avail);
144 # ($cdecl2, $avail) = ($cdecl =~ /^(.*)\s+(DEPRECATED_\w+)\s*$/) if (!defined $avail);
145 $cdecl2 = $cdecl if (!defined $cdecl2);
146
147 # Extract declaration name (assumes availability macro is already gone):
148 # `(*xxx)` (function pointer)
149 # `xxx(` (function)
150 # `xxx`$` or `xxx[nnn]$` (variable or array variable)
151 my $name = undef;
152 ($name) = ($cdecl2 =~ /^[^(]*\(\s*\*\s*(\w+)\s*\)/) if (!defined $name);
153 ($name) = ($cdecl2 =~ /(\w+)\s*\(/) if (!defined $name);
154 ($name) = ($cdecl2 =~ /(\w+)\s*(?:\[\d*\]\s*)*$/) if (!defined $name);
155
156 if (!defined $name) {
157 print "BAD: unintellible declaration:\n $cdecl\n"; $bad++;
158 } elsif (!defined $avail) {
159 print "BAD: no availability on declaration of '$name':\n $cdecl\n"; $bad++;
160 }
161
162 if ($avail eq "UNAVAILABLE_ATTRIBUTE")
163 {
164 $declarations{$name} = "unavailable";
165 } elsif ($avail eq "OBJC2_UNAVAILABLE" && ! $OBJC1) {
166 # fixme OBJC2_UNAVAILABLE may or may not have an exported symbol
167 # $declarations{$name} = "unavailable";
168 } else {
169 $declarations{"_$name"} = "available";
170 }
171 }
172
173 # Find name and availability of Objective-C classes
174 for my $classdecl (@classdecls) {
175 $classdecl =~ s/\n/ /mg; # strip newlines
176
177 # Pull availability macro off the front:
178 # __OSX_AVAILABLE_*(*)
179 # AVAILABLE_MAC_OS_X_VERSION_*
180 # OBJC2_UNAVAILABLE
181 # OBJC_HASH_AVAILABILITY
182 # OBJC_MAP_AVAILABILITY
183 # UNAVAILABLE_ATTRIBUTE
184 # (DEPRECATED_ATTRIBUTE is not good enough. Be specific.)
185 my $avail = undef;
186 my $classdecl2;
187 ($avail, $classdecl2) = ($classdecl =~ /^\s*(__OSX_AVAILABLE_\w+\([a-zA-Z0-9_, ]+\))\s*(.*)$/) if (!defined $avail);
188 ($avail, $classdecl2) = ($classdecl =~ /^\s*(AVAILABLE_MAC_OS_X_VERSION_\w+)\s*(.*)$/) if (!defined $avail);
189 ($avail, $classdecl2) = ($classdecl =~ /^\s*(OBJC2_UNAVAILABLE)\s*(.*)$/) if (!defined $avail);
190 ($avail, $classdecl2) = ($classdecl =~ /^\s*(OBJC_HASH_AVAILABILITY)\s*(.*)$/) if (!defined $avail);
191 ($avail, $classdecl2) = ($classdecl =~ /^\s*(OBJC_MAP_AVAILABILITY)\s*(.*)$/) if (!defined $avail);
192 ($avail, $classdecl2) = ($classdecl =~ /^\s*(UNAVAILABLE_ATTRIBUTE)\s*(.*)$/) if (!defined $avail);
193 # ($avail, $classdecl2) = ($classdecl =~ /^\s*(DEPRECATED_\w+)\s*(.*)$/) if (!defined $avail);
194 $classdecl2 = $classdecl if (!defined $classdecl2);
195
196 # Extract class name.
197 my $name = undef;
198 ($name) = ($classdecl2 =~ /\@interface\s+(\w+)/);
199
200 if (!defined $name) {
201 print "BAD: unintellible declaration:\n $classdecl\n"; $bad++;
202 } elsif (!defined $avail) {
203 print "BAD: no availability on declaration of '$name':\n $classdecl\n"; $bad++;
204 }
205
206 my $availability;
207 if ($avail eq "UNAVAILABLE_ATTRIBUTE") {
208 $availability = "unavailable";
209 } elsif ($avail eq "OBJC2_UNAVAILABLE" && ! $OBJC1) {
210 # fixme OBJC2_UNAVAILABLE may or may not have an exported symbol
211 # $declarations{$name} = "unavailable";
212 $availability = undef;
213 } else {
214 $availability = "available";
215 }
216
217 if (! $OBJC1) {
218 $declarations{"_OBJC_CLASS_\$_$name"} = $availability;
219 $declarations{"_OBJC_METACLASS_\$_$name"} = $availability;
220 # fixme ivars
221 $declarations{"_OBJC_IVAR_\$_$name.isa"} = $availability if ($name eq "Object");
222 } else {
223 $declarations{".objc_class_name_$name"} = $availability;
224 }
225 }
226
227 # All exported symbols must have an export declaration
228 my @missing_symbols;
229 for my $name (keys %symbols) {
230 my $avail = $declarations{$name};
231 if ($avail eq "unavailable" || !defined $avail) {
232 push @missing_symbols, $name;
233 }
234 }
235 for my $symbol (sort @missing_symbols) {
236 print "BAD: symbol $symbol has no export declaration\n"; $bad++;
237 }
238
239
240 # All export declarations must have an exported symbol
241 my @missing_decls;
242 for my $name (keys %declarations) {
243 my $avail = $declarations{$name};
244 my $hasSymbol = exists $symbols{$name};
245 if ($avail ne "unavailable" && !$hasSymbol) {
246 push @missing_decls, $name;
247 }
248 }
249 for my $decl (sort @missing_decls) {
250 print "BAD: declaration $decl has no exported symbol\n"; $bad++;
251 }
252
253 print "OK: verify-exports\n" unless $bad;
254 exit ($bad ? 1 : 0);