/*
 *      Copyright (c) 2023, an unpublished work by CodeSecure, Inc.
 *                      ALL RIGHTS RESERVED
 *
 *      Copyright (c) 2008-2023, an unpublished work by GrammaTech, Inc.
 *                      ALL RIGHTS RESERVED
 *
 *      This software is furnished under a license and may be used and
 *      copied only in accordance with the terms of such license and the       
 *      inclusion of the above copyright notice.  This software or any
 *      other copies thereof may not be provided or otherwise made
 *      available to any other person.  Title to and ownership of the
 *      software is retained by CodeSecure, Inc.
 */

/**
 * Assume you have a program that defines these procedures:
 * 
 * void openParen(void){putchar( '(' );}
 * void closeParen(void){putchar( ')' );}
 * void openBracket(void){putchar( '[' );}
 * void closeBracket(void){putchar( ']' );}
 * 
 * This plugin will check that all strings output by this program
 * (assuming these are the only calls to putchar) have properly
 * balanced parenthesis and brackets.
 * 
 * The check is intraprocedural.  It is subject to false negatives in
 * the presence of loops and conditions that cause resource limits to
 * expire.  It is subject to false positives in the presence of
 * some interprocedural transfer of parenthesis ownership.
 * 
 * Example:
 * void foo(){ int i = rand(); 
 *             if( i ) openParen(); else openBracket(); 
 *             if( i ) closeParen(); else closeBracket(); 
 * }
 * This procedure can output the strings:
 * '()' and '[]'
 * 
 * These strings are well-formed:
 * []
 * ()
 * []()([])
 * 
 * These strings are not well-formed:
 * [)
 * (]
 * [[]
 * )
 * [(])
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>

#include "csonar_plugin.h"
#include "cs_pdg_vertex.h"
#include "cs_utility.h"


/* The user-defined state type on which the step visitor will
 * operate. The parenthesis matching is performed with the help of a
 * stack. Any cs_step_user_state_t arguments to step visitor functions
 * must be manually cast into callseq_state values so that the
 * functions can operate on them; any callseq_state values returned by
 * these operations will be implicitly cast to
 * cs_step_user_state_t. */

typedef struct{
    int pushes_seen;
    char *push_stack;
} callseq_state;


/* Forward declaration of the warning classes introduced by this
 * plug-in. */

static cs_warningclass_t missing_close, missing_open, mismatched;


/* The "open" function for the new step visitor. Initially, the
 * parenthesis stack is empty. */

static cs_step_user_state_t callseq_state_open(void *ctx)
{
    callseq_state *s = malloc( sizeof( *s ) );
    if( !s )
        cs_fatal("malloc() failed in callseq_plugin.c");
    s->pushes_seen = 0;
    s->push_stack = NULL;
    return s;
}

/* The "copy" function for the new step visitor. Copying a
 * callseq_state involves copying its pushes_seen and deep-copying its
 * push_stack. */

static cs_step_user_state_t callseq_state_copy(cs_step_user_state_t _src, void *ctx)
{
    /* cast the incoming cs_step_user_state_t to the user-defined
     * callseq_state */
    callseq_state *src = (callseq_state*)_src;
    callseq_state *dst = malloc( sizeof( *dst ) );
    if( !dst )
        cs_fatal("malloc() failed in callseq_plugin.c");
    dst->pushes_seen = src->pushes_seen;
    dst->push_stack = malloc( dst->pushes_seen * sizeof( dst->push_stack[0] ) );
    if( !dst->push_stack )
        cs_fatal("malloc() failed in callseq_plugin.c");
    memcpy( dst->push_stack, src->push_stack, 
            dst->pushes_seen * sizeof( dst->push_stack[0] ) );
    return dst;             
    
}


/* The "close" function for the new step visitor. Closing a
 * callseq_state involves freeing the memory used by its
 * push_stack. */                 
static void callseq_state_close(cs_step_user_state_t _s, void *ctx)
{
    callseq_state *s = (callseq_state*)_s;
    if( s->push_stack )
        free( s->push_stack );
    free( s );
}


/* A helper function. Given a PDG_VERTEX v and a string function_name,
 * return cs_true if v is a call site where function_name is called,
 * cs_false otherwise. */

static cs_boolean vertex_calls_function( 
    cs_pdg_vertex v, 
    const char *function_name )
{
    switch( cs_pdg_vertex_kind( v ) )
    {
    case cs_vertex_kind_call_site:
    {
        cs_pdg callee;
        if( csonar_pdg_vertex_callee( v, &callee ) == CS_SUCCESS )
        {
            char name[64];
            cs_size_t dummy;

            /* otherwise, this won't work */
            assert( strlen( function_name ) < sizeof( name ) );

            switch( cs_pdg_friendly_name( callee, name, 
                                          sizeof( name ), &dummy ) )
            {
            case CS_SUCCESS:
            case CS_TRUNCATED:
                if( strcmp( name, function_name ) == 0 )
                {
                    return cs_true;
                }
                break;
            default:
                cs_fatal("cs_pdg_friendly_name() failed in callseq_plugin.c");
            }
        }
        break;
    }
    default:
        break;
    }
    return cs_false;
}


/* The "transition" function for the new step visitor.  */

static cs_step_user_state_t transition( 
        cs_step_user_state_t _state,
        cs_pdg_vertex source_vertex, 
        cs_edge_label label, 
        cs_pdg_vertex destination_vertex, 
        cs_step_csonar_xform_t since_entry,
        cs_step_csonar_xform_t this_edge,
        cs_step_path_t path,
        void *ctx )
{
    callseq_state *state = (callseq_state*)_state;
    cs_boolean openParen, openBracket, closeParen, closeBracket;
    int realloc_size;

    /* First, check whether the source vertex at this CFG edge is a
     * call-site to one of the functions we are interested in. */
    openParen = vertex_calls_function( source_vertex, "openParen" );
    openBracket = vertex_calls_function( source_vertex, "openBracket" );
    closeParen = vertex_calls_function( source_vertex, "closeParen" );
    closeBracket = vertex_calls_function( source_vertex, "closeBracket" );
    
    printf( "Testing 1, 2, 3... This will show up in the analysis log page.\n" );

    /* If the source vertex is a call-site to openParen or
     * openBracket, update the state by pushing the appropriate kind
     * of parenthesis onto the stack. */
    if( openParen || openBracket )
    {
        state->pushes_seen++;
        realloc_size = sizeof( state->push_stack[0])  * state->pushes_seen;
        state->push_stack = realloc(state->push_stack, realloc_size);
        /* check that realloc() didn't fail */
        if ( !(state->push_stack) && realloc_size ) 
            cs_fatal("realloc() failed in callseq_plugin.c");
        state->push_stack[state->pushes_seen - 1] = openParen ? '(' : '[';
    }

    /* If the source vertex is a call-site to closeParen or
     * closeBracket, update the state by popping the stack. No
     * checking needs to be carried out here, as it was already
     * carried out when traversing into source_vertex (see below). */
    else if( closeParen || closeBracket )
    {
        if( state->pushes_seen > 0 )
        {
            /* give memory back to the system */
            state->pushes_seen--;
            realloc_size = sizeof( state->push_stack[0] ) * state->pushes_seen;         
            state->push_stack = realloc( state->push_stack, realloc_size );
            /* check that realloc() didn't fail */
            if ( !(state->push_stack) && realloc_size ) 
                cs_fatal("realloc() failed in callseq_plugin.c");
        }
    }

    /* Next, check whether the destination vertex at this CFG edge is
     * a call-site to one of the close-parenthesis functions. */
    closeParen = vertex_calls_function( destination_vertex, "closeParen" );
    closeBracket = vertex_calls_function( destination_vertex, "closeBracket" );
    if( closeParen || closeBracket )
    {
        /* If there is a closing parenthesis without any kind of
         * opening parenthesis to match it against, issue a
         * "missing_open" warning. */
        if( state->pushes_seen == 0 )
        {
            csonar_report_step_path_warning( 
                    path, missing_open, NULL, NULL );
        }
        /* If there is a closing ')' and the symbol on top of the
         * stack is not '(', issue a "mismatched" warning. */
        else if( closeParen && state->push_stack[state->pushes_seen - 1] != '(' )
        {
            csonar_report_step_path_warning( 
                    path, mismatched, NULL, NULL );
        }
        /* If there is a closing ']' and the symbol on top of the
         * stack is not '[', issue a "mismatched" warning. */
         else if( closeBracket && state->push_stack[state->pushes_seen - 1] != '[' )
        {
            csonar_report_step_path_warning( 
                    path, mismatched, NULL, NULL );
        }
    }
    /* If the traversal is about to exit the function while there are
     * outstanding unbalanced opening parentheses, issue a
     * "missing_close" warning */
    if( cs_pdg_vertex_kind( destination_vertex ) == cs_vertex_kind_exit 
        && state->pushes_seen > 0 )
    {
        csonar_report_step_path_warning( 
                path, missing_close, NULL, NULL );
    }
   /* Return the (possibly-updated) state. Note that we could have
    * freed the state that was passed in as an argument and returned a
    * newly-created one, if we wanted to.  The semantics of the
    * transition method are that it destroys the state it is given and
    * returns a new one.*/
    return state;
}


/* Define the three new warning classes in a setup visitor. */

static void setup_warning_classes(void *ctx){
    printf("setting up warning classes\n");
    missing_close = csonar_create_warningclass(
        "Missing close",
        "",
        2.0,
        csonar_bcf_none,
        csws_style );

    missing_open = csonar_create_warningclass(
        "Missing open",
        "",
        1.0,
        csonar_bcf_none,
        csws_style );
    
    mismatched = csonar_create_warningclass(
        "Mismatched close",
        "",
        1.0,
        csonar_bcf_none,
        csws_style );
}


static void cs_plug_main(void)
{                        
    static const cs_language langs[] = { csl_edgcp_c, csl_edgcp_cpp, csl_null };

    /* Set up a cs_step_visitor_dispatch_t containing the step visitor
     * functions defined above. */
    static cs_step_visitor_dispatch_t dispatch = {
        callseq_state_open,
        callseq_state_copy,
        callseq_state_close,
        transition,
    };
    
    /* Attach the new visitors. */
    csonar_add_setup_visitor( langs, setup_warning_classes, NULL );
    csonar_add_step_bottom_up_visitor( langs, &dispatch, NULL );
}
CS_DEFINE_PLUGIN(callseq_plugin)
