Functions that should be static

Francois Gouget fgouget at free.fr
Wed Jan 14 05:55:55 CST 2009


I had a look at the functions generated by Flex and Bison. Neither of 
them can know whether the functions they generate will be called from 
other object files or not (and they don't provide a way to make them all 
static anyway). So I updated my script to automatically ignore these 
functions (and I attached the new version of the script).

With that and the help from Andrew Talbot we're down to less than 200 
warnings :-) Getting there...

Here's the updated list.

dlls/cabinet/fdi.o: QTMupdatemodel should be made static
dlls/cabinet/fdi.o: make_decode_table should be made static
dlls/comctl32/tests/msg.o: flush_sequence should be made static
dlls/dbghelp/storage.o: hash_table_find should be made static
dlls/dmime/patterntrack.o: DMUSIC_CreateDirectMusicPatternTrackImpl should be made static
dlls/dmime/tool.o: DMUSIC_CreateDirectMusicobjImpl should be made static
dlls/dplayx/dplayx_global.o: DPLAYX_DestroyLobbyApplication should be made static
dlls/dplayx/dplayx_global.o: DPLAYX_SetLocalSession should be made static
dlls/dplayx/name_server.o: NS_GetOtherMagic should be made static
dlls/dplayx/name_server.o: NS_SetRemoteComputerAsNameServer should be made static
dlls/dplayx/dplay.o: cbDeleteGroupsElem should be made static
dlls/dplayx/dplay.o: cbDeletePlayerElem should be made static
dlls/dsound/capture.o: DirectSoundCaptureDevice_AddRef should be made static
dlls/fusion/fusion.o: CompareAssemblyIdentity should be made static
dlls/fusion/fusion.o: GetAssemblyIdentityFromFile should be made static
dlls/fusion/assembly.o: assembly_get_architecture should be made static
dlls/inetcomm/internettransport.o: InternetTransport_Read should be made static
dlls/iphlpapi/ifenum.o: getInterfacePhysicalByName should be made static
dlls/itss/chm_lib.o: chm_enumerate should be made static
dlls/mountmgr.sys/mountmgr.o: DriverEntry should be made static
dlls/msi/events.o: ControlEvent_UnSubscribeToEvent should be made static
dlls/msi/string.o: msi_id2stringW should be made static
dlls/msi/string.o: msi_strcmp should be made static
dlls/msi/helpers.o: msi_ui_error should be made static
dlls/msi/registry.o: msi_version_dword_to_str should be made static
dlls/msi/helpers.o: reduce_to_shortfilename should be made static
dlls/netapi32/nbnamecache.o: NBNameCacheUpdateNBName should be made static
dlls/qcap/pin.o: IPinImpl_QueryInternalConnections should be made static
dlls/riched20/string.o: ME_ConcatString should be made static
dlls/riched20/style.o: ME_CopyToCF2W should be made static
dlls/riched20/list.o: ME_DITypesEqual should be made static
dlls/riched20/paint.o: ME_DrawParagraph should be made static
dlls/riched20/list.o: ME_FindItemFwdOrHere should be made static
dlls/riched20/row.o: ME_FindRowStart should be made static
dlls/riched20/string.o: ME_GetOptimalBuffer should be made static
dlls/riched20/para.o: ME_GetParaFormat should be made static
dlls/riched20/paint.o: ME_GetYScrollVisible should be made static
dlls/riched20/paint.o: ME_InvalidateFromOfs should be made static
dlls/riched20/editor.o: ME_LinkNotify should be made static
dlls/riched20/string.o: ME_MakeStringB should be made static
dlls/riched20/para.o: ME_MarkForWrapping should be made static
dlls/riched20/paint.o: ME_QueueInvalidateFromCursor should be made static
dlls/riched20/paint.o: ME_Scroll should be made static
dlls/riched20/para.o: ME_SetParaFormat should be made static
dlls/riched20/style.o: ME_ToCFAny should be made static
dlls/riched20/editor.o: REComboWndProc should be made static
dlls/riched20/editor.o: REListWndProc should be made static
dlls/riched20/reader.o: RTFExpandStyle should be made static
dlls/riched20/reader.o: RTFGetClassCallback should be made static
dlls/riched20/reader.o: RTFGetDestinationCallback should be made static
dlls/riched20/reader.o: RTFGetInputName should be made static
dlls/riched20/reader.o: RTFGetOutputName should be made static
dlls/riched20/reader.o: RTFGetReadHook should be made static
dlls/riched20/reader.o: RTFGetStyle should be made static
dlls/riched20/reader.o: RTFHexToChar should be made static
dlls/riched20/reader.o: RTFPeekToken should be made static
dlls/riched20/reader.o: RTFSetClassCallback should be made static
dlls/riched20/reader.o: RTFSetInputName should be made static
dlls/riched20/reader.o: RTFSetOutputName should be made static
dlls/riched20/reader.o: RTFSetToken should be made static
dlls/riched20/reader.o: RTFUngetToken should be made static
dlls/rpcrt4/rpc_message.o: NCA2RPC_STATUS should be made static
dlls/rpcrt4/ndr_marshall.o: NdrByteCountPointerMemorySize should be made static
dlls/rpcrt4/ndr_marshall.o: NdrRangeBufferSize should be made static
dlls/rpcrt4/ndr_marshall.o: NdrRangeFree should be made static
dlls/rpcrt4/ndr_marshall.o: NdrRangeMarshall should be made static
dlls/rpcrt4/ndr_marshall.o: NdrRangeMemorySize should be made static
dlls/rpcrt4/rpc_transport.o: RPCRT4_SpawnConnection should be made static
dlls/rpcrt4/rpc_message.o: RPCRT4_ValidateCommonHeader should be made static
dlls/rpcrt4/rpc_message.o: RPCRT4_receive_fragment should be made static
dlls/rsaenh/handle.o: alloc_handle_table should be made static
dlls/rsaenh/mpi.o: mp_2expt should be made static
dlls/rsaenh/mpi.o: mp_abs should be made static
dlls/rsaenh/mpi.o: mp_add_d should be made static
dlls/rsaenh/mpi.o: mp_clamp should be made static
dlls/rsaenh/mpi.o: mp_clear should be made static
dlls/rsaenh/mpi.o: mp_cmp_mag should be made static
dlls/rsaenh/mpi.o: mp_cnt_lsb should be made static
dlls/rsaenh/mpi.o: mp_div should be made static
dlls/rsaenh/mpi.o: mp_div_2 should be made static
dlls/rsaenh/mpi.o: mp_div_2d should be made static
dlls/rsaenh/mpi.o: mp_div_d should be made static
dlls/rsaenh/mpi.o: mp_dr_reduce should be made static
dlls/rsaenh/mpi.o: mp_dr_setup should be made static
dlls/rsaenh/mpi.o: mp_exch should be made static
dlls/rsaenh/mpi.o: mp_grow should be made static
dlls/rsaenh/mpi.o: mp_init should be made static
dlls/rsaenh/mpi.o: mp_init_size should be made static
dlls/rsaenh/mpi.o: mp_lshd should be made static
dlls/rsaenh/mpi.o: mp_mod_2d should be made static
dlls/rsaenh/mpi.o: mp_mod_d should be made static
dlls/rsaenh/mpi.o: mp_montgomery_calc_normalization should be made static
dlls/rsaenh/mpi.o: mp_montgomery_reduce should be made static
dlls/rsaenh/mpi.o: mp_montgomery_setup should be made static
dlls/rsaenh/mpi.o: mp_mul_2 should be made static
dlls/rsaenh/mpi.o: mp_mul_2d should be made static
dlls/rsaenh/mpi.o: mp_mul_d should be made static
dlls/rsaenh/mpi.o: mp_prime_is_divisible should be made static
dlls/rsaenh/mpi.o: mp_prime_is_prime should be made static
dlls/rsaenh/mpi.o: mp_prime_miller_rabin should be made static
dlls/rsaenh/mpi.o: mp_reduce should be made static
dlls/rsaenh/mpi.o: mp_reduce_2k should be made static
dlls/rsaenh/mpi.o: mp_reduce_2k_setup should be made static
dlls/rsaenh/mpi.o: mp_reduce_setup should be made static
dlls/rsaenh/mpi.o: mp_rshd should be made static
dlls/rsaenh/mpi.o: mp_set should be made static
dlls/rsaenh/mpi.o: mp_signed_bin_size should be made static
dlls/rsaenh/mpi.o: mp_sqr should be made static
dlls/rsaenh/mpi.o: mp_sqrmod should be made static
dlls/rsaenh/mpi.o: mp_zero should be made static
dlls/rsaenh/handle.o: release_handle_table should be made static
dlls/sane.ds/ds_ctrl.o: SANE_AutomaticCaptureDirectory should be made static
dlls/sane.ds/ds_ctrl.o: SANE_FileSystemCopy should be made static
dlls/sane.ds/ds_ctrl.o: SANE_PendingXfersStopFeeder should be made static
dlls/sane.ds/ds_ctrl.o: SANE_SetupFileXfer2Get should be made static
dlls/sane.ds/ds_ctrl.o: SANE_SetupFileXfer2GetDefault should be made static
dlls/sane.ds/ds_ctrl.o: SANE_SetupFileXfer2Reset should be made static
dlls/sane.ds/ds_ctrl.o: SANE_SetupFileXfer2Set should be made static
dlls/setupapi/parser.o: PARSER_string_substW should be made static
dlls/setupapi/virtcopy.o: VcpEnumFiles should be made static
dlls/setupapi/virtcopy.o: VcpFlush16 should be made static
dlls/setupapi/virtcopy.o: vsmStringCompare16 should be made static
dlls/setupapi/virtcopy.o: vsmStringFind16 should be made static
dlls/shell32/classes.o: HCR_GetDefaultIconFromGUIDW should be made static
dlls/shell32/pidl.o: ILGetDisplayNameExA should be made static
dlls/shell32/clipboard.o: RenderFILECONTENTS should be made static
dlls/shell32/clipboard.o: RenderFILEDESCRIPTOR should be made static
dlls/shell32/clipboard.o: RenderPREFEREDDROPEFFECT should be made static
dlls/shell32/clipboard.o: RenderSHELLIDLISTOFFSET should be made static
dlls/shell32/pidl.o: SHILCreateFromPathA should be made static
dlls/shell32/trash.o: TRASH_DisposeElement should be made static
dlls/shell32/xdg.o: XDG_GetPath should be made static
dlls/shell32/pidl.o: _ILGetSTextPointer should be made static
dlls/shell32/pidl.o: _ILGetTextPointerW should be made static
dlls/urlmon/http.o: create_http_protocol should be made static
dlls/user32/edit.o: EditWndProcW should be made static
dlls/user32/cursoricon.o: LookupIconIdFromDirectory16 should be made static
dlls/uxtheme/uxini.o: UXINI_ResetINI should be made static
dlls/winealsa.drv/alsa.o: ALSA_PeekRingMessage should be made static
dlls/winealsa.drv/waveout.o: wodSetVolume should be made static
dlls/winedos/himem.o: DOSVM_AllocCodeUMB should be made static
dlls/winedos/himem.o: DOSVM_AllocUMB should be made static
dlls/winedos/int31.o: DPMI_FreeInternalRMCB should be made static
dlls/wineps.drv/color.o: PSDRV_CmpColor should be made static
dlls/wineps.drv/ps.o: PSDRV_WriteArrayGet should be made static
dlls/wineps.drv/ps.o: PSDRV_WriteInitClip should be made static
dlls/wineps.drv/ps.o: PSDRV_WriteRRectangle should be made static
dlls/winex11.drv/graphics.o: X11DRV_SetupGCForPen should be made static
dlls/winex11.drv/xvidmode.o: X11DRV_XF86VM_SetExclusiveMode should be made static
dlls/winex11.drv/xrandr.o: X11DRV_XRandR_Cleanup should be made static
dlls/winex11.drv/window.o: X11DRV_get_client_window should be made static
dlls/winex11.drv/window.o: X11DRV_window_to_X_rect should be made static
dlls/wininet/http.o: HTTP_FinishedReading should be made static
dlls/wininet/http.o: HTTP_GetHeader should be made static
dlls/wininet/http.o: HTTP_HttpOpenRequestW should be made static
dlls/wininet/http.o: HTTP_HttpSendRequestW should be made static
dlls/winmm/mci.o: MCI_DefYieldProc should be made static
dlls/winmm/mci.o: MCI_GetDriverFromString should be made static
dlls/winmm/mci.o: MCI_SendCommandFrom16 should be made static
dlls/winmm/mci.o: MCI_SendCommandFrom32 should be made static
dlls/winmm/mci.o: MCI_WriteString should be made static
dlls/wintrust/asn.o: CRYPT_AsnEncodeInt should be made static
dlls/wintrust/wintrust_main.o: WINTRUST_ReAlloc should be made static
dlls/wnaspi32/winaspi16.o: ASPI_SendASPICommand should be made static
dlls/wnaspi32/aspi.o: SCSI_GetDeviceName should be made static
dlls/wuapi/updates.o: AutomaticUpdates_create should be made static
programs/progman/dialog.o: DIALOG_Symbol should be made static
programs/regedit/regproc.o: GetMultiByteStringN should be made static
programs/regedit/childwnd.o: GetRootKeyName should be made static
programs/regedit/regproc.o: GetWideStringN should be made static
programs/regedit/hexedit.o: HexEdit_Unregister should be made static
programs/regedit/regproc.o: processRegLinesA should be made static
programs/regedit/regproc.o: processRegLinesW should be made static
programs/taskmgr/taskmgr.o: Draw3dRect should be made static
programs/taskmgr/taskmgr.o: Draw3dRect2 should be made static
programs/taskmgr/taskmgr.o: FillSolidRect2 should be made static
programs/taskmgr/graphctl.o: GraphCtrl_DrawPoint should be made static
programs/taskmgr/graphctl.o: GraphCtrl_InvalidateCtrl should be made static
programs/taskmgr/graphctl.o: GraphCtrl_Paint should be made static
programs/taskmgr/graphctl.o: GraphCtrl_Resize should be made static
programs/taskmgr/trayicon.o: TrayIcon_GetProcessorUsageIcon should be made static
programs/taskmgr/applpage.o: UpdateApplicationListControlViewSetting should be made static
programs/winecfg/winecfg.o: enumerate_valuesW should be made static
programs/wineconsole/user.o: WCUSER_AreFontsEqual should be made static
programs/wineconsole/wineconsole.o: WINECON_FetchCells should be made static
programs/wineconsole/wineconsole.o: WINECON_GetHistoryMode should be made static
programs/wineconsole/wineconsole.o: WINECON_SetHistoryMode should be made static
programs/wineconsole/wineconsole.o: WINECON_SetHistorySize should be made static
programs/winedbg/memory.o: be_cpu_build_addr should be made static
programs/winedbg/memory.o: be_cpu_linearize should be made static
programs/winedbg/winedbg.o: dbg_outputA should be made static
programs/winedbg/debug.yy.o: lexeme_alloc should be made static
programs/winedbg/dbg.tab.o: parser should be made static
programs/winedbg/tgt_minidump.o: validate_file should be made static


-- 
Francois Gouget <fgouget at free.fr>              http://fgouget.free.fr/
The nice thing about meditation is that it makes doing nothing quite respectable
                                  -- Paul Dean
-------------- next part --------------
#!/usr/bin/perl -w
use strict;

my $name0=$0;
$name0 =~ s%^.*/%%;


my $IDENTIFIER="[a-zA-Z_][a-zA-Z0-9_]*";
my $MANGLEDIDENT="[a-zA-Z0-9_?@\$]+";
my $SPECIDENT="$IDENTIFIER(?:\.$MANGLEDIDENT)*";
my $SPECOPT="-[a-zA-Z0-9_=,]+\\s+";


### List of linefeed issues to ignore
my %lf_blacklist=(
    "dlls/comctl32/rebar.c" => {"\"band # \%u: xHeader=\%u\"" => 1},
    "dlls/d3d8/device.c" => {"\"Searching for declaration for fvf \%08x... \"" => 1},
    "dlls/d3d9/device.c" => {"\"Searching for declaration for fvf \%08x... \"" => 1},
    "dlls/ddraw/ddraw.c" => {"\"Searching for declaration for fvf \%08x... \"" => 1},
    "dlls/ddraw/utils.c" => {"\"\%s \"" => 1,
                             "\" R \"" => 1,
                             "\" - \"" => 1},
    "dlls/dinput/effect_linuxinput.c" => {"\"\%[ds] \"" => 1},
    "dlls/dinput/mouse.c" => {"\"\\(X: \%d Y: \%d Z: \%d\"" => 1},
    "dlls/dsound/dsound.c" => {"\"\%s \",flags\\[i\\]\\.name" => 1},
    "dlls/gdi32/freetype.c" => {"\"\\\\t\%s\\\\t\%08x\"" => 1},
    "dlls/ntdll/loader.c" => {"\"No implementation for \%s\\.%[ds]\"" => 1},
    "dlls/ntdll/rtl.c" => {"\"\%s\%x: \%s\"" => 1},
    "dlls/ntdll/time.c" => {"\"starting date isdst \%d, \%s\"" => 1,
                            "\"year_start: \%s\"" => 1,
                            "\"std: \%s\"" => 1,
                            "\"dlt gmtime: \%s\"" => 1,
                            "\"std gmtime: \%s\"" => 1},
    "dlls/ntdll/virtual.c" => {"\"View: \%p - \%p\"" => 1},
    "dlls/odbc32/proxyodbc.c" => {"\"returns: \%d \\\\t\"" => 1},
    "dlls/ole32/marshal.c" => {"\" MSHLFLAGS_TABLESTRONG\"" => 1},
    "dlls/oleaut32/tmarshal.c" => {"\"*\"" => 1},
    "dlls/oleaut32/typelib.c" => {"\"\%p->\\{\%s\%s\"" => 1,
                                  "\"\\\\t\\\\tu\\.paramdesc\\.wParamFlags\"" => 1},
    "dlls/oleaut32/varformat.c" => {"\"\%s0x%02x\"" => 1},
    "dlls/quartz/dsoundrender.c" => {"\"%02x \"" => 1},
    "dlls/quartz/videorenderer.c" => {"\"%02x \"" => 1},
    "dlls/rpcrt4/ndr_marshall.c" => {"\" RPC_FC_P_ALLOCALLNODES\"" => 1},
    "dlls/rpcrt4/ndr_stubless.c" => {"\" MustSize\"" => 1,
                                    "\" ServerMustSize\"" => 1},
    "dlls/rpcrt4/rpc_binding.c" => {"\"SecurityQos { Version=\%d, Capabilities=0x\%x, IdentityTracking=\%d, ImpersonationLevel=\%d\"" => 1},
    "dlls/secur32/dispatcher.c" => {"\"\%s \"" => 1},
    "dlls/secur32/schannel.c" => {"\"<\%d> \%s\"" => 1},
    "dlls/setupapi/parser.c" => {"\"\%p/\%p/\%d/\%d index \%d returning\"" => 1},
    "dlls/snmpapi/main.c" => {"\"\%u\"" => 1,
                              "\"String \"" => 1,
                              "\"IpAddress \"" => 1,
                              "\"Bits \"" => 1,
                              "\"Opaque \"" => 1,
                              "\"ObjectID \"" => 1},
    "dlls/user32/listbox.c" => {"\\[\%p\\]: settabstops \"" => 1},
    "dlls/user32/menu.c" => {"\"\%s\%s\"" => 1,
                             "\"\%s \"" => 1},
    "dlls/user32/painting.c" => {"\"\%p region \%p box \%s \"" => 1},
    "dlls/user32/scroll.c" => {"\"hwnd=\%p bar=\%d\"" => 1},
    "dlls/usp10/usp10.c" => {"\"New_Script=\%d, eScript=\%d \"" => 1,
                             "\"Item \%d, Glyphs \%d \"" => 1},
    "dlls/version/info.c" => {"\"filetype=APP\"" => 1},
    "dlls/winecoreaudio.drv/midi.c" => {"\"\%02X \"" => 1},
    "dlls/wined3d/baseshader.c" => {"\"GL HW \\(\%u, \%u\\) : \%s\"" => 1,
                                    "\"dcl\"" => 1,
                                    "\"-\"" => 1,
                                    "\"_d8\"" => 1,
                                    "\"def c\%u = \%f, \%f, \%f, \%f\"" => 1,
                                    "\"%s\"" => 1},
    "dlls/wined3d/vertexbuffer.c" => {"\"[[]\%d[]]\"" => 1},
    "dlls/winex11.drv/dib.c" => {"\"%04x/%04lx \"" => 1},
    "dlls/winex11.drv/opengl.c" => {"\"\\('\%s'\\):\%\\*s\"" => 1,
                                    "\"\\*\"" => 1},
    "dlls/wininet/internet.c" => {"\" %s\"" => 1},
);


### List of stdcall equivalents
my @stdcall_seeds=qw(__stdcall);
my (%macros, $stdcall_re);


### List of 'static stdcall' functions to ignore
my %static_ignore=(
    "dlls/winedos/interrupts.c" => {DOSVM_Int20Handler => 1,
                                    DOSVM_Int5cHandler => 1},
    "dlls/winmm/message16.c" => {MMDRV_Aux_Callback => 1,
                                 MMDRV_Mixer_Callback => 1,
                                 MMDRV_MidiIn_Callback => 1,
                                 MMDRV_MidiOut_Callback => 1,
                                 MMDRV_WaveIn_Callback => 1,
                                 MMDRV_WaveOut_Callback => 1},
    "programs/cmdlgtst/cmdlgtst.c" => {dummyfnHook => 1},
);


### Debug channel checker
my %dbg_ignore=(
    "dlls/kernel32/virtual.c" => {"" => 1},
    "dlls/msvcrt/scanf.c" => {"msvcrt" => 1},
    "dlls/ntdll/debugtools.c" => {"" => 1},
);


### List of pointers for the NULL cast checker
my (%typedefs, %pointers);


### List of macros that are equal to 0
my %zeroes=(NULL => 1);


### List of undef-ed macros
my %undefs;


### List of exported functions to ignore
my %export_ignore=(
    "dlls/kernel32" => {NE_DumpModule => 1,
                        NE_WalkModules => 1,
                        debug_handles => 1},
    "dlls/ntdll" => {__wine_process_init => 1,
                     __wine_spec_unimplemented_stub => 1},
    "dlls/ole32" => {STORAGE_dump_pps_entry => 1},
    "dlls/riched20" => {ME_DumpParaStyleToBuf => 1,
                        ME_DumpStyle => 1},
    "dlls/secur32" => {SECUR32_initNegotiateSP => 1, # 2009/01: Till implementation catches up
                       SECUR32_strdupW => 1},        # 2009/01: Till implementation catches up
    "dlls/wined3d" => {debug_fixup_channel_source => 1,
                       debug_yuv_fixup => 1},
    "dlls/wineoss.drv" => {seqbuf_dump => 1},
    "dlls/winhttp" => {netconn_set_timeout => 1},    # 2009/01: Till implementation catches up
);


### List of functions implemented or referenced from assembly code,
### sorted by directory
my %asm_functions;

### List of standard sublanguages
my %standard_sublangs=(
    SUBLANG_NEUTRAL => 1,
    SUBLANG_DEFAULT => 1,
    SUBLANG_SYS_DEFAULT => 1,
    SUBLANG_CUSTOM_DEFAULT => 1,
    SUBLANG_CUSTOM_UNSPECIFIED => 1,
    SUBLANG_UI_CUSTOM_DEFAULT => 1
);

### Lists of the languages default sublang and of those that have many sub
### languages
my (%lang_default, %lang_manysubs);

### List of languages known to have translations for non-default sub languages
my %lang_anysub=(
    LANG_ENGLISH => 1,
    LANG_PORTUGUESE => 1
);


my $verbose;
sub verbose(@)
{
    return print STDERR @_ if ($verbose);
}

my $debug;
sub debug(@)
{
    return print STDERR @_ if ($debug);
}

sub err(@)
{
    print STDERR "$name0:error: ", @_;
}

sub dirname($)
{
    my ($path)=@_;
    return "" if ($path !~ s!/[^/]+$!!);
    return $path;
}


my @file;
my $l;
my @chars;
my $len;
my $c;

sub skip_bracket();
sub skip_parent();
sub skip_string();

sub setup_line($)
{
    return 0 if ($l >= @file);

    $c=$_[0];
    $len=length($file[$l]);
    @chars=split //, $file[$l];
    debug("$l,$c: $file[$l]");
    return 1;
}

sub set_position($$)
{
    return 0 if ($l >= @file);

    $c=$_[0];
    $l=$_[1];
    $len=length($file[$l]);
    @chars=split //, $file[$l];
    debug("$l,$c: $file[$l]");
    return 1;
}

sub skip_bracket()
{
    while (1)
    {
        while ($c < $len)
        {
            debug("skip_bracket: $c -> '$chars[$c]'\n");
            if ($chars[$c] eq "]")
            {
                debug("skip_bracket returns 1\n");
                return 1;
            }
            elsif ($chars[$c] eq "\"")
            {
                $c++;
                skip_string();
            }
            elsif ($chars[$c] eq "(")
            {
                $c++;
                skip_parent();
            }
            elsif ($chars[$c] eq "[")
            {
                $c++;
                skip_bracket();
            }
            $c++;
        }
        $l++;
        return 0 if (!setup_line(0));
    }
    return 0;
}

sub skip_parent()
{
    while (1)
    {
        while ($c < $len)
        {
            debug("skip_parent: $c -> '$chars[$c]'\n");
            if ($chars[$c] eq ")")
            {
                debug("skip_parent returns 1\n");
                return 1;
            }
            elsif ($chars[$c] eq "\"")
            {
                $c++;
                skip_string();
            }
            elsif ($chars[$c] eq "(")
            {
                $c++;
                skip_parent();
            }
            elsif ($chars[$c] eq "[")
            {
                $c++;
                skip_bracket();
            }
            $c++;
        }
        $l++;
        return 0 if (!setup_line(0));
    }
    return 0;
}

sub skip_string()
{
    my $backslash;
    while (1)
    {
        while ($c < $len)
        {
            debug("skip_string: $c -> '$chars[$c]'\n");
            if ($chars[$c] eq "\"")
            {
                if (!$backslash)
                {
                    debug("skip_string returns 1\n");
                    return 1;
                }
                $backslash=undef;
            }
            elsif ($chars[$c] eq "\\")
            {
                $backslash=($backslash?undef:1);
            }
            else
            {
                $backslash=undef;
            }
            $c++;
        }
        $l++;
        return 0 if (!setup_line(0));
    }
    return 0;
}

sub skip_comment()
{
    my $star;
    while (1)
    {
        while ($c < $len)
        {
            debug("skip_comment: $c -> '$chars[$c]'\n");
            if ($chars[$c] eq "*")
            {
                $star=1;
            }
            elsif ($chars[$c] eq "/")
            {
                return 1 if ($star);
            }
            else
            {
                $star=0;
            }
            $c++;
        }
        $l++;
        return 0 if (!setup_line(0));
    }
    return 0;
}

sub skip_arg()
{
    while (1)
    {
        while ($c < $len)
        {
            debug("skip_arg: $c -> '$chars[$c]'\n");
            if ($chars[$c] eq ",")
            {
                debug("skip_arg returns 1\n");
                return 1;
            }
            elsif ($chars[$c] eq "\"")
            {
                $c++;
                skip_string();
            }
            elsif ($chars[$c] eq "(")
            {
                $c++;
                skip_parent();
            }
            elsif ($chars[$c] eq "[")
            {
                $c++;
                skip_bracket();
            }
            $c++;
        }
        $l++;
        return 0 if (!setup_line(0));
    }
    return 0;
}

sub get_quote()
{
    while (1)
    {
        while ($c < $len)
        {
            debug("get_quote: $c -> '$chars[$c]'\n");
            if ($chars[$c] eq "\"")
            {
                debug("get_quote returns 1\n");
                return 1;
            }
            elsif ($chars[$c] eq "\\")
            {
                return 0 if ($c+1==$len or $chars[$c+1] ne "\n");
            }
            elsif ($chars[$c] eq "/" and $c+1 < $len and $chars[$c+1] eq "*")
            {
                return 0 if (!skip_comment());
            }
            elsif ($chars[$c] !~ /(\s|\n)/)
            {
                return 0;
            }
            $c++;
        }
        $l++;
        return 0 if (!setup_line(0));
    }
    return 0;
}

sub get_comma()
{
    while (1)
    {
        while ($c < $len)
        {
            debug("get_comma: $c -> '$chars[$c]'\n");
            if ($chars[$c] eq ",")
            {
                debug("get_comma returns 1\n");
                return 1;
            }
            elsif ($chars[$c] eq "\\")
            {
                return 0 if ($c+1==$len or $chars[$c+1] ne "\n");
            }
            elsif ($chars[$c] !~ /(\s|\n|[a-zA-Z0-9\#_])/)
            {
                return 0;
            }
            $c++;
        }
        $l++;
        return 0 if (!setup_line(0));
    }
    return 0;
}

my $lf_set_ignore;
sub fix_lf($$$$)
{
    my ($needs_lf, $heuristics, $arg, $pos)=@_;
    setup_line($pos);
    verbose("processing:",$l+1," ",substr($file[$l], $pos));

    # Locate the format string
    for (my $i=1; $i < $arg; $i++)
    {
        return undef if (!skip_arg());
        $c++;
    }
    return undef if (!get_quote());

    # See if this is a string of the form "xxx" #var "yyy"
    my ($quote_c, $quote_l);
    while ($c < $len)
    {
        last if ($chars[$c] ne "\"");
        $c++;
        return "" if (!skip_string());
        $quote_c=$c;
        $quote_l=$l;
        $c++;
        last if (get_comma());
    }
    set_position($quote_c, $quote_l);

    # Check and modify
    if ($needs_lf)
    {
        if ($c >= 2 and ($chars[$c-2] ne "\\" or $chars[$c-1] ne "n"))
        {
            my $start=substr($file[$l], 0, $c);
            verbose("  start=[$start]\n");
            if ($start =~ /\\r$/ or $start =~ /\"\s*$/)
            {
                return $file[$l];
            }
            elsif ($heuristics and
                   ($start =~ /(?:->|[=:,|{([])\s*$/ or
                    # For the dm* dlls
                    $start =~ /\": \%s chunk \(size = (?:0x%04x|0x%08X|\%d)\)$/ or
                    $start =~ /\": (?:LIST|RIFF) chunk of type \%s$/
                   ))
            {
                verbose("  matched=[$&]\n");
                $lf_set_ignore=1;
                return $file[$l];
            }
            else
            {
                $start=~s/\s*$/\\n/;
                return $start . substr($file[$l], $c);
            }
        }
        elsif ($c >= 3 and $chars[$c-3] eq " " and
               $chars[$c-2] eq "\\" and $chars[$c-1] eq "n")
        {
            my $space=$c-3;
            while ($space > 0 and $chars[$space] eq " ")
            {
                $space--;
            }
            return substr($file[$l], 0, $space+1) . substr($file[$l], $c-2);
        }
    }
    else
    {
        my $cut=$c;
        if ($cut >= 2 and $chars[$cut-2] eq "\\" and $chars[$cut-1] eq "n")
        {
            $cut-=2;
        }
        while ($cut > 0)
        {
            $cut--;
            last if ($chars[$cut] ne " ");
        }
        return substr($file[$l], 0, $cut+1) . substr($file[$l], $c);
    }
    return $file[$l];
}

sub collect($$)
{
    my ($rootdir, $filename) = @_;

    debug("***** Processing $filename\n");
    if (open(my $fh, "<", "$rootdir$filename"))
    {
        my $dir=dirname($filename);
        while (my $line=<$fh>)
        {
            if ($line =~ /^\s*#\s*define\s+($IDENTIFIER)\s+(0(?:x0+)?L?|\(\s*0(?:x0+)?L?\s*\))\s*(\/\*.*)?$/)
            {
                debug("zero $1\n");
                $macros{$1}=$2;
                $zeroes{$1}=1;
            }
            elsif ($line =~ /^\s*#\s*define\s+($IDENTIFIER)\s+([0-9]+L?|0x[0-9a-fA-F]+L?|\(\s*(?:[0-9]+L?|0x[0-9a-fA-F]+L?)\s*\))\s*(\/\*.*)?$/)
            {
                debug("macro $1=$2\n");
                $macros{$1}=$2;
            }
            elsif ($line =~ /^\s*#\s*define\s+($IDENTIFIER)\s+($IDENTIFIER)\s*(\/\*.*)?$/)
            {
                debug("macro $1=$2\n");
                $macros{$1}=$2;
            }
            elsif ($line =~ /^\s*typedef\s+($IDENTIFIER)\s+($IDENTIFIER)\s*[;,]/)
            {
                debug("typedef $1 $2\n");
                $typedefs{$2}=$1;
            }
            elsif ($line =~ /^\s*typedef\s+[^;,]*\*\s*($IDENTIFIER)\s*[;,]/)
            {
                debug("pointer $1: $line");
                $pointers{$1}=1;
            }
            elsif ($line =~ /^\s*DECLARE_HANDLE\(($IDENTIFIER)\)\s*;/)
            {
                debug("handle $1\n");
                $pointers{$1}=1;
            }
            elsif ($line =~ /^\s*#\s*undef\s+($IDENTIFIER)\s*(\/\*.*)?$/ and
                   $1 ne "YYERROR_VERBOSE")
            {
                debug("undef $1\n");
                $undefs{$1}=1;
            }
            elsif ($line =~ /^\s*__ASM_GLOBAL_FUNC\s*\(\s*($IDENTIFIER)\s*,/)
            {
                my $function=$1;
                $asm_functions{$dir}->{$function}=1;
            }
            elsif ($line =~ /^\s*DEFINE_REGS_ENTRYPOINT\s*\(\s*($IDENTIFIER)\s*,/)
            {
                my $function=$1;
                $asm_functions{$dir}->{$function}=1;
                $asm_functions{$dir}->{"__regs_$function"}=1;
            }
            elsif ($line =~ /^\s*DEFINE_(?:FASTCALL[12]|SETJMP)_ENTRYPOINT\s*\(\s*($IDENTIFIER)\s*\)/)
            {
                my $function="__regs_$1";
                $asm_functions{$dir}->{$function}=1;
            }
            elsif ($line =~ /^\s*DEFINE_THISCALL_WRAPPER\s*\(\s*($IDENTIFIER)\s*\)/)
            {
                my $function=$1;
                $asm_functions{$dir}->{$function}=1;
                $asm_functions{$dir}->{"__thiscall_$function"}=1;
            }
            elsif ($line =~ /__ASM_NAME\(\s*\"($IDENTIFIER)\"\s*\)/)
            {
                my $function=$1;
                $asm_functions{$dir}->{$function}=1;
            }
        }
        close($fh);
    }
    else
    {
        err("unable to open '$filename' for reading: $!\n");
        exit 1;
    }
}

sub fix_c_file($$)
{
    my ($rootdir, $filename) = @_;

    debug("***** Processing $filename\n");
    if (open(my $fh, "<", "$rootdir$filename"))
    {
        @file = <$fh>;
        close($fh);
    }
    else
    {
        err("unable to open '$filename' for reading: $!\n");
        exit 1;
    }

    # Initialize the linefeed checker
    my $lf_blacklist_extra;
    if ($lf_blacklist{$filename})
    {
        $lf_blacklist_extra=join("|", keys %{$lf_blacklist{$filename}});
    }
    my $lf_ignore=0;

    # Initialize the static stdcall checker
    my (%st_statics, %st_refs);
    my $st_prefix="";

    # Initialize the debug channel checker
    my ($dbg_default, %dbg_channels);

    my $modified;
    $l=0;
    while ($l < @file)
    {
        # Ifdef checker
        if ($file[$l] =~ /^\s*#\s*if\s+($IDENTIFIER)\s*(\/\*.*)?$/ and
            $undefs{$1})
        {
            print "$filename:",$l+1,": $1 might be undefined\n";
        }

        # Debug channel checker
        if ($file[$l] =~ /^\s*WINE_DEFAULT_DEBUG_CHANNEL\s*\(\s*(\w+)\s*\)/)
        {
            $dbg_default=$1;
            $dbg_channels{$1}||=0;
        }
        elsif ($file[$l] =~ /^\s*WINE_DECLARE_DEBUG_CHANNEL\s*\(\s*(\w+)\s*\)/)
        {
            $dbg_channels{$1}||=0;
        }
        elsif ($file[$l] =~ /\b(?:WINE_)?(?:ERR|FIXME|TRACE|WARN)(?:_|_ON)?\b/ and
               $file[$l] !~ /^\s*\/\*/)
        {
            my $line=$file[$l];
            while ($line =~ s/^.*?((?:WINE_)?(?:ERR|FIXME|TRACE|WARN)(?:_|_ON)?)\s*\(//)
            {
                my $type=$1;
                if ($type !~ /(?:_|_ON)$/)
                {
                    if (!defined $dbg_default)
                    {
                        print "$filename:",$l+1,": uses the default channel but none has been defined yet\n";
                    }
                    else
                    {
                        $dbg_channels{$dbg_default}++;
                    }
                }
                elsif ($line =~ /^\s*(\w+)\s*\)/)
                {
                    my $channel=$1;
                    if (!exists $dbg_channels{$channel})
                    {
                        print "$filename:",$l+1,": unknown debug channel '$channel'\n";
                    }
                    else
                    {
                        $dbg_channels{$channel}++;
                    }
                }
                else
                {
                    print "$filename:",$l+1,": unable to parse the trace statement\n";
                }
            }
        }

        # Zero-flag checker
        if ($file[$l] =~ /[^&]&[^&]/)
        {
            my $line=$file[$l];
            debug("zflags: $line");
            my %warned;
            while ($line =~ s/([^&,({= ]\s*[&]\s*)($IDENTIFIER)\b/$1 1/)
            {
                debug("zflags1: $2 -> $line");
                if ($zeroes{$2} and !$warned{$2})
                {
                    print "$filename:",$l+1,": '&' with zero-flag $2\n";
                    $warned{$1}=1;
                }
            }
            while ($line =~ s/\b($IDENTIFIER)(\s*&[^&])/1 $2/)
            {
                debug("zflags2: $1 -> $line");
                if ($zeroes{$1} and !$warned{$1})
                {
                    print "$filename:",$l+1,": '&' with zero-flag $1\n";
                    $warned{$1}=1;
                }
            }
            if (%warned)
            {
                $line=$file[$l];
                $line =~ s/^\s*//;
                print "    $line";
            }
        }

        # Static stdcall checker
        if ($file[$l] =~ /^\s*static(?:\s+$IDENTIFIER\s*\**)+\s+(?:$stdcall_re)\s+($IDENTIFIER)\s*(?:\(|$)/)
        {
            debug("$filename: static $1\n");
            push @{$st_statics{$1}}, $l;
            $st_prefix="";
        }
        elsif ("$st_prefix $file[$l]" =~ /^\s*static(?:\s+$IDENTIFIER\s*\**)+\s+(?:$stdcall_re)\s+($IDENTIFIER)\s*(?:\(|$)/)
        {
            debug("$filename: static $1\n");
            push @{$st_statics{$1}}, $l;
            $st_prefix="";
        }
        elsif ($file[$l] =~ /^\s*static(?:\s+$IDENTIFIER\s*\**)*\s*$/)
        {
            $st_prefix=$file[$l];
            chomp $st_prefix;
        }
        else
        {
            my $s=$file[$l];
            while ($s =~ s/(^|[\{\(\)&=?:,])\s*($IDENTIFIER)\s*(?![\(a-zA-Z0-9_])/$1/)
            {
                #debug("$filename: & $2\n");
                $st_refs{$2}=1;
            }
            $st_prefix="";
        }

        # NULL cast checker
        # Ignore the '#define foo(xxx) NULL' lines
        if ($file[$l] !~ /^\s*#/)
        {
            my $cast=$file[$l];
            $cast =~ s/\(\s*$IDENTIFIER(?:\s+$IDENTIFIER|\s*\*)*\s*\*\s*\)\s*(?:NULL|0L?)(?!\w|[0-9])/NULL/g;
            if ($cast =~ /\(\s*($IDENTIFIER)\s*\)\s*(?:NULL|0L?)(?!\w|[0-9])/)
            {
                # There may be others but if that happens
                # they will be removed manually
                $cast =~ s/\(\s*$1\s*\)\s*(?:NULL|0L?)(?!\w|[0-9]|\s*\)\s*->)/NULL/g if ($pointers{$1});
            }
            if ($cast ne $file[$l])
            {
                $file[$l]=$cast;
                $modified=1;
            }
        }

        # Linefeed processing
        # This may read more lines so this should be done last
        $lf_set_ignore=0;
        my $fixed;
        if ($file[$l] =~ /^(.*?\bok\s*\()/)
        {
            $fixed=fix_lf(1, 0, 2, length($1));
        }
        elsif ($file[$l] =~ /^(.*?\btrace\s*\()/)
        {
            $fixed=fix_lf(1, 1, 1, length($1));
        }
        elsif ($file[$l] =~ /^(.*?\b(?:WINE_)?(?:ERR|FIXME|TRACE|WARN)(?:_\s*\(\s*\w+\s*\))?\s*\()/)
        {
            $fixed=fix_lf(1, 1, 1, length($1));
        }
        # Ignore DPRINTF because it is often intentionally used without a
        # trailing '\n'
        elsif ($file[$l] =~ /^(.*?\b(?:WINE_)?MESSAGE\s*\()/)
        {
            $fixed=fix_lf(1, 1, 1, length($1));
        }
        elsif ($file[$l] =~ /^(.*?\bshader_addline\s*\()/)
        {
            # This is a wined3d trace function
            $fixed=fix_lf(1, 1, 2, length($1));
        }
        elsif ($file[$l] =~ /^(.*?\b(?:mcy_|parser_|xyy)?(?:chat|warning)\s*\()/)
        {
            # Error and warning reporting functions used in tools/
            $fixed=fix_lf(1, 0, 1, length($1));
        }
        elsif ($file[$l] =~ /^(.*?\berror\s*\()/)
        {
            # Error reporting functions used in tools/
            $fixed=fix_lf(1, 0, 1, length($1));
        }
        elsif ($file[$l] =~ /^(.*?\bparser_error\s*\()/)
        {
            # Error reporting function used in tools/ that must not have
            # a trailing '\n'
            $fixed=fix_lf(0, 0, 1, length($1));
        }
        elsif ($file[$l] =~ /^(.*?\berror_loc\s*\()/)
        {
            # Error reporting function used in tools/widl/
            $fixed=fix_lf(1, 0, 1, length($1));
        }
        elsif ($file[$l] =~ /^(.*?\b(?:error|warning)_loc_info\s*\()/)
        {
            # Error and warning reporting functions used in tools/widl/
            $fixed=fix_lf(1, 0, 2, length($1));
        }
        elsif ($file[$l] =~ /^(.*?\binternal_error\s*\()/)
        {
            # Error reporting function used in tools/wrc/
            $fixed=fix_lf(1, 0, 3, length($1));
        }
        elsif ($lf_ignore and $file[$l] =~ /^[{}]/)
        {
            verbose("$filename:",$l+1,": resuming checks\n");
            $lf_ignore=0;
        }

        if (defined $fixed)
        {
            # fix_lf() successfully checked this line
            if (!$lf_ignore and !$lf_set_ignore and
                $fixed ne $file[$l] and
                $lf_blacklist_extra and $file[$l] =~ m!$lf_blacklist_extra!)
            {
                # Mark this blacklist entry as used
                foreach my $re (keys %{$lf_blacklist{$filename}})
                {
                    if ($file[$l] =~ /$re/)
                    {
                        delete $lf_blacklist{$filename}->{$re};
                        verbose("$filename:",$l+1,": matched $re\n");
                        last;
                    }
                }
                $lf_set_ignore=1;
            }

            if ($lf_ignore)
            {
                if (!$lf_set_ignore and $fixed eq $file[$l])
                {
                    # We found a trace that looks ok so resume the checks
                    my $line=$file[$l];
                    $line =~ s/^\s*//;
                    verbose("$filename:",$l+1,": resuming checks due to: $line");
                    $lf_ignore=0;
                }
            }
            else
            {
                if ($lf_set_ignore)
                {
                    my $line=$file[$l];
                    $line =~ s/^\s*//;
                    verbose("$filename:",$l+1,": ignoring further errors due to: $line");
                    $lf_ignore=1;
                }
                elsif ($fixed ne $file[$l])
                {
                    $file[$l]=$fixed;
                    $modified=1;
                    $fixed =~ s/^\s*//;
                    verbose("$filename:",$l+1,": fixed $fixed");
                }
            }
        }

        $l++;
    }
    if ($modified)
    {
        if (open(my $fh, ">", "$rootdir$filename"))
        {
            print $fh @file;
            close($fh);
        }
        else
        {
            err("unable to open '$filename' for writing: $!\n");
        }
    }

    # Debug channel checker
    if (!defined $dbg_default and %dbg_channels)
    {
        if ($dbg_ignore{$filename}->{""})
        {
            delete $dbg_ignore{$filename}->{""};
        }
        else
        {
            printf "$filename: contains debug channels but no default debug channel\n";
        }
    }
    while (my ($channel, $value)=each %dbg_channels)
    {
        if ($dbg_ignore{$filename}->{$channel})
        {
            delete $dbg_ignore{$filename}->{$channel};
        }
        elsif (!$value)
        {
            printf "$filename: the '$channel' debug channel is unused\n";
        }
    }
    delete $dbg_ignore{$filename};

    # Check for unused linefeed blacklist entries
    if ($lf_blacklist{$filename})
    {
        foreach my $re (keys %{$lf_blacklist{$filename}})
        {
            print STDERR "$filename: unused linefeed blacklist: $re\n";
        }
    }
    delete $lf_blacklist{$filename};

    # Static stdcall checker
    foreach my $func (sort { $st_statics{$a}->[0] <=> $st_statics{$b}->[0] } keys %st_statics)
    {
        if (!$st_refs{$func})
        {
            if ($static_ignore{$filename}->{$func})
            {
                verbose("$filename: ignored static stdcall $func\n");
                delete $static_ignore{$filename}->{$func};
            }
            else
            {
                print "***** $filename ", join(" ", @{$st_statics{$func}}), "\n";
                $st_prefix="";
                foreach my $line (@file)
                {
                    if ($line =~ /\b$func\b/)
                    {
                        print "  $st_prefix $line";
                        $st_prefix="";
                    }
                    elsif ($line =~ /^\s*static(?:\s+$IDENTIFIER\s*\**)*\s*$/)
                    {
                        $st_prefix=$line;
                        chomp $st_prefix;
                    }
                    else
                    {
                        $st_prefix="";
                    }
                }
                print "\n";
            }
        }
    }
    foreach my $func (keys %{$static_ignore{$filename}})
    {
        print "$filename:$func not used\n";
    }
    delete $static_ignore{$filename};
}

sub fix_rc_file($$)
{
    my ($rootdir, $filename) = @_;

    debug("***** Processing $filename\n");
    if (open(my $fh, "<", "$rootdir$filename"))
    {
        @file = <$fh>;
        close($fh);
    }
    else
    {
        err("unable to open '$filename' for reading: $!\n");
        exit 1;
    }

    my $modified;
    $l=0;
    while ($l < @file)
    {
        my $line=$file[$l];
        # Fix extraneous spaces in '...'
        $line =~ s/\.(?: ?\.){2-5}/.../g;
        if ($filename =~ m!Si\.rc$!)
        {
            # Slovenian requires a space before '...'
            $line =~ s/([^ [])\.{3}/$1 .../g;
        }
        else
        {
            # Other languages say there should be no space before '...'
            # Except English where it's a bit ambiguous but where there
            # usually is no space in computer programs
            $line =~ s/([^,*]) *\.{3}/$1.../g;
        }

        # Remove spaces before '\n'
        $line =~ s/ +\\n/\\n/g;

        # Fix SUBLANGs
        if ($line !~ /^\s*#/ and
            $line =~ /\b(LANG_[A-Z]+)\s*,\s*(SUBLANG_[A-Z]+)\b/)
        {
            my ($lang, $sublang)=($1, $2);
            if ($lang eq "LANG_NEUTRAL")
            {
                if ($sublang ne "SUBLANG_NEUTRAL")
                {
                    print "$filename:",$l+1,": expected LANG_NEUTRAL, SUBLANG_NEUTRAL for language-independent resource\n";
                }
            }
            elsif (!$standard_sublangs{$sublang} and
                $sublang =~ /^SUB(LANG_[A-Z]+)(?:_|$)/ and $1 ne $lang)
            {
                print "$filename:",$l+1,": $sublang does not match $lang\n";
            }
            elsif (!exists $lang_default{$lang})
            {
                print "$filename:",$l+1,": unknown language $lang\n";
            }
            elsif ($lang eq "LANG_RUSSIAN")
            {
                # FIXME: Say nothing for now until the SUBLANG_RUSSIAN_MOLDAVIA
                # situation is resolved
            }
            elsif ($lang eq "LANG_GERMAN" and $sublang eq "SUBLANG_DEFAULT" and
                   $filename =~ m!kernel32/tests/resource\.rc$!)
            {
                # This is a test resource, presumably the SUBLANG_DEFAULT
                # is important
                # FIXME: Though maybe not
            }
            elsif (!$lang_manysubs{$lang})
            {
                # There is only one sublanguage
                if ($lang_default{$lang} eq $sublang and $sublang ne "SUBLANG_DEFAULT")
                {
                    # So it should be specified with SUBLANG_DEFAULT
                    $line =~ s/\b$lang\s*,\s*$sublang\b/$lang, SUBLANG_DEFAULT/;
                }
            }
            elsif ($lang_anysub{$lang})
            {
                # This language has many sublanguages, and sublanguage-specific
                # translations
                if ($lang eq "LANG_ENGLISH")
                {
                    # FIXME: Leave LANGUAGE_ENGLISH, SUBLANG_DEFAULT alone
                    # for now
                }
                elsif ($sublang eq "SUBLANG_DEFAULT")
                {
                    # It's better to use the explicit name for the default
                    # sublanguage: it's clearer for the next translator.
                    $line =~ s/\b$lang\s*,\s*SUBLANG_DEFAULT\b/$lang, $lang_default{$lang}/;
                }
            }
            else
            {
                # Although this language has many sublanguages, we believe
                # that all the current translations are neutral.
                if ($sublang eq "SUBLANG_DEFAULT")
                {
                    $line =~ s/\b$lang\s*,\s*SUBLANG_DEFAULT\b/$lang, SUBLANG_NEUTRAL/;
                }
                elsif ($sublang eq $lang_default{$lang})
                {
                    $line =~ s/\b$lang\s*,\s*$lang_default{$lang}\b/$lang, SUBLANG_NEUTRAL/;
                }
                elsif ($sublang ne "SUBLANG_NEUTRAL")
                {
                    # Warn for non-neutral, non-default translations.
                    # Maybe we finally have per-sublanguage translations?
                    print "$filename:",$l+1,": $lang has an unexpected non neutral translation: $sublang\n";
                }
            }


        }
        if ($line ne $file[$l])
        {
            verbose("$filename:",$l+1,": fixed $line");
            $file[$l]=$line;
            $modified=1;
        }

        $l++;
    }

    if ($modified)
    {
        if (open(my $fh, ">", "$rootdir$filename"))
        {
            print $fh @file;
            close($fh);
        }
        else
        {
            err("unable to open '$filename' for writing: $!\n");
        }
    }
}

sub fix_so($$$$)
{
    my ($rootdir, $so_file, $spec_files, $obj_files)=@_;
    return if (!defined $so_file or !@$obj_files);

    my ($is_exe, $is_test);
    $is_exe=1 if ($so_file =~ /\.exe\.so$/);
    $is_test=1 if ($so_file =~ /_test\.exe\.so$/);
    if (!@$spec_files and !$is_exe)
    {
        # This is not a Wine dll or exe so we have no idea
        # what should or should not be exported
        return;
    }

    my %spec_apis;
    foreach my $spec_file (@$spec_files)
    {
        if (open(my $fh, "<", "$rootdir$spec_file"))
        {
            while (my $line = <$fh>)
            {
                next if ($line =~ /^\s*(?:#|$)/);
                chomp $line;
                if ($line =~ /^\s*(?:\d+|@)\s+\w+\s+(?:$SPECOPT)*\$?$MANGLEDIDENT\s*\([^)]*\)\s+($SPECIDENT)\s*(?:#.*)?$/)
                {
                    my $identifier=$1;
                    $spec_apis{$identifier}=1 if ($identifier !~ /\./);
                }
                elsif ($line =~ /^\s*(?:\d+|@)\s+\w+\s+(?:$SPECOPT)*($SPECIDENT)\s*\([^)]*\)\s*(?:#.*)?$/)
                {
                    my $identifier=$1;
                    $spec_apis{$identifier}=1 if ($identifier !~ /\./);
                }
                elsif ($line =~ /^\s*(?:\d+|@)\s+stub\s+(?:$SPECOPT)*($SPECIDENT)(?:@\d+)?\s*(?:#.*)?$/)
                {
                    my $identifier=$1;
                    $spec_apis{$identifier}=1 if ($identifier !~ /\./);
                }
                elsif ($line =~ /^\s*(?:\d+|@)\s+stub\s+(?:$SPECOPT)*$MANGLEDIDENT\s*(?:#.*)?$/)
                {
                    # Stub with only a mangled name -> no use to us
                }
                elsif ($line =~ /^\s*\d+\s+stub\s+@\s*(?:#.*)?$/)
                {
                    # Unnamed stub -> no use to us
                }
                elsif ($line =~ /^\s*(?:\d+|@)\s+extern\s+(?:$SPECOPT)*\$?$MANGLEDIDENT\s+($SPECIDENT)\s*(?:#.*)?$/)
                {
                    my $identifier=$1;
                    $spec_apis{$identifier}=1 if ($identifier !~ /\./);
                }
                elsif ($line =~ /^\s*(?:\d+|@)\s+extern\s+(?:$SPECOPT)*($SPECIDENT)\s*(?:#.*)?$/)
                {
                    my $identifier=$1;
                    $spec_apis{$identifier}=1 if ($identifier !~ /\./);
                }
                elsif ($line =~ /^\s*\d+\s+equate\s/)
                {
                    # No use to us
                }
                else
                {
                    err("$spec_file: unknown line format\n  $line\n");
                }
            }
            close($fh);
        }
        else
        {
            err("unable to open '$spec_file' for reading: $!\n");
            exit 1;
        }
    }

    my (%idl_ignore, %obj_exports, %obj_references);
    foreach my $obj_file (@$obj_files)
    {
        my ($src_file, $bison, $flex, $idl_cs, $idl_proxy);
        $src_file=$obj_file;
        if ($src_file =~ s/\.tab\.o$/.y/ and -f "$rootdir$src_file")
        {
            $bison=$src_file;
            $bison =~ s/\.y$/_parse/;
            $bison =~ s!^.*/!!;
        }
        $src_file=$obj_file;
        if ($src_file =~ s/\.yy\.o$/.l/ and -f "$rootdir$src_file")
        {
            # Flex generates some non-static functions which may not be used
            # in other files. So ignore those. However their prefix is
            # configurable so we need to scan the Flex file to figure it out.
            if (open(my $fh, "<", "$rootdir$src_file"))
            {
                while (my $line=<$fh>)
                {
                    if ($line =~ /^\s*\%option\s.*\sprefix=\"($IDENTIFIER)\"/)
                    {
                        $flex=$1;
                        last;
                    }
                }
                close($fh);
            }
            $flex||="yy";
        }
        $src_file=$obj_file;
        if ($src_file =~ s/_[csp]\.o$/.idl/ and -f "$rootdir$src_file")
        {
            $idl_cs=1 if ($obj_file =~ /_[cs]\.o$/);
            $idl_proxy=1 if ($obj_file =~ /_p\.o$/);
        }

        if (open(my $fh, "objdump -t \"$rootdir$obj_file\" |"))
        {
            while (my $line = <$fh>)
            {
                if ($line =~ /^.*\s\.text\s+[0-9a-f]+\s+(?:\.hidden\s+)?([A-Za-z_][A-Za-z0-9_]*)$/)
                {
                    my $symbol=$1;
                    if ($bison and $symbol eq $bison)
                    {
                        $idl_ignore{$symbol}=1;
                    }
                    elsif ($flex and $symbol =~ /^$flex/)
                    {
                        $idl_ignore{$symbol}=1;
                    }
                    elsif ($idl_cs)
                    {
                        $idl_ignore{$symbol}=1;
                    }
                    elsif ($idl_proxy and $symbol =~ /_(?:Proxy|Stub)$/)
                    {
                        $idl_ignore{$symbol}=1;
                    }
                    else
                    {
                        $obj_exports{$symbol}=$obj_file;
                        $obj_references{$symbol}++;
                    }
                }
                elsif ($line =~ /^.*\s\*UND\*\s+[0-9a-f]+\s+(?:\.hidden\s+)?([A-Za-z_][A-Za-z0-9_]*)$/)
                {
                    $obj_references{$1}++;
                }
            }
            close($fh);
        }
        else
        {
            err("unable to analyze '$obj_file': $!\n");
            exit 1;
        }
    }

    my $dir=dirname($so_file);
    if (open(my $fh, "nm -D \"$rootdir$so_file\" |"))
    {
        while (my $line = <$fh>)
        {
            if ($line =~ /^[0-9a-f]+\sT\s(\w+)$/)
            {
                my $function=$1;
                if (!$obj_exports{$function})
                {
                    # We're importing this function, not exporting it!
                }
                elsif (($obj_references{$function} || 0) > 1)
                {
                    # This function is used in more than one object file
                    # so it cannot be made static.
                }
                elsif ($idl_ignore{$function})
                {
                    # These are widl-generated functions. They are meant to
                    # be used from other object files, but widl cannot know
                    # whether that will actually be the case or not.
                    # So these functions cannot be made static.
                }
                elsif ($asm_functions{$dir}->{$function})
                {
                    # Functions implemented in assembly, or referenced from
                    # assembly code, cannot be made static due to gcc
                    # limitations.
                }
                elsif ($spec_apis{$function})
                {
                    # These are meant to be exported
                }
                elsif ($is_exe and $function =~ /^w?(?:main|WinMain)$/)
                {
                    # Executables are supposed to export these.
                }
                elsif ($is_test and $function =~ /^(?:broken$|wine_dbgstr_[aw]n?$|winetest_)/)
                {
                    # Tests export these because of wine/test.h
                }
                elsif ($export_ignore{$dir}->{$function})
                {
                    delete $export_ignore{$dir}->{$function};
                }
                else
                {
                    print "$obj_exports{$function}: $function should be made static\n";
                }
            }
        }
        close($fh);
    }
    else
    {
        err("unable to analyze '$so_file': $!\n");
        exit 1;
    }
    foreach my $func (keys %{$export_ignore{$dir}})
    {
        print "$dir:$func not used\n";
    }
    delete $export_ignore{$dir};
}

sub fix_tree($$$$$$)
{
    my ($rootdir, $filter, $collect, $fix_c, $fix_rc, $fix_so)=@_;

    my @dirs=("");
    while (@dirs)
    {
        my $dir=pop @dirs;
        if (opendir(my $dh, "$rootdir$dir"))
        {
            my ($so_file, @spec_files, @obj_files);
            foreach my $dentry (sort { $b cmp $a } readdir($dh))
            {
                next if ($dentry eq "." or $dentry eq "..");
                $dentry="$dir$dentry";
                if (-d "$rootdir$dentry")
                {
                    if (-l "$rootdir$dentry")
                    {
                        verbose("skipping '$dentry' directory symbolic link\n");
                        next;
                    }
                    push @dirs, "$dentry/";
                    next;
                }
                next if ($filter and $dentry !~ /^$filter/);

                if ($collect and $dentry =~ /\.[chly]$/)
                {
                    collect($rootdir, $dentry);
                }
                elsif ($collect and $dentry eq "include/config.h.in")
                {
                    collect($rootdir, $dentry);
                }
                elsif ($fix_c and $dentry =~ /\.[cly]$/)
                {
                    fix_c_file($rootdir, $dentry);
                }
                elsif ($fix_rc and $dentry =~ /\.rc$/)
                {
                    fix_rc_file($rootdir, $dentry);
                }
                elsif ($fix_so and $dentry =~ /\.so$/)
                {
                    $so_file=$dentry;
                }
                elsif ($fix_so and $dentry =~ /\.spec$/)
                {
                    push @spec_files, $dentry;
                }
                elsif ($fix_so and $dentry =~ /\.o$/ and $dentry !~ /\.cross\.o$/)
                {
                    push @obj_files, $dentry;
                }
            }
            closedir($dh);
            fix_so($rootdir, $so_file, \@spec_files, \@obj_files) if ($fix_so);
        }
        else
        {
            err("unable to open the '$dir' directory: $!\n");
        }
    }
}


#####
#
# Main
#
#####

my ($opt_c, $opt_rc, $opt_so, $opt_help, $opt_dir, $opt_filter);
use Getopt::Long;
my $rc=GetOptions("c"       => \$opt_c,
                  "rc"      => \$opt_rc,
                  "so"      => \$opt_so,
                  "debug"   => \$debug,
                  "verbose" => \$verbose,
                  "help"    => \$opt_help
                 );
if ($rc)
{
    $opt_dir=shift @ARGV if (@ARGV);
    $opt_filter=shift @ARGV if (@ARGV);
    if (@ARGV)
    {
        err("only one directory can be specified\n");
        $rc=0;
    }
    $opt_dir||=".";
    $opt_dir.="/" if ($opt_dir !~ m!/$!);
}
if (!$rc)
{
    err("try running $name0 --help\n");
    exit 2;
}
if (!defined $opt_c and !defined $opt_rc and !defined $opt_so)
{
    $opt_c=$opt_rc=$opt_so=1;
}
$verbose=1 if ($debug);
if ($opt_help)
{
    print "Usage: $name0 [--c|--rc|--so] [--verbose] [--debug] [--help] [dir [filter]]\n";

    print "\n";
    print "Checks for various types of errors in the Wine source.\n";

    print "\n";
    print "Options:\n";
    print "  dir               The directory containing the Wine sources\n";
    print "  filter            Only handle that specific subdirectory\n";
    print "  --c               Fix the C files\n";
    print "  --rc              Fix the resource files\n";
    print "  --so              Check the API exported by .so files\n";
    print "  --verbose         Print some details and progress information\n";
    print "  --debug           Print lots of debug information\n";
    print "  --help            Prints this help message\n";
    exit 0;
}


### First pass: Collect information for the second pass
verbose("First pass...\n");
fix_tree($opt_dir, $opt_filter, 1, 0, 0, 0);

sub set_closure($@)
{
    my $set=shift @_;
    my $count=0;
    while (1)
    {
        my $c=keys %$set;
        last if ($count == $c);
        $count=$c;
        foreach my $map (@_)
        {
            while (my ($name, $value)=each %$map)
            {
                $set->{$name}=1 if ($set->{$value});
            }
        }
    }
}

# Find all the macros that expand to __stdcall
my %stdcalls;
map { $stdcalls{$_}=1 } @stdcall_seeds;
set_closure(\%stdcalls, \%macros);
verbose("stdcall = ", join(" ", sort keys %stdcalls), "\n");
$stdcall_re=join("|", keys %stdcalls);

# Round up the macros that expand to 0
set_closure(\%zeroes, \%macros);
verbose("zeroes = ", join(" ", sort keys %zeroes), "\n");

# Round up the pointer types
set_closure(\%pointers, \%typedefs, \%macros);
verbose("pointers = ", join(" ", sort keys %pointers), "\n");

# Find all languages with more than one sublanguage
foreach my $name (keys %macros)
{
    next if ($standard_sublangs{$name});
    if ($name =~ /^SUB(LANG_[A-Z]+)(?:_[A-Z][A-Z_]+)?$/)
    {
        my $lang=$1;
        if ($name !~ /^SUBLANG_(?:LOWER|UPPER)_SORBIAN_GERMANY$/ and
            !exists $macros{$lang})
        {
            err("$name has no matching language definition\n");
        }

        my $val=$macros{$name};
        my $indirect;
        if ($val =~ /^SUBLANG_/)
        {
            $val=$macros{$val};
            $indirect=1;
        }
        $val =~ s/^.*\(\s*((?:0x)?[0-9a-fA-F]+)\s*\).*$/$1/;
        $val =~ s/^0x0*(?=0$|[1-9a-fA-F])//;
        if ($val !~ /^[0-9a-fA-F]+$/)
        {
            err("unknown sublanguage type for $name: $macros{$name}\n");
            exit 1;
        }
        elsif ($val ne "1")
        {
            $lang_manysubs{$lang}=1;
        }
        elsif (!$indirect or ($lang_default{$lang} || "SUBLANG_DEFAULT") eq "SUBLANG_DEFAULT")
        {
            $lang_default{$lang}=$name;
        }
    }
    elsif ($name =~ /^(LANG_[A-Z]+)$/ and !$lang_default{$name})
    {
        $lang_default{$name}="SUBLANG_DEFAULT";
    }
}


### Second pass: Actually fix files and report issues
verbose("Second pass...\n");
fix_tree($opt_dir, $opt_filter, 0, $opt_c, $opt_rc, $opt_so);


### Check for unused blacklist entries
if (-f "$opt_dir/configure.ac" and !$opt_filter)
{
    if ($opt_c)
    {
        map { print "linefeed: $_ not found\n"; } sort keys %lf_blacklist;
        map { print "static: $_ not found\n"; } sort keys %static_ignore;
        map { print "debug channels: $_ not found\n"; } sort keys %dbg_ignore;
    }
    if ($opt_rc)
    {
        map { print "$_ should not be in \%lang_anysub\n" if (!$lang_manysubs{$_}) } sort keys %lang_anysub;
    }
    if ($opt_so)
    {
        map { print "export: $_ not found\n"; } sort keys %export_ignore;
    }
}


More information about the wine-devel mailing list