sub detectPublicSymbols()

in src/main/abi-symbols/abi-dumper.pl [6269:6690]


sub detectPublicSymbols($)
{
    my $Path = $_[0];
    
    if(not -e $Path) {
        exitStatus("Access_Error", "can't access \'$Path\'");
    }
    
    my $Path_A = abs_path($Path);
    
    printMsg("INFO", "Detect public symbols");
    
    if($UseTU)
    {
        if(not checkCmd($GPP)) {
            exitStatus("Not_Found", "can't find \"$GPP\"");
        }
    }
    else
    {
        if(not checkCmd($CTAGS)) {
            exitStatus("Not_Found", "can't find \"$CTAGS\"");
        }
        
        if(my $CtagsVer = `$CTAGS --version 2>&1`)
        {
            if($CtagsVer!~/Universal/i)
            {
                printMsg("ERROR", "requires Universal Ctags to work properly");
                if($CtagsVer=~/Exuberant/i) {
                    $EXUBERANT_CTAGS = 1;
                }
            }
        }
    }
    
    $PublicSymbols_Detected = 1;
    
    my @Files = ();
    my @Headers = ();
    my @DefaultInc = ();
    
    if($PublicHeadersIsDir)
    { # directory
        @Files = findFiles($Path, "f");
        
        foreach my $File (@Files)
        {
            if(isHeader($File)) {
                push(@Headers, $File);
            }
        }
        
        push(@DefaultInc, $Path_A);
        
        if(-d $Path_A."/include") {
            push(@DefaultInc, $Path_A."/include");
        }
    }
    else
    { # list of headers
        @Headers = split(/\n/, readFile($Path));
    }
    
    if(not @Headers) {
        exitStatus("Error", "headers not found in \'$Path\'");
    }
    
    my $PublicHeader_F = $CacheHeaders."/PublicHeader.data";
    my $SymbolToHeader_F = $CacheHeaders."/SymbolToHeader.data";
    my $TypeToHeader_F = $CacheHeaders."/TypeToHeader.data";
    my $Path_F = $CacheHeaders."/PATH";
    
    if($CacheHeaders
    and -f $PublicHeader_F
    and -f $SymbolToHeader_F
    and -f $TypeToHeader_F
    and -f $Path_F)
    {
        if(readFile($Path_F) eq $Path_A)
        {
            %PublicHeader = %{eval(readFile($PublicHeader_F))};
            %SymbolToHeader = %{eval(readFile($SymbolToHeader_F))};
            %TypeToHeader = %{eval(readFile($TypeToHeader_F))};
            
            return;
        }
    }
    
    foreach my $File (@Headers)
    {
        $PublicHeader{getFilename($File)} = 1;
    }
    
    my $Is_C = ($OBJ_LANG eq "C");
    
    my @Langs = undef;
    
    if($EXUBERANT_CTAGS)
    {
        @Langs = ("C++");
        if($Is_C) {
            @Langs = ("C");
        }
    }
    else
    {
        @Langs = ("C++", "OldC++");
        if($Is_C) {
            @Langs = ("C", "OldC");
        }
    }
    
    @Headers = sort {length($b)<=>length($a)} sort {lc($b) cmp lc($a)} @Headers;
    
    foreach my $File (@Headers)
    {
        my $HName = getFilename($File);
        
        if($UseTU)
        {
            my $TmpDir = $TMP_DIR."/tu";
            if(not -d $TmpDir) {
                mkpath($TmpDir);
            }
            
            my $File_A = abs_path($File);
            
            my $IncDir = getDirname($File_A);
            my $IncDir_O = getDirname($IncDir);
            
            my $TmpInc = $TmpDir."/tmp-inc.h";
            my $TmpContent = "";
            if($IncludeDefines)
            {
                foreach my $D (split(/;/, $IncludeDefines)) {
                    $TmpContent = "#define $D\n";
                }
            }
            if($IncludePreamble)
            {
                foreach my $P (split(/;/, $IncludePreamble))
                {
                    if($P=~/\A\//) {
                        $TmpContent = "#include \"".$P."\"\n";
                    }
                    else {
                        $TmpContent = "#include <".$P.">\n";
                    }
                }
            }
            $TmpContent .= "#include \"$File_A\"\n";
            writeFile($TmpInc, $TmpContent);
            
            my $Cmd = $GPP." -w -fpermissive -fdump-translation-unit -fkeep-inline-functions -c \"$TmpInc\"";
            
            if(defined $IncludePaths)
            {
                foreach my $P (split(/;/, $IncludePaths))
                {
                    if($P!~/\A\//) {
                        $P = $Path_A."/".$P;
                    }
                    
                    $Cmd .= " -I\"".$P."\"";
                }
            }
            else
            { # automatic
                $Cmd .= " -I\"$IncDir\" -I\"$IncDir_O\"";
            }
            
            foreach my $P (@DefaultInc) {
                $Cmd .= " -I\"$P\"";
            }
            
            $Cmd .= " -o ./a.out >OUT 2>&1";
            
            chdir($TmpDir);
            system($Cmd);
            chdir($ORIG_DIR);
            
            my $TuDump = $TmpDir."/tmp-inc.h.001t.tu";
            my $Errors = $TmpDir."/OUT";
            
            if(not -e $TuDump)
            {
                printMsg("ERROR", "failed to list symbols in the header \'$HName\'");
                if($Debug) {
                    printMsg("ERROR", readFile($Errors));
                }
                next;
            }
            elsif($?)
            {
                printMsg("ERROR", "some errors occured when compiling header \'$HName\'");
                if($Debug) {
                    printMsg("ERROR", readFile($Errors));
                }
            }
            
            my (%Fdecl, %Tdecl, %Tname, %Ident, %NotDecl) = ();
            my $Content = readFile($TuDump);
            $Content=~s/\n[ ]+/ /g;
            
            my @Lines = split(/\n/, $Content);
            foreach my $N (0 .. $#Lines)
            {
                my $Line = $Lines[$N];
                if(index($Line, "function_decl")!=-1
                or index($Line, "var_decl")!=-1)
                {
                    if($Line=~/name: \@(\d+)/)
                    {
                        my $Id = $1;
                        
                        if($Line=~/srcp: ([^:]+)\:\d/)
                        {
                            if(defined $PublicHeader{$1}) {
                                $Fdecl{$Id} = $1;
                            }
                        }
                    }
                }
                elsif($Line=~/\@(\d+)\s+identifier_node\s+strg:\s+(\w+)/)
                {
                    $Ident{$1} = $2;
                }
                elsif($Is_C)
                {
                    if(index($Line, "type_decl")!=-1)
                    {
                        if($Line=~/\A\@(\d+)/)
                        {
                            my $Id = $1;
                            if($Line=~/name: \@(\d+)/)
                            {
                                my $NId = $1;
                                
                                if($Line=~/srcp: ([^:]+)\:\d/)
                                {
                                    if(defined $PublicHeader{$1})
                                    {
                                        $Tdecl{$Id} = $1;
                                        $Tname{$Id} = $NId;
                                    }
                                }
                            }
                        }
                    }
                    elsif(index($Line, "record_type")!=-1
                    or index($Line, "union_type")!=-1)
                    {
                        if($Line!~/ flds:/)
                        {
                            if($Line=~/name: \@(\d+)/)
                            {
                                $NotDecl{$1} = 1;
                            }
                        }
                    }
                    elsif(index($Line, "enumeral_type")!=-1)
                    {
                        if($Line!~/ csts:/)
                        {
                            if($Line=~/name: \@(\d+)/)
                            {
                                $NotDecl{$1} = 1;
                            }
                        }
                    }
                    elsif(index($Line, "integer_type")!=-1)
                    {
                        if($Line=~/name: \@(\d+)/)
                        {
                            $NotDecl{$1} = 1;
                        }
                    }
                }
            }
            
            foreach my $Id (keys(%Fdecl))
            {
                if(my $Name = $Ident{$Id}) {
                    $SymbolToHeader{$Name}{$Fdecl{$Id}} = 1;
                }
            }
            
            if($Is_C)
            {
                foreach my $Id (keys(%Tdecl))
                {
                    if(defined $NotDecl{$Id}) {
                        next;
                    }
                    
                    if(my $Name = $Ident{$Tname{$Id}}) {
                        $TypeToHeader{$Name} = $Tdecl{$Id};
                    }
                }
            }
            
            unlink($TuDump);
        }
        else
        { # using Ctags
            my $IgnoreTags = "";
            
            if(defined $IgnoreTagsPath) {
                $IgnoreTags .= " -I \@".$IgnoreTagsPath;
            }
            
            if(@CtagsDef)
            {
                foreach my $Def (@CtagsDef) {
                    $IgnoreTags .= " -D '".$Def."'";
                }
            }
            
            foreach my $Lang (@Langs)
            {
                my $List_S = `$CTAGS -x --$Lang-kinds=fpvxd --languages=+$Lang --language-force=$Lang $IgnoreTags \"$File\"`;
                foreach my $Line (split(/\n/, $List_S))
                {
                    if($Line=~/\A(\w+)\s+(\w+)/) {
                        $SymbolToHeader{$1}{$HName} = $2;
                    }
                    
                    if(index($Line, " macro ")!=-1)
                    {
                        if($Line=~/#define\s+(\w+)\s+(\w+)\Z/) {
                            $SymbolToHeader{$2}{$HName} = "prototype";
                        }
                    }
                    
                    if(not $Is_C)
                    {
                        if(index($Line, "operator ")==0)
                        {
                            if($Line=~/\A(operator) (\w.*?)\s+(prototype|function)/) {
                                $SymbolToHeader{$1." ".$2}{$HName} = $3;
                            }
                            elsif($Line=~/\A(operator) (\W.*?)\s+(prototype|function)/) {
                                $SymbolToHeader{$1.$2}{$HName} = $3;
                            }
                        }
                    }
                }
                
                if($Is_C)
                {
                    my $List_T = `$CTAGS -x --$Lang-kinds=gstu --languages=+$Lang --language-force=$Lang $IgnoreTags \"$File\"`;
                    foreach my $Line (split(/\n/, $List_T))
                    {
                        if($Line=~/\A(\w+)/)
                        {
                            my $N = $1;
                            
                            if($Line!~/\b$N\s+$N\b/) {
                                $TypeToHeader{$N} = $HName;
                            }
                        }
                    }
                }
            }
        }
    }
    
    # We can't fully rely on the output of Ctags because it may
    # miss some symbols intentionally (due to branches of code)
    # or occasionally (due to complex macros).
    if(not $UseTU)
    {
        foreach my $File (@Headers)
        {
            my $HName = getFilename($File);
            my $Content = readFile($File);
            
            $Content=~s&/\*.+?\*/&&sg;
            $Content=~s&(//|#define).*\n&\n&g;
            
            # Functions
            my @Func = ($Content=~/([a-zA-Z]\w+)\s*\(/g);
            foreach (@Func)
            {
                if(not defined $SymbolToHeader{$_} or not defined $SymbolToHeader{$_}{$HName}) {
                    $SymbolToHeader{$_}{$HName} = "prototype";
                }
            }
            
            # Data
            my @Data = ($Content=~/([a-zA-Z_]\w+)\s*;/gi);
            foreach (@Data)
            {
                if(not defined $SymbolToHeader{$_} or not defined $SymbolToHeader{$_}{$HName}) {
                    $SymbolToHeader{$_}{$HName} = "prototype";
                }
            }
            
            # Types
            if($Is_C)
            {
                my @Type1 = ($Content=~/}\s*([a-zA-Z]\w+)\s*;/g);
                my @Type2 = ($Content=~/([a-zA-Z]\w+)\s*{/g);
                foreach (@Type1, @Type2)
                {
                    if(not defined $TypeToHeader{$_} or not defined $TypeToHeader{$_}{$HName}) {
                        $TypeToHeader{$_}{$HName} = 1;
                    }
                }
            }
        }
    }
    
    if($CacheHeaders)
    {
        writeFile($PublicHeader_F, Dumper(\%PublicHeader));
        writeFile($SymbolToHeader_F, Dumper(\%SymbolToHeader));
        writeFile($TypeToHeader_F, Dumper(\%TypeToHeader));
        writeFile($Path_F, $Path_A);
    }
}