#!/usr/bin/perl -w # # Usage: wine_locks $lock < $file.sm # # Script to check following type of locking/unlocking: # lock(this); # do_something(); # unlock(this); # # Limitations: # The following style of locking/unlocking is falsly detected as error: # if (condition) # lock(this); # do_something # if (condition) # unlock(this); # # The idea and also some code riped from the smatch request_release.pl script. # # Copyright 2003-2007 Michael Stefaniuc use strict; use smatch; use ifcond; # Variables ############ # This hash contains the lock/unlock pairs. # The "exceptions" are here to prevent error messages for functions that are # practical only a wrapper around the lock and NOT to hide errors due to # probems in this script or smatch. my %locks = ( "create_window_handle" => { "lock" => "create_window_handle", "unlock" => "WIN_ReleasePtr"}, "CriticalSection" => { "lock" => "(?:Rtl)?EnterCriticalSection", "unlock" => "(?:Rtl)?LeaveCriticalSection", "exceptions" => "EnterCriticalSection|_EnterSysLevel|IMAPIPROP_Lock|LdrLockLoaderLock|ldt_lock|_lock|msiobj_lock|RtlAcquirePebLock|RtlLockHeap|server_enter_uninterrupted_section|wine_pthread_mutex_lock|wine_tsx11_lock|X11DRV_DIB_Lock"}, "DC_GetDC" => { "lock" => "DC_GetDC(?:Ptr|Update)", "unlock" => "GDI_ReleaseObj"}, "GDI_AllocObject" => { "lock" => "GDI_AllocObject", "unlock" => "GDI_ReleaseObj"}, "GDI_GetObjPtr" => { "lock" => "GDI_GetObjPtr", "unlock" => "GDI_ReleaseObj"}, # Commented out as it isn't used exclusively for locking but # also for counting producing LOTS of false positives. #"InterlockedIncrement" => { # "lock" => "InterlockedIncrement", # "unlock" => "InterlockedDecrement"}, "ldt_lock" => { "lock" => "ldt_lock", "unlock" => "ldt_unlock"}, "LdrLockLoaderLock" => { "lock" => "LdrLockLoaderLock", "unlock" => "LdrUnlockLoaderLock"}, "_lock" => { "lock" => "_lock", "unlock" => "_unlock"}, "msiobj_lock" => { "lock" => "msiobj_lock", "unlock" => "msiobj_unlock"}, "PebLock" => { "lock" => "RtlAcquirePebLock", "unlock" => "RtlReleasePebLock"}, "server_enter_uninterrupted_section" => { "lock" => "server_enter_uninterrupted_section", "unlock" => "server_leave_uninterrupted_section"}, "RtlLockHeap" => { "lock" => "RtlLockHeap", "unlock" => "RtlUnlockHeap"}, "SysLevel" => { "lock" => "_EnterSysLevel", "unlock" => "_LeaveSysLevel", "exceptions" => "_EnterWin16Lock|GDI_AllocObject|GDI_GetObjPtr|RestoreThunkLock|USER_Lock|WIN_RestoreWndsLock"}, "USER_Lock" => { "lock" => "USER_Lock", "unlock" => "USER_Unlock", "exceptions" => "create_window_handle|WIN_GetPtr"}, "WIN_GetPtr" => { "lock" => "WIN_GetPtr", "unlock" => "WIN_ReleasePtr"}, "wine_pthread_mutex_lock" => { "lock" => "wine_pthread_mutex_lock", "unlock" => "wine_pthread_mutex_unlock"}, "wine_tsx11_lock" => { "lock" => "wine_tsx11_lock(?:_ptr)?", "unlock" => "wine_tsx11_unlock(?:_ptr)?", "exceptions" => "X11DRV_expect_error"}, "X11DRV_DIB_Lock" => { "lock" => "X11DRV_DIB_Lock", "unlock" => "X11DRV_DIB_Unlock"}, ); # Functions ############ sub usage { print "Usage: wine_locks.pl lock < file.sm\n"; print "lock can be one of the following values:\n"; foreach my $i (sort(keys(%locks))) { print "\t$i\n"; } } sub error_msg{ my $msg = shift; my $start_line = shift; if ($start_line =~ /^$/) { $start_line = get_start_line(); } print get_filename(), " ", $start_line, " ", get_lineno(), " ", get_func_pos(), " $msg\n"; } sub merge_rules($$$){ my ($name, $one, $two) = @_; if (($one =~ /^__none/)&&($two =~ /^__none/)){ return 0; } if ($one =~ /^__none/){ return $two; } if ($two =~ /^__none/){ return $one; } if ($one > $two){ return $one; }else{ return $two; } } add_merge_function(\&merge_rules); # Main ####### if ($#ARGV + 1 != 1) { usage(); exit 1; } my $testlock = shift(@ARGV); if (!exists($locks{$testlock})) { usage(); exit 1; } my $lockref = $locks{$testlock}; my $start_line = ""; while (my $data = get_data()){ if ($data =~ /expr_stmt call_expr\(\(addr_expr function_decl\($$lockref{"lock"}\)/){ set_state(get_state() + 1); next; } if ($data =~ /if_cond (.*) ne_expr\(\(call_expr\(\(addr_expr function_decl\($$lockref{"lock"}\)/){ set_true_path(get_state() + 1); next; } if ($data =~ /if_cond (.*)(eq_expr)\(\(call_expr\(\(addr_expr function_decl\($$lockref{"lock"}\)/){ set_false_path(get_state() + 1); next; } if ($data =~ /expr_stmt call_expr\(\(addr_expr function_decl\($$lockref{"unlock"}\)/){ # set_state will overwrite the start_line so save it first $start_line = get_start_line(); set_state(get_state() - 1); next; } # Check that we left the CriticalSection if (($data =~ /^return_stmt/) or ($data =~ /^end_func/)){ if (get_state() > 0){ my $func = get_cur_func(); if (!exists($$lockref{"exceptions"}) || $func !~ /^(?:$$lockref{"exceptions"})$/) { my $msg = get_state() . " $testlock not released"; error_msg($msg, $start_line); }; } # reset the saved start_line if ($data =~ /^end_func/) { $start_line = ""; } } }