text: /*******************************************************************\
  
  Module: Assembler Mode
  
  Author: Michael Tautschnig
  
  \*******************************************************************/
  
  /// \file
  /// Assembler Mode
  
  #include "as_mode.h"
  
  #ifdef _WIN32
  #define EX_OK 0
  #define EX_USAGE 64
  #define EX_SOFTWARE 70
  #else
  #include <sysexits.h>
  #endif
  
  #include <fstream>
  #include <iostream>
  #include <cstring>
  
  #include <util/config.h>
  #include <util/get_base_name.h>
  #include <util/run.h>
  #include <util/tempdir.h>
  #include <util/version.h>
  
  #include "compile.h"
  #include "hybrid_binary.h"
  
  static std::string assembler_name(
    const cmdlinet &cmdline,
    const std::string &base_name)
  {
    if(cmdline.isset("native-assembler"))
      return cmdline.get_value("native-assembler");
  
    if(base_name=="as86" ||
       base_name.find("goto-as86")!=std::string::npos)
      return "as86";
  
    std::string::size_type pos=base_name.find("goto-as");
  
    if(pos==std::string::npos)
      return "as";
  
    std::string result=base_name;
    result.replace(pos, 7, "as");
  
    return result;
  }
  
  as_modet::as_modet(
    goto_cc_cmdlinet &_cmdline,
    const std::string &_base_name,
    bool _produce_hybrid_binary):
    goto_cc_modet(_cmdline, _base_name, message_handler),
    produce_hybrid_binary(_produce_hybrid_binary),
    native_tool_name(assembler_name(cmdline, base_name))
  {
  }
  
  /// does it.
  int as_modet::doit()
  {
    if(cmdline.isset('?') ||
       cmdline.isset("help"))
    {
      help();
      return EX_OK;
    }
  
    bool act_as_as86=
      base_name=="as86" ||
      base_name.find("goto-as86")!=std::string::npos;
  
    if((cmdline.isset('v') && act_as_as86) ||
       cmdline.isset("version"))
    {
      if(act_as_as86)
        status() << "as86 version: 0.16.17 (goto-cc " << CBMC_VERSION << ")"
                 << eom;
      else
        status() << "GNU assembler version 2.20.51.0.7 20100318"
                 << " (goto-cc " << CBMC_VERSION << ")" << eom;
  
      status()
        << '\n'
        << "Copyright (C) 2006-2014 Daniel Kroening, Christoph Wintersteiger\n"
        << "CBMC version: " << CBMC_VERSION << '\n'
        << "Architecture: " << config.this_architecture() << '\n'
        << "OS: " << config.this_operating_system() << eom;
  
      return EX_OK; // Exit!
    }
  
    auto default_verbosity = (cmdline.isset("w-") || cmdline.isset("warn")) ?
      messaget::M_WARNING : messaget::M_ERROR;
    eval_verbosity(
      cmdline.get_value("verbosity"), default_verbosity, message_handler);
  
    if(act_as_as86)
    {
      if(produce_hybrid_binary)
        debug() << "AS86 mode (hybrid)" << eom;
      else
        debug() << "AS86 mode" << eom;
    }
    else
    {
      if(produce_hybrid_binary)
        debug() << "AS mode (hybrid)" << eom;
      else
        debug() << "AS mode" << eom;
    }
  
    // get configuration
    config.set(cmdline);
  
    // determine actions to be undertaken
    compilet compiler(cmdline, message_handler, cmdline.isset("fatal-warnings"));
  
    if(cmdline.isset('b')) // as86 only
    {
      compiler.mode=compilet::COMPILE_LINK_EXECUTABLE;
      debug() << "Compiling and linking an executable" << eom;
    }
    else
    {
      compiler.mode=compilet::COMPILE_LINK;
      debug() << "Compiling and linking a library" << eom;
    }
  
    config.ansi_c.mode=configt::ansi_ct::flavourt::GCC;
  
    compiler.object_file_extension="o";
  
    if(cmdline.isset('o'))
    {
      compiler.output_file_object=cmdline.get_value('o');
      compiler.output_file_executable=cmdline.get_value('o');
    }
    else if(cmdline.isset('b')) // as86 only
    {
      compiler.output_file_object=cmdline.get_value('b');
      compiler.output_file_executable=cmdline.get_value('b');
    }
    else
    {
      compiler.output_file_object="a.out";
      compiler.output_file_executable="a.out";
    }
  
    // We now iterate over any input files
  
    temp_dirt temp_dir("goto-cc-XXXXXX");
  
    for(goto_cc_cmdlinet::parsed_argvt::iterator
        arg_it=cmdline.parsed_argv.begin();
        arg_it!=cmdline.parsed_argv.end();
        arg_it++)
    {
      if(!arg_it->is_infile_name)
        continue;
  
      // extract the preprocessed source from the file
      std::string infile=arg_it->arg=="-"?cmdline.stdin_file:arg_it->arg;
      std::ifstream is(infile);
      if(!is.is_open())
      {
        error() << "Failed to open input source " << infile << eom;
        return 1;
      }
  
      // there could be multiple source files in case GCC's --combine
      // was used
      unsigned outputs=0;
      std::string line;
      std::ofstream os;
      std::string dest;
  
      const std::string comment2=act_as_as86 ? "::" : "##";
  
      // search for comment2 GOTO-CC
      // strip comment2 from all subsequent lines
      while(std::getline(is, line))
      {
        if(line==comment2+" GOTO-CC")
        {
          if(outputs>0)
          {
            assert(!dest.empty());
            compiler.add_input_file(dest);
            os.close();
          }
  
          ++outputs;
          std::string new_name=
            get_base_name(infile, true)+"_"+
            std::to_string(outputs)+".i";
          dest=temp_dir(new_name);
  
          os.open(dest);
          if(!os.is_open())
          {
            error() << "Failed to tmp output file " << dest << eom;
            return 1;
          }
  
          continue;
        }
        else if(outputs==0)
          continue;
  
        if(line.size()>2)
          os << line.substr(2) << '\n';
      }
  
      if(outputs>0)
      {
        assert(!dest.empty());
        compiler.add_input_file(dest);
      }
      else
        warning() << "No GOTO-CC section found in " << arg_it->arg << eom;
    }
  
    // Revert to as in case there is no source to compile
  
    if(compiler.source_files.empty())
      return run_as(); // exit!
  
    // do all the rest
    if(compiler.doit())
      return 1; // GCC exit code for all kinds of errors
  
    // We can generate hybrid ELF and Mach-O binaries
    // containing both executable machine code and the goto-binary.
    if(produce_hybrid_binary)
      return as_hybrid_binary(compiler);
  
    return EX_OK;
  }
  
  /// run as or as86 with original command line
  int as_modet::run_as()
  {
    assert(!cmdline.parsed_argv.empty());
  
    // build new argv
    std::vector<std::string> new_argv;
    new_argv.reserve(cmdline.parsed_argv.size());
    for(const auto &a : cmdline.parsed_argv)
      new_argv.push_back(a.arg);
  
    // overwrite argv[0]
    new_argv[0]=native_tool_name;
  
    #if 0
    std::cout << "RUN:";
    for(std::size_t i=0; i<new_argv.size(); i++)
      std::cout << " " << new_argv[i];
    std::cout << '\n';
    #endif
  
    return run(new_argv[0], new_argv, cmdline.stdin_file, "", "");
  }
  
  int as_modet::as_hybrid_binary(const compilet &compiler)
  {
    std::string output_file="a.out";
  
    if(cmdline.isset('o'))
    {
      output_file=cmdline.get_value('o');
    }
    else if(cmdline.isset('b')) // as86 only
      output_file=cmdline.get_value('b');
  
    if(output_file=="/dev/null")
      return EX_OK;
  
    debug() << "Running " << native_tool_name
            << " to generate hybrid binary" << eom;
  
    // save the goto-cc output file
    std::string saved = output_file + ".goto-cc-saved";
    int result = rename(output_file.c_str(), saved.c_str());
    if(result!=0)
    {
      error() << "Rename failed: " << std::strerror(errno) << eom;
      return result;
    }
  
    result=run_as();
  
    if(result == 0)
    {
      result = hybrid_binary(
        native_tool_name,
        saved,
        output_file,
        compiler.mode == compilet::COMPILE_LINK_EXECUTABLE,
        get_message_handler());
    }
  
    return result;
  }
  
  /// display command line help
  void as_modet::help_mode()
  {
    std::cout << "goto-as understands the options of as plus the following.\n\n";
  }
  