//
//      Copyright (c) 2023, an unpublished work by CodeSecure, Inc.
//                      ALL RIGHTS RESERVED
//
//      Copyright (c) 2017-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:
// [)
// (]
// [[]
// )
// [(])
// Must precede #include "csonar_api.hpp".
#define CS_CPP_IMPL

// Include the entire CodeSonar C++ API.
#include "csonar_api.hpp"  
 
// for std::cout
#include <iostream>

// for paren_state::push_stack
#include <stack>



static cs::warningclass missing_open_wc = cs::analysis::create_warningclass(
    "Missing open",
    "",
    1.0,
    cs::warningclass_flags::NONE,
    cs::warning_significance::STYLE);
 
static cs::warningclass missing_close_wc = cs::analysis::create_warningclass(
    "Missing close",
    "",
    2.0,
    cs::warningclass_flags::NONE,
    cs::warning_significance::STYLE);
 
static cs::warningclass mismatched_wc = cs::analysis::create_warningclass(
    "Mismatched close",
    "",
    1.0,
    cs::warningclass_flags::NONE,
    cs::warning_significance::STYLE);

// A helper function. Given a point p and a string fname,
// return True if p is a call site where fname is called,
// False otherwise. 
bool point_calls_function(cs::point p, std::string fname){
  try{
    cs::procedure pcallee = p.callee();
    if (pcallee.name()==fname) return true;
    return false;
  }
  catch  ( const cs::result &r ) {
    if (r == cs::result::ERROR_NOT_A_CALL_SITE) return false;
    throw;
  }
}

                   
class paren_state : public cs::step_state {
  std::stack<char> push_stack;

 public:
  // Default constructor  sets up the initial state
  paren_state(): cs::step_state(), push_stack(){ }

  ~paren_state(){ }
    
    // Copy constructor makes a copy of the stack.
  paren_state(const paren_state &s): cs::step_state(s), push_stack(s.push_stack){ }
    
  paren_state *copy() const{
    return new paren_state(*this);
  }

    // The transition() method does most of the step visitor work.
  void transition(cs::point srcpt,
                  cs::edge_label edgelabel,
                  cs::point destpt,
                  cs::step_xform tosrc_xform,
                  cs::step_xform edge_xform,
                  cs::step_path tosrc_path){
    char stacktop;
    bool dest_closeParen = point_calls_function(destpt, "closeParen");
    bool dest_closeBracket = point_calls_function(destpt, "closeBracket");
  
       
    std::cout<< "Testing 1, 2, 3... This will show up in the analysis log page." << std::endl;

    // If the source point is a call-site to openParen() or
    // openBracket(), update the state by pushing the appropriate
    // kind of parenthesis onto the stack.
    if (point_calls_function(srcpt, "openParen")){
      push_stack.push('(');
    }
    else if (point_calls_function(srcpt, "openBracket")){ 
      push_stack.push('[');
    }
     
    // If the source point 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 srcpt (see below)
    else if ( point_calls_function(srcpt, "closeParen") || point_calls_function(srcpt, "closeBracket")){ 
      if (! push_stack.empty()){
        push_stack.pop();
      }
    }
    // If the destination point is a call site to closeParen() or
    // closeBracket(), check to make sure the corresponding '(' or
    // ']' is on top of the stack and issue an appropriate warning
    // if not. 
    if ( dest_closeParen || dest_closeBracket) {

      // closeParen or closeBracket with empty stack (no top at all)
      // -> issue a 'missing open' warning
      if (push_stack.empty()){
        missing_open_wc.report(tosrc_path);
      }
      else {
        stacktop = push_stack.top();
        // closeParen without matching '(' on top of the stack
        // -> issue a 'mismatched' warning.
        if (dest_closeParen && stacktop != '('){
          mismatched_wc.report(tosrc_path);
        }
        // closeBracket without matching '[' on top of the stack
        // -> issue a 'mismatched' warning.
        else if (dest_closeBracket && stacktop != '['){
          mismatched_wc.report(tosrc_path);
        }
      }
    }
    // Traversal is about to exit the function while there
    // are outstanding unbalanced opening parentheses
    // -> issue a 'missing close' warning 
    if ((destpt.get_kind() == cs::point_kind::EXIT) && !push_stack.empty()){
      missing_close_wc.report(tosrc_path);
    }
  }
};
    

// Create an instance of paren_state, and add it as a point visitor.
// Visitors must be added either in the top-level scope or in
// cs_plug_main().
static char dummy = (cs::analysis::add_step_bottom_up_visitor(new paren_state()), 0);


// Every C++ plug-in for CodeSonar must define this function. 
static void cs_plug_main(void){}


// All CodeSonar C++ plug-ins must finish with a line of this form (at
// the top level). Note that the argument callseq_plugin to
// CS_DEFINE_PLUGIN corresponds to the name of the compiled plug-in
// (which will be either callseq_plugin.dll or
// callseq_plugin.so, depending on your system).
CS_DEFINE_PLUGIN(callseq_plugin)
