// This file is part of the AspectC++ compiler 'ac++'.
// Copyright (C) 1999-2003  The 'ac++' developers (see aspectc.org)
//                                                                
// This program is free software;  you can redistribute it and/or 
// modify it under the terms of the GNU General Public License as 
// published by the Free Software Foundation; either version 2 of 
// the License, or (at your option) any later version.            
//                                                                
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of 
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  
// GNU General Public License for more details.                   
//                                                                
// You should have received a copy of the GNU General Public      
// License along with this program; if not, write to the Free     
// Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, 
// MA  02111-1307  USA                                            

#include "ClangIntroSema.h"
#define private public  // experimental HACK!!!
#include "ClangIntroParser.h"
#undef private
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_20_0_0
#include "clang/Basic/DiagnosticParse.h"
#else
#include "clang/Parse/ParseDiagnostic.h"
#endif

// AspectC++ includes
#include "ACBase/Logger.h"
using ACBase::logger;
using ACBase::endlog;

#include "Transformer.h"
#include "PointCut.h"
#include "PointCutContext.h"
#include "OrderInfo.h"
#include "AdviceInfo.h"
#include "AspectInfo.h"
#include "IntroductionInfo.h"
#include "Plan.h"
#include "PointCutContext.h"
#include "CFlow.h"
#include "BackEndProblems.h"
#include "ACConfig.h"
#include "ACIntroducer.h"
#include "IncludeGraph.h"
#include "PointCutExpr.h"
#include "ModelBuilder.h"
#include "IntroductionUnit.h"
#include "version.h"
#include "ACModel/XmlModelWriter.h"
#include "ACModel/XmlModelReader.h"
#include "Phase1.h"
#include "NamespaceAC.h"

#include "ClangASTConsumer.h"
#include "ClangBinding.h"
#include "ClangPragmaHandler.h"

#include "clang/Frontend/CompilerInstance.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Parse/ParseAST.h"
#include "clang/AST/ASTContext.h"
#include "clang/Basic/Builtins.h"

// C++ includes
#include <sstream>
using std::stringstream;
using std::endl;
#include <stdlib.h> // for getenv
#ifdef _MSC_VER
#include <io.h>
#else
#include <unistd.h> // for access()!
#endif // _MSC_VER
#include <fcntl.h>
#include <filesystem>

using namespace ACBase;

ACFileID Transformer::work (const string &tunit_name, bool gen_deps) {

  // determine back-end compiler problems and setup code weaver
  BackEndProblems back_end_problems;
  back_end_problems._local_class       = _conf.problem_local_class ();
  back_end_problems._spec_scope        = _conf.problem_spec_scope ();
  back_end_problems._use_always_inline = !_conf.problem_force_inline ();
  back_end_problems._warn_macro        = _conf.warn_macro();
  back_end_problems._warn_deprecated   = _conf.warn_deprecated();
  _code_weaver.problems (back_end_problems);
  
  logger << "Path \"" << tunit_name << "\"" << endlog;

  ModelBuilder jpm (_err, _conf, _project);
  jpm.set_version(ac_version ());
  IncludeGraph ig (_project);
  
  // perform the transformation
  bool ok = (phase1 (tunit_name, jpm, ig, gen_deps) &&
             (gen_deps || phase2 (tunit_name, jpm, ig)));

  if (!ok)
    logger << "Aborting" << endlog;
  if (ok) {
    clang::CompilerInstance *ci = _project.get_compiler_instance();
    clang::FileManager &fm = ci->getFileManager ();
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_18_1_3
    auto fe = fm.getOptionalFileRef (tunit_name); // result is an llvm::ErrorOr<clang::FileEntry>
    return fe.has_value() ? fe.value() : ACFileID(0);
#else
    auto fe = fm.getFile (tunit_name); // result is an llvm::ErrorOr<clang::FileEntry>
    return fe ? fe.get() : ACFileID(0);
#endif
  }
  return ACFileID(0);
}

void Transformer::reinitializeSourceManager(clang::CompilerInstance *ci,
    CodeWeaver &cwb, const char *name) {
  ci->getDiagnostics().Reset();
  clang::SourceManager *NewSM = new clang::SourceManager(ci->getDiagnostics(),
                                                         ci->getFileManager());
  // Fetch code weaver changes into the new source manager.
  cwb.commit(*NewSM);
  clang::SourceManager &OldSM = ci->getSourceManager();
  // Transplant files that were changed in phase 1 but not rewritten in phase 2
  // into the new SourceManager.
  for (clang::SourceManager::fileinfo_iterator fi = OldSM.fileinfo_begin(),
                                               fe = OldSM.fileinfo_end();
       fi != fe; ++fi) {
    if (!_project.isVirtualFile(ACFileID(fi->first)))
      _touched_files.insert (fi->first);
    if (OldSM.isFileOverridden(fi->first) &&
        !NewSM->isFileOverridden(fi->first)) {
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_12_0_0
      auto buf = OldSM.getMemoryBufferForFileOrFake (fi->first);
      NewSM->overrideFileContents(fi->first,
        llvm::MemoryBuffer::getMemBufferCopy(buf.getBuffer(), buf.getBufferIdentifier()));
#else
      const llvm::MemoryBuffer *buf = fi->second->getRawBuffer();
      NewSM->overrideFileContents(fi->first,
          llvm::MemoryBuffer::getMemBufferCopy(buf->getBuffer(), buf->getBufferIdentifier()));
#endif
    }
  }

 // // print all buffers (for debugging code transformations, especially in phase 1)
 // for (clang::SourceManager::fileinfo_iterator fi = NewSM->fileinfo_begin(),
 //                                              fe = NewSM->fileinfo_end();
 //      fi != fe; ++fi) {
 //   const llvm::MemoryBuffer *buf = fi->second->getRawBuffer();
 //   cout << "BUF: " << buf->getBuffer().str() << endl;
 // }

  // Now insert the new source manager and initialize it.
  ci->resetAndLeakSourceManager ();
  ci->setSourceManager(NewSM);
  ci->InitializeSourceManager(clang::FrontendInputFile(name, clang::InputKind(clang::Language::CXX)));

  // Make sure the diagnostics engine doesn't have references to source
  // locations from the old source manager:
  ci->getDiagnostics().Reset();
}


bool Transformer::phase1 (const string &tunit_name,
    ModelBuilder &jpm, IncludeGraph &ig, bool gen_deps) {

#ifdef PROFILING
  static Profiler::Data data (__PRETTY_FUNCTION__, NULL);
  Profiler::ClockTicks start = Profiler::rdtsc ();
#endif

  clang::CompilerInstance *ci = _project.get_compiler_instance();
  // _err.set_source_manager(&ci->getSourceManager());
  _err.set_compiler_instance(ci);

  // Create a forced include for each aspect header file that should be
  // considered in this translation unit. Without this the phase 1 parser
  // would not be able to analyse aspect headers!
  for (auto &ah_file : _project.aspect_headers())
      _project.add_forced_include(ah_file.string());

  // fill the model while parsing the AOP elements of the language in phase 1
  logger << "Parsing ..." << endlog;
  Phase1 phase1 (jpm, _pos_hints, tunit_name, _project, _conf, _code_weaver, ig);
  ci->getPreprocessor().AddPragmaHandler(new ClangPragmaHandler(phase1));
  int result = phase1.run ();
  if (result == 0) {
    logger << "file is empty" << endlog;
  }
  if (result != 1)
    return false;

  if (gen_deps) {
    for (auto file : jpm.get_files()) {
      std::filesystem::path file_path;
      if (file->has_path()) {
        file_path = file->get_path()->get_canonical();
        file_path /= file->get_filename();
      }
      else
        file_path = file->get_filename();
      for (auto dep : file->get_affects())
        _deps.push_back({ file_path.string(), dep->get_given() });
    }
    return (_err.severity () < sev_error);
  }

//  cout << "Project Model after Phase1" << endl;
//  jpm.dump();

  // now remove the forced includes of aspect headers from the parser configuration
  // and generate includes at the end of the source code
  string aspect_includes = "";
  vector<string> incs;
  _project.get_forced_includes(incs);
  for (vector<string>::iterator i = incs.begin (); i != incs.end (); ++i) {
    if ((*i).rfind(".ah") == ((*i).size()-3)) {// aspect header!
      _project.remove_forced_include (*i);
      aspect_includes += "#include \"";
      auto incname = _project.include_path(tunit_name, *i);
      aspect_includes += incname;
      aspect_includes += "\"\n";
    }
  }
  if (aspect_includes != "") {
    logger << "Inserting aspect header includes" << endlog;
    aspect_includes =
        string ("\n#ifndef __ac_have_predefined_includes__\n"
        "/*** begin of aspect includes ***/\n") +
        aspect_includes +
        "/*** end of aspect includes ***/\n" +
        "#endif";

    _code_weaver.insert (_code_weaver.footer_pos (), aspect_includes);
  }

  // generate a string with aspect forward declarations
  logger << "Weaving Aspects Forward Declarations ..." << endlog;
  determine_aspect_fwd_decls (jpm);
//  _code_weaver.insert (_code_weaver.header_pos (), _aspect_fwd_decls);

//  log << "Inserting namespace AC" << endlog;
//  _code_weaver.insert( _code_weaver.header_pos(), NamespaceAC::def( _conf ) );
  
  // STU mode and not generating transformed headers!
  if (!_conf.iterate () && !_conf.ifiles () && !_conf.file_in ().empty()) {
    // expand forced includes in source code
    string forced_includes = "";
    vector<string> incs;
    _project.get_forced_includes(incs);
    for (vector<string>::iterator i = incs.begin (); i != incs.end (); ++i) {
      const string &filename = *i;
      forced_includes += "#include ";
      if (filename[0] == '\"' || filename[0] == '<')
        forced_includes += filename;
      else {
        forced_includes += "\"";
        forced_includes += filename;
        forced_includes += "\"";
      }
      forced_includes += "\n";
    }
    _project.remove_forced_includes();
    if (forced_includes != "") {
      logger << "Inserting forced includes" << endlog;
      _code_weaver.insert (_code_weaver.header_pos (), forced_includes);
    }
  }

  logger << "Committing" << endlog;
  // Reinitialize the source manager for phase 2. Commit changes into the new
  // SourceManager.
  _phase1_sm = &ci->getSourceManager();
  reinitializeSourceManager(ci, _code_weaver, tunit_name.c_str ());
//  reinitializeSourceManager(ci, _code_weaver, unit->name());
  // _err.set_source_manager(&ci->getSourceManager());

  // Some debugging code:
//  unit->print(cout);

//    log << "Stage1 save!" << endlog;
//    _project.save();
//    log << " done." << endlog;
//    exit(1);

#ifdef PROFILING
  Profiler::ClockTicks end = Profiler::rdtsc ();
  data._time += Profiler::duration (start, end);
  data._calls = 1;
#endif

  return (_err.severity () < sev_error);
}


bool Transformer::phase2 (const string &tunit_name, ModelBuilder &jpm, IncludeGraph &ig) {

#ifdef PROFILING
  static Profiler::Data data (__PRETTY_FUNCTION__, NULL);
  Profiler::ClockTicks start = Profiler::rdtsc ();
#endif

  Plan plan (_err, jpm);

  // setup the parser for phase 2; important already for diagnostics related to introductions
  clang::CompilerInstance *ci = _project.get_compiler_instance();
  std::unique_ptr<ClangASTConsumer> Consumer (new ClangASTConsumer(jpm));
  ci->createPreprocessor(clang::TU_Complete);
  ci->createASTContext();
  ci->setASTConsumer(std::move (Consumer));
  clang::Preprocessor &PP = ci->getPreprocessor();
  clang::Sema *sema = new clang::Sema(PP, ci->getASTContext (),
      ci->getASTConsumer(), clang::TU_Complete, NULL);

  ::ACIntroducer introducer (plan, _code_weaver, jpm, ig, _conf, _pos_hints, ci);
  intro_sema = new ClangIntroSema(*sema, introducer);
  ci->setSema (sema);

  // TODO: Maybe turning of system header warning should be controlled by a command line option.
  //       Ideally this should be done in ACProject, but it did not work, probably because of all
  //       the re-initializations (above)
  ci->getDiagnostics().setSuppressSystemWarnings(true);

  // make sure that attributes can be handled properly by ClangWeaverBase
  _code_weaver.set_annotation_map(&jpm.annotation_map());

  ci->getDiagnosticClient().BeginSourceFile(ci->getLangOpts(), &PP);
  PP.getBuiltinInfo().initializeBuiltins(PP.getIdentifierTable(),
                                         PP.getLangOpts());
  // FIXME: Here we enable 'skipFunctionBodies' in order to make the decision dynamically
  //        in ClangIntroSema::canSkipFunctionBody. We want to skip external functions bodies,
  //        because the clang parser sometimes is incompatible with gcc.
  ClangIntroParser P(PP, *sema, true);
  introducer._parser = &P;

  PP.EnterMainSourceFile();
  P.Initialize();

  logger << "Preparing introductions ..." << endlog;
  PointCutContext context (jpm);

  // perform semantic analysis of all pointcut expressions used for introductions
  const list<IntroductionInfo*> &intros = plan.introduction_infos ();
  for (list<IntroductionInfo*>::const_iterator i = intros.begin ();
       i != intros.end (); ++i) {
    IntroductionInfo *intro_info = *i;
    ACM_Introduction &intro = intro_info->intro();
    context.concrete_aspect(intro_info->aspect());
    context.pct_func (intro.get_pointcut());
    PointCutExpr *pce = (PointCutExpr*)intro_info->pointcut_expr().get();
    pce->semantics(_err, context);
    if (!(pce->possible_types() & JPT_Class)) {
      _err << sev_warning << TI_Pointcut::of(*intro.get_pointcut())->get_location()
           << "pointcut expression for introduction can't match class"
           << endMessage;
    }
  }

  // ... and also for the pointcut expressions in order advice
  const list<OrderInfo*> &orders = plan.order_infos ();
  for (list<OrderInfo*>::const_iterator i = orders.begin ();
       i != orders.end (); ++i) {
    OrderInfo *oi = *i;
    context.concrete_aspect (oi->aspect());

    static_cast<PointCutExpr*>(oi->jp_pce().get())->semantics(_err, context);
    for (auto &expr : oi->pces()) {
      PointCutExpr *pce = static_cast<PointCutExpr*>(expr.get());
      pce->semantics(_err, context);
      if (!(pce->possible_types() & JPT_Class)) {
        _err  << sev_warning
            << TI_Pointcut::of(*oi->order().get_pointcut ())->get_location()
            << "pointcut expression in order advice can't match aspect"
            << endMessage;
      }
    }
  }

  logger << "Parsing again ..." << endlog;

  // experimental start
#if 0
  ci->getPreprocessor().AddPragmaHandler(new JoinPointPragmaHandler(P, introducer));
  ci->getPreprocessor().addCommentHandler(new JoinPointCommentHandler(P));
#endif
  // experimental end

  clang::Parser::DeclGroupPtrTy ADecl;
  clang::ExternalASTSource *External = ci->getASTContext().getExternalSource();
  if (External)
    External->StartTranslationUnit(&ci->getASTConsumer ());

  // parse all top-level declarations
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_18_1_3
  clang::Sema::ModuleImportState IS = clang::Sema::ModuleImportState::NotACXX20Module;
  if (P.ParseFirstTopLevelDecl(ADecl, IS)) {
#else
  if (P.ParseTopLevelDecl(ADecl)) {
#endif
    if (!External && !sema->getLangOpts().CPlusPlus)
      P.Diag(clang::diag::ext_empty_translation_unit);
  } else {
    do {
      // If we got a null return and something *was* parsed, ignore it.  This
      // is due to a top-level semicolon, an action override, or a parse error
      // skipping something.
      if (ADecl && !ci->getASTConsumer ().HandleTopLevelDecl(ADecl.get()))
        break;
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_18_1_3
    } while (!P.ParseTopLevelDecl(ADecl, IS));
#else
    } while (!P.ParseTopLevelDecl(ADecl));
#endif
  }

  // Process any TopLevelDecls generated by #pragma weak.
  for (clang::SmallVectorImpl<clang::Decl *>::iterator
      I = sema->WeakTopLevelDecls().begin(),
      E = sema->WeakTopLevelDecls().end(); I != E; ++I)
    ci->getASTConsumer().HandleTopLevelDecl(clang::DeclGroupRef(*I));

  ci->getASTConsumer().HandleTranslationUnit(sema->getASTContext());

  //ci->getASTContext().getTranslationUnitDecl()->dump();

  // Abort in case of error.
  if (ci->getDiagnosticClient().getNumErrors() > 0) {
    // Indicate errors to Weaver.
    ci->getDiagnosticClient().EndSourceFile();
    _err.set_severity(sev_error);
    return false;
  }

  //#ifdef TRY_INTRODUCER
//  cout << "Printing semantic database..." << endl;
//  tunit->db ().Dump (cout, 10);
//#endif // TRY_INTRODUCER
  
  //    CPrintVisitor printer;
  //    printer.print (tunit->tree (), cout);
  
  // get all class from the join point model
  ProjectModel::Selection all_classes;
  jpm.select ((JoinPointType)(JPT_Class|JPT_Aspect), all_classes);
  // update the plan for intros
  for (ProjectModel::Selection::iterator iter = all_classes.begin ();
      iter != all_classes.end (); ++iter) {
    ACM_Class &jpl = (ACM_Class&)**iter;
    link_members (jpl);
    link_bases (jpl);
  }

  logger << "Weaving access control bypass classes ..." << endlog;
  insert_bypass_class (jpm);

  logger << "Weaving Join Points ..." << endlog;
  join_points (jpm, plan);
  if (_err.severity () >= sev_error) {
    ci->getDiagnosticClient().EndSourceFile();
    return false;
  }
      
  logger << "Final cleanup" << endlog;
  cleanup (jpm);

  if (_conf.dynamic ()) {
    logger << "Preparing for dynamic weaving" << endlog;
    prepare_dynamic_weaving (jpm);  
  }

  // generate a string with aspect header include directives
  determine_aspect_includes (ig);
  
  logger << "Committing" << endlog;
  // Now insert all IntroductionUnits back into the original files. After this
  // it's no longer legal to use the CodeWeaver.
  _code_weaver.resolve_introduction_units(introducer);

  // must be done late to be able to generate error messages!
  ci->getDiagnosticClient().EndSourceFile();
  // Reinitialize the source manager for the include expander step in Weaver.
  // Commit changes into the new SourceManager.
  clang::SourceManager *phase2_sm = &ci->getSourceManager();
  reinitializeSourceManager(ci, _code_weaver, tunit_name.c_str ());
  // _err.set_source_manager(&ci->getSourceManager());
  _phase1_sm->Release();
  phase2_sm->Release();

  // TODO: delete takes too much time and has no real use for ac++
  // so we skip it for now 
  // delete tunit;
  
#ifdef PROFILING
  Profiler::ClockTicks end = Profiler::rdtsc ();
  data._time += Profiler::duration (start, end);
  data._calls = 1;
#endif

  return (_err.severity () < sev_error);
}


void Transformer::determine_aspect_fwd_decls (ModelBuilder &jpm) {
  // get all aspects from the join point model
  ProjectModel::Selection all_aspects;
  jpm.select (JPT_Aspect, all_aspects);

  // remember that these aspects should become friend of all classes
  _aspect_fwd_decls = "";
  for (ProjectModel::Selection::iterator iter = all_aspects.begin ();
       iter != all_aspects.end (); ++iter) {
    ACM_Aspect &jpl = (ACM_Aspect&)**iter;
    _aspect_fwd_decls += "class ";
    _aspect_fwd_decls += jpl.get_name();
    _aspect_fwd_decls += ";\n";
  }
}

void Transformer::determine_aspect_includes (const IncludeGraph &ig) {
  // find all files that are included by aspect headers
  for (auto &ah_file : _project.aspect_headers())
    aspect_include_cluster (ah_file.string().c_str(), ig);
}

void Transformer::aspect_include_cluster (const char* ah_file,
  const IncludeGraph &ig) {

  // find the corresponding unit object for the aspect header file name
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_18_1_3
  auto fe = _project.get_compiler_instance()->getFileManager().getOptionalFileRef(ah_file);
  ACFileID fid;
  if (fe.has_value())
    fid = fe;
#else
  auto fe = _project.get_compiler_instance()->getFileManager().getFile(ah_file);
  ACFileID fid (fe ? fe.get() : 0);
#endif
  if (!fid.is_valid()) {
    _err << sev_fatal << "Unit for \"" << ah_file << "\" not found or no file."
         << endMessage;
    return;
  }

  // determine the aspect header cluster for this aspect header unit
  set<ACFileID> cluster_units;
  determine_aspect_cluster (fid, ig, cluster_units);

  AspectIncludeCluster aic(fid);
  aic.set_cluster(cluster_units);
  _aspect_include_clusters.push_back(aic);
}


void Transformer::determine_aspect_cluster (ACFileID ah_unit,
  const IncludeGraph &ig, set<ACFileID> &cluster) {
  
  // if the ah file is already a cluster member, we return immediately
  if (cluster.find (ah_unit) != cluster.end ())
    return;

  // otherwise the unit will be inserted
  cluster.insert (ah_unit);
      
  // find all header files that are included by this aspect header
  set<ACFileID> inc_units;
  ig.included_files (ah_unit, inc_units);
  
  // include all aspect headers that affect join points in these headers
  // and also aspect headers that affect the aspect header itself
  inc_units.insert (ah_unit);
  AspectIncludes &ais = _code_weaver.aspect_includes ();
  clang::SourceManager &sm =
      _project.get_compiler_instance()->getSourceManager();
  for (set<ACFileID>::iterator i = inc_units.begin ();
    i != inc_units.end (); ++i) {
    ACFileID inc_unit = *i;
    AspectIncludes::const_iterator aii = ais.find (inc_unit);
    if (aii != ais.end ()) {
      const set<AspectRef> &aspect_refs = aii->second;
      for (set<AspectRef>::const_iterator ari = aspect_refs.begin ();
        ari != aspect_refs.end (); ++ari) {
        ACM_Aspect &jpl_aspect = ari->_aspect->loc ();
        clang::Decl *aspect_decl = TI_Aspect::of(jpl_aspect)->decl();
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_18_1_3
        auto aspect_unit = sm.getFileEntryRefForID(sm.getFileID(aspect_decl->getLocation()));
#else
        const clang::FileEntry *aspect_unit =
            sm.getFileEntryForID(sm.getFileID(aspect_decl->getLocation()));
#endif
        // recursively analyze the cluster of this aspect header unit
        determine_aspect_cluster (aspect_unit, ig, cluster);
      }
    }
  }
}

void Transformer::prepare_dynamic_weaving (ModelBuilder &jpm) {

#if 0 // TODO: implement with clang?
  // mark all operations that access introduced attributes
  const list<AccessInfo> &access_infos = jpm.access_infos ();
  for (list<AccessInfo>::const_iterator i = access_infos.begin ();
    i != access_infos.end (); ++i) {
    if (i->_info->isStatic () || i->_info->isAnonymous ())
      continue;
    Unit *unit = (Unit*)i->_info->Tree ()->token ()->belonging_to ();
    while (unit->isMacroExp ())
      unit = ((MacroUnit*)unit)->CallingUnit ();
    if (IntroductionUnit::cast (unit)) {
      ACToken tok_before (i->_tree->token ());
      const WeavePos &before = _code_weaver.weave_pos (tok_before, WeavePos::WP_BEFORE);
      _code_weaver.insert (before, string ("/*** +access ") +
        string (i->_info->QualName ()) + string (" ***/\n"));      
      ACToken tok_after (i->_tree->end_token ());
      const WeavePos &after = _code_weaver.weave_pos (tok_after, WeavePos::WP_AFTER);
      _code_weaver.insert (after, string ("/*** -access ") +
        string (i->_info->QualName ()) + string (" ***/\n"));      
      if (i->_tree->NodeName () == CT_MembPtrExpr::NodeId () ||
          i->_tree->NodeName () == CT_MembRefExpr::NodeId ()) {
        CTree *op = i->_tree->Son (1);
        ACToken tok_before (op->token ());
        const WeavePos &before = _code_weaver.weave_pos (tok_before, WeavePos::WP_BEFORE);
        _code_weaver.insert (before, string ("/*** op ") +
          string (i->_info->QualName ()) + string (" ***/\n"));      
      }
    }
  }
  
  // mark all classes that contain dynamically introduced attributes
  ProjectModel::Selection classes;
  jpm.select ((JoinPointType)(JPT_Class|JPT_Aspect), classes);
  for (ProjectModel::Selection::iterator i = classes.begin ();
    i != classes.end (); ++i) {
    ACM_Class *cls = (ACM_Class*)*i;
    if (!cls->get_intro_target ())
      continue;
    bool mark_class = false;
    const TI_Class *ti = TI_Class::of (*cls);
    CClassInfo *ci = ti->class_info ();
    for (unsigned i = 0; i < ci->Attributes (); i++) {
      CAttributeInfo *attr = ci->Attribute (i);
      if (attr->isStatic () || attr->isAnonymous ())
        continue;
      Unit *unit = (Unit*)attr->Tree ()->token ()->belonging_to ();
      IntroductionUnit *iunit = IntroductionUnit::cast (unit);
      if (iunit) {
        mark_class = true;
        ACM_Name *jpl_aspect = (ACM_Name*)iunit->intro ()->get_parent ();
        ACToken tok_before (attr->Tree ()->ObjDecl ()->token ());
        const WeavePos &before = _code_weaver.weave_pos (tok_before, WeavePos::WP_BEFORE);
        _code_weaver.insert (before, string ("/*** +intro ") +
        signature (*jpl_aspect) + string (" ***/\n"));      
        ACToken tok_after (attr->Tree ()->ObjDecl ()->end_token ());
        const WeavePos &after = _code_weaver.weave_pos (tok_after, WeavePos::WP_AFTER);
        _code_weaver.insert (after, string ("/*** -intro ") +
        signature (*jpl_aspect) + string (" ***/\n"));      
      }
    }
    if (mark_class) {
      ACToken tok_before (((CT_ClassDef*)ci->Tree ())->ObjDecl ()->token ());
      const WeavePos &before = _code_weaver.weave_pos (tok_before, WeavePos::WP_BEFORE);
      _code_weaver.insert (before, string ("/*** +class ") +
      signature (*cls) + string (" ***/\n"));      
      
      ACToken tok_after (((CT_ClassDef*)ci->Tree ())->Members ()->end_token ());
      const WeavePos &after = _code_weaver.weave_pos (tok_after, WeavePos::WP_BEFORE);
      _code_weaver.insert (after, string ("/*** -class ") +
      signature (*cls) + string (" ***/\n"));      
    }
  }
#endif
}


void Transformer::cleanup (ModelBuilder &jpm) {
}


void Transformer::insert_bypass_class (ModelBuilder &jpm) {
  _code_weaver.bypass_info_clear();

  // Iterate over classes and structs, what about unions?
  ProjectModel::Selection all_classes;
  jpm.select ((JoinPointType)(JPT_Class|JPT_Aspect), all_classes);
  for (ProjectModel::Selection::iterator i = all_classes.begin ();
       i != all_classes.end (); ++i) {
    ACM_Class *cls = (ACM_Class*)*i;

    // handle all classes that are not on the blacklist
    if (!_code_weaver.bypass_in_blacklist(cls)) {
      _code_weaver.bypass_insert (cls);
    }
  }
}


void Transformer::join_points (ModelBuilder &jpm, Plan &plan) {

#ifdef PROFILING
  static Profiler::Data data (__PRETTY_FUNCTION__, NULL);
  Profiler::ClockTicks start = Profiler::rdtsc ();
#endif
  logger++;

  logger << "Advicecode manipulation" << endlog;
  // Iterate through advice
  ProjectModel::Selection advice_codes;
  jpm.select (JPT_AdviceCode, advice_codes);
  for (ProjectModel::Selection::iterator i = advice_codes.begin ();
       i != advice_codes.end (); ++i) {
    ACM_AdviceCode &code = *(ACM_AdviceCode*)*i;
    TI_AdviceCode  &ti   = *TI_AdviceCode::of (code);
    // setup ThisJoinPoint object of this advice code
    _code_weaver.setup_tjp(ti.this_join_point (), ti.decl ());
  }

  logger << "Collecting Advice" << endlog;
  // Iterate through advice
  logger++;

  PointCutContext context (jpm);

  // Create a data structure for collecting advice per join point type
  typedef list<AdviceInfo*> AdviceInfoList;
  typedef JoinPointType JPT;
  typedef map<JPT, AdviceInfoList> TypeAdviceMap;
  TypeAdviceMap advice_map;

  Plan::AspectContainer &aspects = plan.aspect_infos ();
  for (Plan::AspectContainer::iterator i = aspects.begin ();
    i != aspects.end (); ++i) {
    AspectInfo &aspect_info = (AspectInfo&)*i;
    ACM_Aspect &jpl_aspect = aspect_info.loc ();
    context.concrete_aspect (jpl_aspect);

    // setup thisJoinPoint for aspectOf function, if there is one
    clang::FunctionDecl *aspect_of_func = TI_Aspect::of (jpl_aspect)->aspectof();
    if (aspect_of_func) {
      logger << "Setting up thisJoinPoint for aspectof" << endlog;
      _code_weaver.setup_tjp(aspect_info.aspectof_this_join_point (),
          aspect_of_func);
    }

    // handle the advice for the current aspect
    list<AdviceInfo*> &advices = aspect_info.advice_infos ();
    for (list<AdviceInfo*>::const_iterator i = advices.begin ();
      i != advices.end (); ++i) {
      AdviceInfo *advice_info = *i;
      ACM_AdviceCode *code = &advice_info->code ();
      logger++;

      // let the pointcut evaluator create the pointcut expression object
      logger << "Create pointcut expression tree" << endlog;
      advice_info->pointcut_expr() = code->get_pointcut ()->get_parsed_expr();
      PointCutExpr *pce = (PointCutExpr*)advice_info->pointcut_expr().get ();
      context.pct_func (code->get_pointcut ());
      if (pce) {
        ArgVector new_arg_bindings;
        typedef ACM_Container<ACM_Arg, true> Container;
        Container &arguments = context.pct_func()->get_args();
        for (Container::iterator i = arguments.begin (); i != arguments.end (); ++i)
          new_arg_bindings.push_back (*i);
        context.arg_bindings ().push (&new_arg_bindings);
        pce->semantics(_err, context);
        context.arg_bindings ().pop ();

        // set the pointcut type before destroy the expression(!)
        advice_info->pointcut().type (pce->type() == PCE_CODE ? PointCut::PCT_CODE : PointCut::PCT_CLASS);

        // remember the advice for each joinpoint type that might match
        int mask = 1;
        while (mask) {
          if (mask & JPT_Code) {
            pair<TypeAdviceMap::iterator, bool> result =
              advice_map.insert (TypeAdviceMap::value_type ((TypeAdviceMap::key_type)mask, AdviceInfoList()));
            result.first->second.push_back (advice_info);
          }
          mask <<= 1;
        }
      }

      // copy the cflow trigger pointcut from the expressions to the pointcut
      for (set<PointCutExpr*>::const_iterator iter = context.cflows ().begin ();
           iter != context.cflows ().end (); ++iter) {
        advice_info->pointcut().cflow_triggers(((PCE_CFlow*)*iter)->arg_pointcut ());
      }
      logger--;
    }
    context.cflow_reset ();
  }
  logger--;

  // now iterate over all join points and check whether they match the
  // pointcut expressions
  logger << "Matching joinpoints" << endlog;
  logger++;

  for (TypeAdviceMap::iterator mi = advice_map.begin (); mi != advice_map.end ();
      ++mi) {
    JPT jp_type = mi->first;
    if( ! _conf.data_joinpoints() && ( jp_type & ( JPT_Get | JPT_Set | JPT_Ref | JPT_GetRef | JPT_SetRef ) ) )
      continue; // skip non enabled (cmdline) joinpoints

    AdviceInfoList &advice_info_list = mi->second;
    ProjectModel::Selection all;
    jpm.select (jp_type, all);
    for (ProjectModel::Selection::iterator iter = all.begin ();
        iter != all.end (); ++iter) {
      assert( static_cast<ACM_Node *>( *iter )->type_val() & JPT_Code );
      ACM_Code &jpl = (ACM_Code&)**iter;
      if( ! _conf.builtin_operators() && ( jpl.type_val() == JPT_Builtin ) ) // suppress joinpoints of builtin_operators
        continue;
      bool has_advice = false;

      for (AdviceInfoList::iterator li = advice_info_list.begin ();
          li != advice_info_list.end (); ++li) {
        AdviceInfo *advice_info = *li;
        context.pseudo_true (false);

        // Current pointcut function:
        ACM_Pointcut* pointcut_func = advice_info->code().get_pointcut();
        context.pct_func(pointcut_func);

        // now match
        Binding binding;
        Condition condition;
        PointCutExpr *expr = (PointCutExpr*)advice_info->pointcut_expr().get();

        // Does the pointcut expression match the join point?
        bool match_result = expr->match (jpl, context, binding, condition);

        // Check whether the joinpoint is filtered-out by #pragma acxx filter
        if (match_result && !is_pseudo(jpl)) {
          for (auto source : joinpoint_shadow(jpl)) {
            if (TI_AdviceCode::of(advice_info->code())->is_filtered_out(*source)) {
              match_result = false;
              break;
            }
          }
        }

        // Handle match result:
        if (match_result) {
          auto jp = advice_info->pointcut().insert(&jpl, condition); // copies and destroys the local condition
          Condition &condition = jp->second; // get and use the reference to the copy!

          // consider this joinpoint in the big plan
          plan.consider( &jpl, condition, advice_info );
          has_advice = true;

          // remember units for inclusion of aspect headers
          _code_weaver.add_aspect_include (&jpl, advice_info->aspect_info(),
                                  AspectRef::AR_ADVICE);

          // if the advice uses a joinpoint ID, make sure that one is allocated
          // for the matched joinpoint
          if (TI_AdviceCode::of (advice_info->code())->this_join_point().id() &&
              !jpl.has_jpid())
            jpl.set_jpid(jpm.alloc_jpid());

          if (jpl.type_val () == JPT_Call && !jpl.get_parent()) // TODO: pseudo
            continue;

          // check if the context variable binding is the same for all
          // non-pseudo join points
          if (!advice_info->binding ().is_compatible_with(binding)) {
            if (!advice_info->binding ().is_used()) {
              advice_info->binding () = binding;
            }
            else {
              _err << sev_error
                  << TI_Pointcut::of(*pointcut_func)->get_location()
                  << "incompatible argument bindings in pointcut expression"
                  << endMessage;
              // remove this erroneous advice from all lists
              for (TypeAdviceMap::iterator i = advice_map.begin (); i != advice_map.end ();
                  ++i) {
                i->second.remove (advice_info);
              }
              break;
            }
          }

          // check whether there are warnings related to builtins with short-circuit evaluation
          // these warning should be printed only once per advice
          if(_conf.warn_limitations() && _conf.builtin_operators()) {
            if (!advice_info->binding().problem_sc_arg_no_match() &&
              binding.problem_sc_arg_no_match()) {
              advice_info->binding().set_problem_sc_arg_no_match();
                _err << sev_warning
                  << TI_Pointcut::of(*pointcut_func)->get_location()
                  << "the 'args' pointcut function with three arguments will never match calls "
                    "to the built-in short-circuiting operator '?:'"
                  << endMessage;
            }
            if (!advice_info->binding().problem_sc_arg_used() &&
              binding.problem_sc_arg_used()) {
              advice_info->binding().set_problem_sc_arg_used();
                _err << sev_warning
                  << TI_Pointcut::of(*pointcut_func)->get_location()
                  << "the 'args' pointcut function is not guaranteed to match "
                    "the built-in short-circuiting operators '&&', '||' and '?:', "
                    "because an argument might be missing at runtime"
                  << endMessage;
            }
          }
        }
      }

      // only required for features of the Clang variant
      if( has_advice ) {
        // do some preplanning that might influence other joinpoints
        _code_weaver.preplanTransform( jpl );

        // remember we have a plan ( and thus weave at this jpl )
        TI_Code::of( jpl )->remember_planned();

        // for implicit joinpoints: make sure the parent is considered so we can weave calling code there
        ACM_Code *node = &jpl;
        while( node && is_implicit( *node ) ) {
          assert( static_cast<ACM_Node *>( node->get_parent() )->type_val() & JPT_Code );
          node = static_cast<ACM_Code *>( node->get_parent() );
          plan.consider( node );
          TI_Code::of( *node )->remember_planned();
          TI_Code::of( *node )->remember_implicit();
        }

        // if we weave for a builtin copy constructor, we replace array types in the class (see CodeWeaver::gen_special_member_function)
        // give the class a chance to know that
        if( jpl.type_val() == JPT_Construction ) {
          assert( static_cast<ACM_Any *>( jpl.get_parent() )->type_val() == JPT_Function );
          ACM_Function *func = static_cast<ACM_Function *>( jpl.get_parent() );
          assert( static_cast<ACM_Any *>( func->get_parent() )->type_val() & ( JPT_Class | JPT_Aspect ) );
          ACM_Class *cls = static_cast<ACM_Class *>( func->get_parent() );

          if( func->get_builtin() && get_arg_count( *func ) == 1 ) // parallel to check in CodeWeaver::cons_join_point
            TI_Class::of( *cls )->remember_builtin_copyconstructor_advice();
        }
      }
    }
  }

  // again iterate over all aspects
  for (Plan::AspectContainer::iterator i = aspects.begin ();
    i != aspects.end (); ++i) {
    AspectInfo &aspect_info = (AspectInfo&)*i;
    ACM_Aspect &jpl_aspect = aspect_info.loc ();
    context.concrete_aspect (jpl_aspect);
    int index = 0; // CFlow index (each CFlow has a unique index per aspect)

    // and now over all advice
    list<AdviceInfo*> advices = aspect_info.advice_infos ();
    for (list<AdviceInfo*>::const_iterator i = advices.begin ();
      i != advices.end (); ++i) {
      AdviceInfo *advice_info = *i;
      PointCut &pc = advice_info->pointcut();
      // consider the cflow trigger needed for this advice in the plan
      const list<PointCut*> &trigger_pcs = pc.cflow_triggers();
      for (list<PointCut*>::const_iterator iter = trigger_pcs.begin ();
           iter != trigger_pcs.end (); ++iter, ++index) {
        PointCut *trigger_pc = *iter;
        // Consider a cflow trigger for every joinpoint is pointcut
        for (auto jp : *trigger_pc) {
        // for (PointCut::iterator iter = trigger_pc->begin ();
        //      iter != trigger_pc->end (); ++iter) {
        //   const JoinPoint &jp = *iter;

          // consider this joinpoint in the big plan
          // plan.consider ((ACM_Code*)jp.location (), CFlow (advice_info, index));
          plan.consider ((ACM_Code*)jp.first, CFlow (advice_info, index));

          // remember units for inclusion of aspect headers
          // _code_weaver.add_aspect_include (jp.location (), aspect_info,
          //                                  AspectRef::AR_DECL);
          _code_weaver.add_aspect_include (jp.first, aspect_info,
                                           AspectRef::AR_DECL);
        }
      }
    }

    // TODO: why are these two nested calls necessary? => One function
    _code_weaver.insert_invocation_functions (&jpl_aspect,
        _code_weaver.aspect_ifct_defs(aspect_info));
  }
  logger--;

  // ordering must be done after the checks above, because it destroys the advice map of the planner
  logger << "Aspect ordering ..." << endlog;
  plan.order_code_joinpoints ();

  // now do final checks on the accumulated plan
  logger << "Checking aspect header dependency rules" << endlog;
  plan.check_dep_rules();

  // Don't weave if there were errors in the planning phase
  if (_err.severity () >= sev_error)
    return;

  const string &repo_file = _conf.repository();
  if (!repo_file.empty()) {
    ACModelFile model_file(repo_file);
    if (!model_file.open_or_create()) {
      _err << sev_error << "project repository '" << repo_file.c_str()
        << "' cannot be created or opened" << endMessage;
      return;
    }
 
    if (!model_file.is_empty()) {
      logger << "Updating project repository '" << repo_file << "'" << endlog;
      ProjectModel project_model;
      XmlModelReader reader;
      if (!reader.read (project_model, model_file)) {
        _err << sev_error << "project repository '" << repo_file.c_str()
          << "' is invalid" << endMessage;
        model_file.close();
        return;
      }
      if (project_model.get_version () != ac_version ())
        _err << sev_warning << "project file version '" << project_model.get_version().c_str ()
             << "' differs from ac++ version" << endMessage;

      // merge jpm and project_mode here
      project_model.merge (jpm);

      // save the merged model
      XmlModelWriter writer;
      if (!writer.write (project_model, model_file)) {
        _err << sev_error << "saving merged project file '" << repo_file.c_str() <<
          "'failed" << endMessage;
        model_file.close();
        return;
      }
    }
    else {
      logger << "Creating project repository '" << repo_file << "'" << endlog;
      XmlModelWriter writer;
      if (!writer.write (jpm, model_file)) {
        _err << sev_error << "saving new project file '" << repo_file.c_str() <<
          "'failed" << endMessage;
        model_file.close();
        return;
      }
    }
    if (!model_file.close()) {
      _err << sev_error << "project repository '" << repo_file.c_str()
        << "' cannot be closed" << endMessage;
      return;
    }
  }

  logger << "Type Check Functions" << endlog;
  logger++;
  const TypeCheckSet &checks_false = plan.type_checks_false ();
  for (TypeCheckSet::const_iterator iter = checks_false.begin ();
       iter != checks_false.end (); ++iter) {
    logger << "check for " << iter->second << " in "
        << signature(*iter->first) << " is false" << endlog;
    _code_weaver.type_check (iter->first, iter->second, false);
  }
  const TypeCheckSet &checks_true = plan.type_checks_true ();
  for (TypeCheckSet::const_iterator iter = checks_true.begin ();
       iter != checks_true.end (); ++iter) {
    logger << "check for " << iter->second << " in "
        << signature (*iter->first) << " is true" << endlog;
    _code_weaver.type_check (iter->first, iter->second, true);
  }
  logger--;

  logger << "Access Join Points" << endlog;
  logger++;
  for (int i = 0; i < plan.access_jp_plans (); i++) {
    ACM_Access &jp_loc = plan.access_jp_loc (i);

    if( is_implicit( jp_loc ) ) // check for implicit joinpoints
      continue; // skip here, they are handled internally by the weaver

    logger << jp_loc.type_str() << ": " << signature (jp_loc) << endlog;
    if( jp_loc.type_val() == JPT_Builtin ) { // print implicit too
      ACM_Builtin &jp_builtin = static_cast<ACM_Builtin &>( jp_loc );
      typedef const ACM_Container<ACM_Access, true> SubList;
      SubList &implicit = jp_builtin.get_implicit_access();

      logger++;
      for( SubList::const_iterator it = implicit.begin(); it != implicit.end(); it++ ) { // TODO iterate over all nesting level (not yet neccessary as no deep nesting)
        if( (*it)->has_plan() ) // only print jpls for which we weave
          logger << (*it)->type_str() << ": " << signature( **it ) << endlog;
      }
      logger--;
    }

    // handle access joinpoint itself
    _code_weaver.access_join_point (&jp_loc);
  }
  logger--;

  logger << "Execution Join Points" << endlog;
  logger++;
  for (int i = 0; i < plan.exec_jp_plans (); i++) {
    ACM_Execution &jp_loc = plan.exec_jp_loc (i);
    logger << signature (jp_loc) << endlog;

    // handle exec joinpoint itself
    _code_weaver.exec_join_point (&jp_loc);
  }
  logger--;

  logger << "Construction Join Points" << endlog;
  logger++;
  for (int i = 0; i < plan.cons_jp_plans (); i++) {
    ACM_Construction &jp_loc = plan.cons_jp_loc (i);
    logger << signature (jp_loc) << endlog;

    // handle construction joinpoint itself
    _code_weaver.cons_join_point (&jp_loc);
  }
  logger--;

  logger << "Destruction Join Points" << endlog;
  logger++;
  for (int i = 0; i < plan.dest_jp_plans (); i++) {
    ACM_Destruction &jp_loc = plan.dest_jp_loc (i);
    logger << signature (jp_loc) << endlog;

    // handle destruction joinpoint itself
    _code_weaver.dest_join_point (&jp_loc);
  }
  logger--;

  logger--;

  logger << "Aspect Includes ..." << endlog;
  _code_weaver.insert_aspect_includes ();

#ifdef PROFILING
  Profiler::ClockTicks end = Profiler::rdtsc ();
  data._time += Profiler::duration (start, end);
  data._calls = 1;
#endif
}

// add references to the introduced elements to the plan
void Transformer::link_members (ACM_Class &jpl) {
  if (!jpl.has_plan ())
    return;

  ACM_ClassPlan *plan = jpl.get_plan ();

  typedef ACM_Container<ACM_Any, true> Container;
  Container &children = jpl.get_children();
  typedef ACM_Container<ACM_MemberIntro, true> Container2;
  Container2 &member_intros = plan->get_member_intros();
  clang::SourceManager &sm = _project.get_compiler_instance()->getSourceManager();
  for (Container::iterator i = children.begin (); i != children.end (); ++i) {
    ACM_Any *any = *i;
    if (any->type_val() == JPT_Class) {
      ACM_Class *cls = (ACM_Class*)any;
      clang::FileID fid = sm.getFileID(TI_Class::of(*cls)->decl()->getLocation());
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_12_0_0
      llvm::MemoryBufferRef u = sm.getBufferOrFake(fid);
#else
      const llvm::MemoryBuffer *u = sm.getBuffer(fid);
#endif
      if (IntroductionUnit::cast(u)) {
        ACM_Introduction *intro = IntroductionUnit::cast(u)->intro();
        for (Container2::iterator mi = member_intros.begin ();
            mi != member_intros.end(); ++mi)
          if ((*mi)->get_intro() == intro)
            (*mi)->get_members().insert(cls);
      }
    }
    else if (any->type_val() == JPT_Function) {
      ACM_Function *func = (ACM_Function*)any;
      if (func->get_builtin())
        continue;
      clang::FileID fid = sm.getFileID(TI_Function::of(*func)->decl()->getLocation());
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_12_0_0
      llvm::MemoryBufferRef u = sm.getBufferOrFake(fid);
#else
      const llvm::MemoryBuffer *u = sm.getBuffer(fid);
#endif
      if (IntroductionUnit::cast(u)) {
        ACM_Introduction *intro = IntroductionUnit::cast(u)->intro();
        for (Container2::iterator mi = member_intros.begin ();
            mi != member_intros.end(); ++mi)
          if ((*mi)->get_intro() == intro)
            (*mi)->get_members().insert(func);
      }
    }
  }
}

// add references to the introduced elements to the plan
void Transformer::link_bases (ACM_Class &jpl) {
  if (!jpl.has_plan ())
    return;

  ACM_ClassPlan *plan = jpl.get_plan ();
  clang::CXXRecordDecl *ci = llvm::cast<clang::CXXRecordDecl>(TI_Class::of(jpl)->decl());

  typedef ACM_Container<ACM_Class, false> Container;
  Container &bases = jpl.get_bases();
  typedef ACM_Container<ACM_BaseIntro, true> Container2;
  Container2 &base_intros = plan->get_base_intros();
  clang::SourceManager &sm = _project.get_compiler_instance()->getSourceManager();
  for (Container::iterator i = bases.begin (); i != bases.end (); ++i) {
    ACM_Class *cls = *i;
    // find the base class info in Puma's semantic data structure
    for (clang::CXXRecordDecl::base_class_iterator bi = ci->bases_begin(),
                                                   be = ci->bases_end();
         bi != be; ++bi) {
      if (bi->getType()->getAsCXXRecordDecl() != TI_Class::of (*cls)->decl())
        continue;
      clang::FileID fid = sm.getFileID(bi->getType()->getAsCXXRecordDecl()->getLocation());
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_12_0_0
      llvm::MemoryBufferRef u = sm.getBufferOrFake(fid);
#else
      const llvm::MemoryBuffer *u = sm.getBuffer(fid);
#endif
      if (IntroductionUnit::cast(u)) {
        ACM_Introduction *intro = IntroductionUnit::cast(u)->intro();
        for (Container2::iterator bi = base_intros.begin ();
            bi != base_intros.end(); ++bi) {
          if ((*bi)->get_intro () == intro) {
            (*bi)->get_bases().insert(cls);
          }
        }
      }
    }
  }
}
