1########################################################################### 2# A module with basic functions 3# 4# Copyright (C) 2015-2018 Andrey Ponomarenko's ABI Laboratory 5# 6# Written by Andrey Ponomarenko 7# 8# This library is free software; you can redistribute it and/or 9# modify it under the terms of the GNU Lesser General Public 10# License as published by the Free Software Foundation; either 11# version 2.1 of the License, or (at your option) any later version. 12# 13# This library is distributed in the hope that it will be useful, 14# but WITHOUT ANY WARRANTY; without even the implied warranty of 15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16# Lesser General Public License for more details. 17# 18# You should have received a copy of the GNU Lesser General Public 19# License along with this library; if not, write to the Free Software 20# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 21# MA 02110-1301 USA 22########################################################################### 23use strict; 24 25my %Cache; 26 27my %IntrinsicKeywords = map {$_=>1} ( 28 "true", 29 "false", 30 "_Bool", 31 "_Complex", 32 "const", 33 "int", 34 "long", 35 "void", 36 "short", 37 "float", 38 "volatile", 39 "restrict", 40 "unsigned", 41 "signed", 42 "char", 43 "double", 44 "class", 45 "struct", 46 "union", 47 "enum" 48); 49 50sub initABI($) 51{ 52 my $V = $_[0]; 53 foreach my $K ("SymbolInfo", "TypeInfo", "TName_Tid", "Constants") 54 { 55 if(not defined $In::ABI{$V}{$K}) { 56 $In::ABI{$V}{$K} = {}; 57 } 58 } 59} 60 61sub cmdFind(@) 62{ # native "find" is much faster than File::Find (~6x) 63 # also the File::Find doesn't support --maxdepth N option 64 # so using the cross-platform wrapper for the native one 65 my ($Path, $Type, $Name, $MaxDepth, $UseRegex) = (); 66 67 $Path = shift(@_); 68 if(@_) { 69 $Type = shift(@_); 70 } 71 if(@_) { 72 $Name = shift(@_); 73 } 74 if(@_) { 75 $MaxDepth = shift(@_); 76 } 77 if(@_) { 78 $UseRegex = shift(@_); 79 } 80 81 my $TmpDir = $In::Opt{"Tmp"}; 82 83 if($In::Opt{"OS"} eq "windows") 84 { 85 $Path = getAbsPath($Path); 86 my $Cmd = "cmd /C dir \"$Path\" /B /O"; 87 if($MaxDepth!=1) { 88 $Cmd .= " /S"; 89 } 90 if($Type eq "d") { 91 $Cmd .= " /AD"; 92 } 93 elsif($Type eq "f") { 94 $Cmd .= " /A-D"; 95 } 96 my @Files = split(/\n/, qx/$Cmd/); 97 if($Name) 98 { 99 if(not $UseRegex) 100 { # FIXME: how to search file names in MS shell? 101 # wildcard to regexp 102 $Name=~s/\*/.*/g; 103 $Name='\A'.$Name.'\Z'; 104 } 105 @Files = grep { /$Name/i } @Files; 106 } 107 my @AbsPaths = (); 108 foreach my $File (@Files) 109 { 110 if(not isAbsPath($File)) { 111 $File = join_P($Path, $File); 112 } 113 if($Type eq "f" and not -f $File) 114 { # skip dirs 115 next; 116 } 117 push(@AbsPaths, pathFmt($File)); 118 } 119 if($Type eq "d") { 120 push(@AbsPaths, $Path); 121 } 122 return @AbsPaths; 123 } 124 else 125 { 126 my $FindCmd = "find"; 127 if(not checkCmd($FindCmd)) { 128 exitStatus("Not_Found", "can't find a \"find\" command"); 129 } 130 $Path = getAbsPath($Path); 131 if(-d $Path and -l $Path 132 and $Path!~/\/\Z/) 133 { # for directories that are symlinks 134 $Path.="/"; 135 } 136 my $Cmd = $FindCmd." \"$Path\""; 137 if($MaxDepth) { 138 $Cmd .= " -maxdepth $MaxDepth"; 139 } 140 if($Type) { 141 $Cmd .= " -type $Type"; 142 } 143 if($Name and not $UseRegex) 144 { # wildcards 145 $Cmd .= " -name \"$Name\""; 146 } 147 my $Res = `$Cmd 2>\"$TmpDir/null\"`; 148 if($? and $!) { 149 printMsg("ERROR", "problem with \'find\' utility ($?): $!"); 150 } 151 my @Files = split(/\n/, $Res); 152 if($Name and $UseRegex) 153 { # regex 154 @Files = grep { /$Name/ } @Files; 155 } 156 return @Files; 157 } 158} 159 160sub findLibs($$$) 161{ # FIXME: correct the search pattern 162 my ($Path, $Type, $MaxDepth) = @_; 163 return cmdFind($Path, $Type, '\.'.$In::Opt{"Ext"}.'[0-9.]*\Z', $MaxDepth, 1); 164} 165 166sub getPrefixes($) 167{ 168 my %Prefixes = (); 169 getPrefixes_I([$_[0]], \%Prefixes); 170 return keys(%Prefixes); 171} 172 173sub getPrefixes_I($$) 174{ 175 my $S = "/"; 176 if($In::Opt{"OS"} eq "windows") { 177 $S = "\\"; 178 } 179 180 foreach my $P (@{$_[0]}) 181 { 182 my @Parts = reverse(split(/[\/\\]+/, $P)); 183 my $Name = $Parts[0]; 184 foreach (1 .. $#Parts) 185 { 186 $_[1]->{$Name}{$P} = 1; 187 if($_>4 or $Parts[$_] eq "include") { 188 last; 189 } 190 $Name = $Parts[$_].$S.$Name; 191 } 192 } 193} 194 195sub getCompileCmd($$$$) 196{ 197 my ($Path, $Opt, $Inc, $LVer) = @_; 198 my $GccCall = $In::Opt{"GccPath"}; 199 if($Opt) { 200 $GccCall .= " ".$Opt; 201 } 202 $GccCall .= " -x "; 203 if($In::Opt{"OS"} eq "macos") { 204 $GccCall .= "objective-"; 205 } 206 207 if($In::Opt{"GccMissedMangling"}) 208 { # workaround for GCC 4.8 (C only) 209 $GccCall .= "c++"; 210 } 211 elsif(checkGcc("4")) 212 { # compile as "C++" header 213 # to obtain complete dump using GCC 4.0 214 $GccCall .= "c++-header"; 215 } 216 else 217 { # compile as "C++" source 218 # GCC 3.3 cannot compile headers 219 $GccCall .= "c++"; 220 } 221 if(my $Opts = platformSpecs($LVer)) 222 { # platform-specific options 223 $GccCall .= " ".$Opts; 224 } 225 # allow extra qualifications 226 # and other nonconformant code 227 $GccCall .= " -fpermissive"; 228 $GccCall .= " -w"; 229 if($In::Opt{"NoStdInc"}) 230 { 231 $GccCall .= " -nostdinc"; 232 $GccCall .= " -nostdinc++"; 233 } 234 if(my $Opts = getGccOptions($LVer)) 235 { # user-defined options 236 $GccCall .= " ".$Opts; 237 } 238 $GccCall .= " \"$Path\""; 239 if($Inc) 240 { # include paths 241 $GccCall .= " ".$Inc; 242 } 243 return $GccCall; 244} 245 246sub platformSpecs($) 247{ 248 my $LVer = $_[0]; 249 250 if($In::Opt{"Target"} eq "symbian") 251 { # options for GCCE compiler 252 my @Symbian_Opts = ( 253 "-D__GCCE__", 254 "-DUNICODE", 255 "-fexceptions", 256 "-D__SYMBIAN32__", 257 "-D__MARM_INTERWORK__", 258 "-D_UNICODE", 259 "-D__S60_50__", 260 "-D__S60_3X__", 261 "-D__SERIES60_3X__", 262 "-D__EPOC32__", 263 "-D__MARM__", 264 "-D__EABI__", 265 "-D__MARM_ARMV5__", 266 "-D__SUPPORT_CPP_EXCEPTIONS__", 267 "-march=armv5t", 268 "-mapcs", 269 "-mthumb-interwork", 270 "-DEKA2", 271 "-DSYMBIAN_ENABLE_SPLIT_HEADERS" 272 ); 273 return join(" ", @Symbian_Opts); 274 } 275 elsif($In::Opt{"OS"} eq "windows" 276 and $In::Opt{"GccTarget"}=~/mingw/i) 277 { # add options to MinGW compiler 278 # to simulate the MSVC compiler 279 my @MinGW_Opts = ( 280 "-D__unaligned=\" \"", 281 "-D__nullptr=\"nullptr\"", 282 "-D_WIN32", 283 "-D_STDCALL_SUPPORTED", 284 "-D__int64=\"long long\"", 285 "-D__int32=int", 286 "-D__int16=short", 287 "-D__int8=char", 288 "-D__possibly_notnullterminated=\" \"", 289 "-D__nullterminated=\" \"", 290 "-D__nullnullterminated=\" \"", 291 "-D__assume=\" \"", 292 "-D__w64=\" \"", 293 "-D__ptr32=\" \"", 294 "-D__ptr64=\" \"", 295 "-D__forceinline=inline", 296 "-D__inline=inline", 297 "-D__uuidof(x)=IID()", 298 "-D__try=", 299 "-D__except(x)=", 300 "-D__declspec(x)=__attribute__((x))", 301 "-D__pragma(x)=", 302 "-D_inline=inline", 303 "-D__forceinline=__inline", 304 "-D__stdcall=__attribute__((__stdcall__))", 305 "-D__cdecl=__attribute__((__cdecl__))", 306 "-D__fastcall=__attribute__((__fastcall__))", 307 "-D__thiscall=__attribute__((__thiscall__))", 308 "-D_stdcall=__attribute__((__stdcall__))", 309 "-D_cdecl=__attribute__((__cdecl__))", 310 "-D_fastcall=__attribute__((__fastcall__))", 311 "-D_thiscall=__attribute__((__thiscall__))", 312 "-DSHSTDAPI_(x)=x", 313 "-D_MSC_EXTENSIONS", 314 "-DSECURITY_WIN32", 315 "-D_MSC_VER=1500", 316 "-D_USE_DECLSPECS_FOR_SAL", 317 "-D__noop=\" \"", 318 "-DDECLSPEC_DEPRECATED=\" \"", 319 "-D__builtin_alignof(x)=__alignof__(x)", 320 "-DSORTPP_PASS"); 321 322 if($In::ABI{$LVer}{"Arch"} eq "x86") 323 { 324 push(@MinGW_Opts, "-D_X86_=300"); 325 push(@MinGW_Opts, "-D_M_IX86=300"); 326 } 327 elsif($In::ABI{$LVer}{"Arch"} eq "x86_64") 328 { 329 push(@MinGW_Opts, "-D_AMD64_=300"); 330 push(@MinGW_Opts, "-D_M_AMD64=300"); 331 push(@MinGW_Opts, "-D_M_X64=300"); 332 } 333 elsif($In::ABI{$LVer}{"Arch"} eq "ia64") 334 { 335 push(@MinGW_Opts, "-D_IA64_=300"); 336 push(@MinGW_Opts, "-D_M_IA64=300"); 337 } 338 339 return join(" ", @MinGW_Opts); 340 } 341 return undef; 342} 343 344sub uncoverTypedefs($$) 345{ 346 my ($TypeName, $LVer) = @_; 347 348 if(defined $Cache{"uncoverTypedefs"}{$LVer}{$TypeName}) { 349 return $Cache{"uncoverTypedefs"}{$LVer}{$TypeName}; 350 } 351 my ($TypeName_New, $TypeName_Pre) = (formatName($TypeName, "T"), ""); 352 while($TypeName_New ne $TypeName_Pre) 353 { 354 $TypeName_Pre = $TypeName_New; 355 my $TypeName_Copy = $TypeName_New; 356 my %Words = (); 357 while($TypeName_Copy=~s/\b([a-z_]([\w:]*\w|))\b//io) 358 { 359 if(not $IntrinsicKeywords{$1}) { 360 $Words{$1} = 1; 361 } 362 } 363 foreach my $Word (keys(%Words)) 364 { 365 my $BaseType_Name = $In::ABI{$LVer}{"TypedefBase"}{$Word}; 366 367 next if(not $BaseType_Name); 368 next if($BaseType_Name=~/\b$Word\b/); 369 next if($TypeName_New=~/\b(struct|union|enum)\s\Q$Word\E\b/); 370 371 if($BaseType_Name=~/\([\*]+\)/) 372 { # FuncPtr 373 if($TypeName_New=~/\Q$Word\E(.*)\Z/) 374 { 375 my $Type_Suffix = $1; 376 $TypeName_New = $BaseType_Name; 377 if($TypeName_New=~s/\(([\*]+)\)/($1 $Type_Suffix)/) { 378 $TypeName_New = formatName($TypeName_New, "T"); 379 } 380 } 381 } 382 else 383 { 384 if($TypeName_New=~s/\b\Q$Word\E\b/$BaseType_Name/g) { 385 $TypeName_New = formatName($TypeName_New, "T"); 386 } 387 } 388 } 389 } 390 return ($Cache{"uncoverTypedefs"}{$LVer}{$TypeName} = $TypeName_New); 391} 392 393sub getGccOptions($) 394{ 395 my $LVer = $_[0]; 396 397 my @Opt = (); 398 399 if(my $COpt = $In::Desc{$LVer}{"CompilerOptions"}) 400 { # user-defined options 401 push(@Opt, $COpt); 402 } 403 if($In::Opt{"GccOptions"}) 404 { # additional 405 push(@Opt, $In::Opt{"GccOptions"}); 406 } 407 408 if(@Opt) { 409 return join(" ", @Opt); 410 } 411 412 return undef; 413} 414 415sub setTarget($) 416{ 417 my $Target = $_[0]; 418 419 if($Target eq "default") 420 { 421 $Target = getOSgroup(); 422 423 $In::Opt{"OS"} = $Target; 424 $In::Opt{"Ar"} = getArExt($Target); 425 } 426 427 $In::Opt{"Target"} = $Target; 428 $In::Opt{"Ext"} = getLibExt($Target, $In::Opt{"UseStaticLibs"}); 429} 430 431sub filterFormat($) 432{ 433 my $FiltRef = $_[0]; 434 foreach my $Entry (keys(%{$FiltRef})) 435 { 436 foreach my $Filt (@{$FiltRef->{$Entry}}) 437 { 438 if($Filt=~/[\/\\]/) { 439 $Filt = pathFmt($Filt); 440 } 441 } 442 } 443} 444 445sub checkGcc(@) 446{ 447 my $Req = shift(@_); 448 my $Gcc = $In::Opt{"GccPath"}; 449 450 if(@_) { 451 $Gcc = shift(@_); 452 } 453 454 if(defined $Cache{"checkGcc"}{$Gcc}{$Req}) { 455 return $Cache{"checkGcc"}{$Gcc}{$Req}; 456 } 457 if(my $Ver = dumpVersion($Gcc)) 458 { 459 $Ver=~s/(-|_)[a-z_]+.*\Z//; # remove suffix (like "-haiku-100818") 460 if(cmpVersions($Ver, $Req)>=0) { 461 return ($Cache{"checkGcc"}{$Gcc}{$Req} = $Gcc); 462 } 463 } 464 return ($Cache{"checkGcc"}{$Gcc}{$Req} = ""); 465} 466 467sub dumpVersion($) 468{ 469 my $Cmd = $_[0]; 470 471 if($Cache{"dumpVersion"}{$Cmd}) { 472 return $Cache{"dumpVersion"}{$Cmd}; 473 } 474 my $TmpDir = $In::Opt{"Tmp"}; 475 my $V = `$Cmd -dumpversion 2>\"$TmpDir/null\"`; 476 chomp($V); 477 return ($Cache{"dumpVersion"}{$Cmd} = $V); 478} 479 480sub dumpMachine($) 481{ 482 my $Cmd = $_[0]; 483 484 if($Cache{"dumpMachine"}{$Cmd}) { 485 return $Cache{"dumpMachine"}{$Cmd}; 486 } 487 my $TmpDir = $In::Opt{"Tmp"}; 488 my $Machine = `$Cmd -dumpmachine 2>\"$TmpDir/null\"`; 489 chomp($Machine); 490 return ($Cache{"dumpMachine"}{$Cmd} = $Machine); 491} 492 493return 1; 494