#!/usr/bin/perl
#
# Usage:
#    while_for_check.pl < $file.sm
#
# Description:
#    searches for following construct:
#	while|for (something);
#    and does some sanity checks like:
#	- 'while|for (something); {' 
#	- 'while|for (something);' is an infinite loop
#
# Copyright 2003-2007 Michael Stefaniuc

use strict;
use smatch;
use ifcond;

sub error_msg($$$) {
    my $lineno = shift;
    my $funcpos = shift;
    my $msg = shift;
    print get_filename(), " $lineno $funcpos $msg\n";
}

sub is_invariant($) {
    # return's 1 if the expr evaluates to the same value on every call
    my $expr = shift;

    # having a '++', '--' or a function call should be safe
    if ($expr =~ m/\b(?:p(?:ost|re)(?:in|de)crement|call)_expr\b/) {
	return 0;
    }
    # check for +=, -=, *=, etc. and also "foo = foo->bar"
    if ($expr =~ m/modify_expr\(var_decl\(([^)]+)\)= (?:\w+_expr|component_ref)\(\((?:[^)]+ )?var_decl\(\1\)/) {
	return 0;
    }

    # default
    return 1;
}

# no need for the external state functions
my $state = "__none";
my $payload = "";

while (my $data = get_data()){
    if ($data =~ /^(while_cond|for_stmt)\s*(.+)$/) {
	if (ord($1) == 0x66) {
	    $state = "for";
	} else {
	    $state = "while";
	}
	$payload = $2;
	next;
    }
    if ($data =~ /^end_$state/) {
	# found a while|for (...);
	# checking what's in the (...) is expensive so first peek if we get
	# next a '{'. But first cache some data which we'll loose
	my $lineno = get_lineno();
	my $func_pos = get_func_pos();
	my $state_sav = $state;
	$state = "__none";

	$data = get_data();
	if ($data =~ /^cmpstmt_start/) {
	    # bummer ... found while|for (...); {
	    error_msg($lineno, $func_pos, "found '$state_sav (...); {'");
	    next;
	}
	# now check if the (...) is invariant
	if (is_invariant($payload)) {
	    # seems to be an infinite loop
	    error_msg($lineno, $func_pos, "possible infinite $state_sav loop");
	}
	# we peeked a line in advance
	redo;
    }
    # not found what we are looking for. reset the state.
    $state = "__none";
}
