#!/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|GDI_GetObjPtr|IMAPIPROP_Lock|LdrLockLoaderLock|ldt_lock|_lock|msiobj_lock|RtlAcquirePebLock|RtlLockHeap|server_enter_uninterrupted_section|USER_Lock|wined3d_mutex_lock|wined3d_wndproc_mutex_lock|X11DRV_DIB_Lock"},
	"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|RestoreThunkLock"},
	"USER_Lock" => {
	    "lock" => "USER_Lock",
	    "unlock" => "USER_Unlock",
	    "exceptions" => "create_window_handle|WIN_GetPtr"},
	"WIN_GetPtr" => {
	    "lock" => "WIN_GetPtr",
	    "unlock" => "WIN_ReleasePtr"},
	"wined3d_mutex_lock" => {
	    "lock" => "wined3d_mutex_lock",
	    "unlock" => "wined3d_mutex_unlock"},
	"wined3d_wndproc_mutex_lock" => {
	    "lock" => "wined3d_wndproc_mutex_lock",
	    "unlock" => "wined3d_wndproc_mutex_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 = "";
	}
    }
}

