From 8105324f1508ad0ec434f477d1235caea26f29a8 Mon Sep 17 00:00:00 2001
From: Botond Baranyi <botond.baranyi@ericsson.com>
Date: Wed, 30 Mar 2022 18:33:48 +0200
Subject: [PATCH] OOP: updated presence checking operators and object methods
 (issue #596)

Signed-off-by: Botond Baranyi <botond.baranyi@ericsson.com>
---
 compiler2/Type.cc            |  19 +++----
 compiler2/Type_codegen.cc    |  16 +++---
 compiler2/main.cc            |   3 ++
 compiler2/ttcn3/AST_ttcn3.cc |  14 +++--
 compiler2/ttcn3/Ttcnstuff.cc |  44 +++++++++++++++
 compiler2/ttcn3/Ttcnstuff.hh |   6 +++
 core/OOP.hh                  | 102 +++++++++++++++++++----------------
 regression_test/oop/oop.ttcn |  64 ++++++++++++++++++----
 8 files changed, 189 insertions(+), 79 deletions(-)

diff --git a/compiler2/Type.cc b/compiler2/Type.cc
index 1641dcb5d..3f59a33bc 100644
--- a/compiler2/Type.cc
+++ b/compiler2/Type.cc
@@ -1804,17 +1804,18 @@ namespace Common {
           return 0;
         }
         Ttcn::ClassTypeBody* class_ = t->get_class_type_body();
-        bool base_toString = false;
+        bool is_object_method = false;
         if (!class_->has_local_ass_withId(id)) {
-          if (id.get_name() == string("toString")) {
-            // the 'toString' method is not in the AST, but it is inherited by
-            // every class from the 'object' class
-            base_toString = true;
+          Ttcn::FormalParList* object_method_fplist =
+            Ttcn::ClassTypeBody::get_object_method_fplist(id.get_name());
+          if (object_method_fplist != NULL) {
+            // the methods of the 'object' class are not in the AST,
+            // but they are inherited by every class
+            is_object_method = true;
             if (!ref->parameters_checked()) {
-              Ttcn::FormalParList fp_list; // empty formal parameter list
               Ttcn::ParsedActualParameters* parsed_pars = ref->get_parsed_pars();
               Ttcn::ActualParList* ap_list = new Ttcn::ActualParList;
-              bool is_erroneous = fp_list.fold_named_and_chk(parsed_pars, ap_list);
+              bool is_erroneous = object_method_fplist->fold_named_and_chk(parsed_pars, ap_list);
               if (is_erroneous) {
                 delete ap_list;
                 return 0;
@@ -1823,7 +1824,7 @@ namespace Common {
               ap_list->set_my_scope(parsed_pars->get_my_scope());
               ref->set_parameter_list(ap_list, NULL);
             }
-            t = get_pooltype(T_USTR);
+            t = Ttcn::ClassTypeBody::get_object_method_return_type(id.get_name());
             // todo: set *last_method
           }
           else {
@@ -1834,7 +1835,7 @@ namespace Common {
             return 0;
           }
         }
-        if (!base_toString) {
+        if (!is_object_method) {
           Assignment* ass = class_->get_local_ass_byId(id);
           if (!class_->chk_visibility(ass, ref, subrefs->get_my_scope())) {
             // the method is not visible (the error has already been reported)
diff --git a/compiler2/Type_codegen.cc b/compiler2/Type_codegen.cc
index 5eece2f0d..649ae034a 100644
--- a/compiler2/Type_codegen.cc
+++ b/compiler2/Type_codegen.cc
@@ -3172,9 +3172,11 @@ void Type::generate_code_ispresentboundchosen(expression_struct *expr,
         const string& tmp_id2 = module->get_temporary_id();
         const char *tmp_id_str = tmp_id.c_str();
         const char *tmp_id2_str = tmp_id2.c_str();
+        bool next_is_class = next_t->get_type_refd_last()->typetype == T_CLASS;
         
         if (t->typetype == T_CLASS) {
-          expr->expr = mputprintf(expr->expr, "const %s%s %s = %s->%s;\n",
+          expr->expr = mputprintf(expr->expr, "%s%s%s %s = %s->%s;\n",
+	    next_is_class ? "" : "const ",
             next_t->get_genname_value(module).c_str(),
             is_template ? "_template" : "", tmp_id2_str,
             tmp_generalid_str, id.get_name().c_str());
@@ -3198,8 +3200,8 @@ void Type::generate_code_ispresentboundchosen(expression_struct *expr,
         }
 
         if (i != nof_refs - 1 || optype == ISCHOSEN) {
-          expr->expr = mputprintf(expr->expr, "%s = %s.is_bound();\n",
-            global_id.c_str(), tmp_id2_str);
+          expr->expr = mputprintf(expr->expr, "%s = %s.is_%s();\n",
+            global_id.c_str(), tmp_id2_str, next_is_class ? "present" : "bound");
         }
         if (i == nof_refs - 1) {
           switch (optype) {
@@ -3253,7 +3255,9 @@ void Type::generate_code_ispresentboundchosen(expression_struct *expr,
       const string& tmp_id = module->get_temporary_id();
       const char *tmp_id_str = tmp_id.c_str();
       
-      expr->expr = mputprintf(expr->expr, "const %s%s %s = %s->%s(",
+      bool next_is_class = next_t->get_type_refd_last()->typetype == T_CLASS;
+      expr->expr = mputprintf(expr->expr, "%s%s%s %s = %s->%s(",
+        next_is_class ? "" : "const ",
         next_t->get_genname_value(module).c_str(),
         is_template ? "_template" : "", tmp_id_str,
         tmp_generalid_str, id.get_name().c_str());
@@ -3261,8 +3265,8 @@ void Type::generate_code_ispresentboundchosen(expression_struct *expr,
       expr->expr = mputstr(expr->expr, ");\n");
 
       if (i != nof_refs - 1 || optype == ISCHOSEN) {
-        expr->expr = mputprintf(expr->expr, "%s = %s.is_bound();\n",
-          global_id.c_str(), tmp_id_str);
+        expr->expr = mputprintf(expr->expr, "%s = %s.is_%s();\n",
+          global_id.c_str(), tmp_id_str, next_is_class ? "present" : "bound");
       }
       if (i == nof_refs - 1) {
         switch (optype) {
diff --git a/compiler2/main.cc b/compiler2/main.cc
index c191b41fd..794d5087c 100644
--- a/compiler2/main.cc
+++ b/compiler2/main.cc
@@ -61,6 +61,7 @@
 #include "AST.hh"
 #include "asn1/AST_asn1.hh"
 #include "ttcn3/AST_ttcn3.hh"
+#include "ttcn3/Ttcnstuff.hh"
 
 #include "CodeGenHelper.hh"
 #include "Stopwatch.hh"
@@ -1272,6 +1273,8 @@ int main(int argc, char *argv[])
     }
   }
 
+  Ttcn::ClassTypeBody::object_method_cleanup();
+
   if (Kflag) {
     while (tcov_files != NULL) {
 	  tcov_file_list *next_file = tcov_files->next;
diff --git a/compiler2/ttcn3/AST_ttcn3.cc b/compiler2/ttcn3/AST_ttcn3.cc
index ba979174e..db942e97c 100644
--- a/compiler2/ttcn3/AST_ttcn3.cc
+++ b/compiler2/ttcn3/AST_ttcn3.cc
@@ -496,16 +496,13 @@ namespace Ttcn {
           Free(prev_expr);
         }
         const Identifier& id = *ref->get_id();
-        // 'ass' is null if the 'toString' method from the 'object' class is called
+        // 'ass' is null if one of the methods from the 'object' class is called
         Common::Assignment* ass = class_->has_local_ass_withId(id) ?
           class_->get_local_ass_byId(id) : NULL;
         expr->expr = mputprintf(expr->expr, "->%s(", id.get_name().c_str());
         FormalParList* fp_list = ass != NULL ? ass->get_FormalParList() :
-          new FormalParList; // the formal parameter list of 'toString' is empty
+          ClassTypeBody::get_object_method_fplist(id.get_name());
         ref->get_actual_par_list()->generate_code_noalias(expr, fp_list);
-        if (ass == NULL) {
-          delete fp_list;
-        }
         expr->expr = mputc(expr->expr, ')');
         type = ass != NULL ? ass->get_Type() :
           Common::Type::get_pooltype(Common::Type::T_USTR);
@@ -1505,9 +1502,10 @@ namespace Ttcn {
       
       expression_struct isbound_expr;
       Code::init_expr(&isbound_expr);
+      Common::Type* ass_type = ass->get_Type();
       isbound_expr.preamble = mputprintf(isbound_expr.preamble,
-        "boolean %s = %s.is_bound();\n", tmp_generalid_str,
-        ass_id_str);
+        "boolean %s = %s.is_%s();\n", tmp_generalid_str, ass_id_str,
+	ass_type->get_type_refd_last()->get_typetype() == Common::Type::T_CLASS ? "present" : "bound");
       namedbool p_optype;
       if (optype == Value::OPTYPE_ISBOUND) {
         p_optype = ISBOUND;
@@ -1520,7 +1518,7 @@ namespace Ttcn {
       } else {
         FATAL_ERROR("AST_ttcn3.cc::generate_code_ispresentboundchosen()");
       }
-      ass->get_Type()->generate_code_ispresentboundchosen(&isbound_expr, &subrefs, my_scope->get_scope_mod_gen(),
+      ass_type->generate_code_ispresentboundchosen(&isbound_expr, &subrefs, my_scope->get_scope_mod_gen(),
           tmp_generalid, ass_id2, is_template, p_optype, field);
 
       expr->preamble = mputstr(expr->preamble, isbound_expr.preamble);
diff --git a/compiler2/ttcn3/Ttcnstuff.cc b/compiler2/ttcn3/Ttcnstuff.cc
index eb62de86e..cc737ae0e 100644
--- a/compiler2/ttcn3/Ttcnstuff.cc
+++ b/compiler2/ttcn3/Ttcnstuff.cc
@@ -3066,6 +3066,9 @@ namespace Ttcn {
   // ===== ClassTypeBody
   // =================================
   
+  FormalParList* ClassTypeBody::object_toString_fplist = NULL;
+  FormalParList* ClassTypeBody::object_equals_fplist = NULL;
+
   ClassTypeBody::ClassTypeBody(Common::Identifier* p_class_id, boolean p_external, boolean p_final,
                                boolean p_abstract, boolean p_trait, Types* p_base_types,
                                Reference* p_runs_on_ref, Reference* p_mtc_ref, Reference* p_system_ref,
@@ -4511,4 +4514,45 @@ namespace Ttcn {
     Free(user_info);
   }
 
+  FormalParList* ClassTypeBody::get_object_method_fplist(const string& p_id)
+  {
+    if (p_id == string("toString")) {
+      if (object_toString_fplist == NULL) {
+	object_toString_fplist = new FormalParList;
+      }
+      return object_toString_fplist;
+    }
+    else if (p_id == string("equals")) {
+      if (object_equals_fplist == NULL) {
+	object_equals_fplist = new FormalParList;
+	FormalPar* fp = new FormalPar(FormalPar::A_PAR_VAL_IN, new Common::Type(Common::Type::T_CLASS),
+	  new Common::Identifier(Common::Identifier::ID_TTCN, string("obj")), NULL);
+	object_equals_fplist->add_fp(fp);
+      }
+      return object_equals_fplist;
+    }
+    else {
+      return NULL;
+    }
+  }
+
+  Common::Type* ClassTypeBody::get_object_method_return_type(const string& p_id)
+  {
+    if (p_id == string("toString")) {
+      return Common::Type::get_pooltype(Common::Type::T_USTR);
+    }
+    else if (p_id == string("equals")) {
+      return Common::Type::get_pooltype(Common::Type::T_BOOL);
+    }
+    else {
+      return NULL;
+    }
+  }
+
+  void ClassTypeBody::object_method_cleanup()
+  {
+    delete object_toString_fplist;
+    delete object_equals_fplist;
+  }
+
 } // namespace Ttcn
diff --git a/compiler2/ttcn3/Ttcnstuff.hh b/compiler2/ttcn3/Ttcnstuff.hh
index 9b5c9c51b..9f29c9fce 100644
--- a/compiler2/ttcn3/Ttcnstuff.hh
+++ b/compiler2/ttcn3/Ttcnstuff.hh
@@ -783,6 +783,8 @@ class ClassTypeBody : public Common::Scope, public Common::Location {
   bool checked;
   bool default_constructor; /// true if the class uses a default constructor
   map<FormalPar*, char> defpar_list;
+  static FormalParList* object_toString_fplist;
+  static FormalParList* object_equals_fplist;
   
 public:
   ClassTypeBody(Common::Identifier* p_class_id, boolean p_external, boolean p_final,
@@ -832,6 +834,10 @@ public:
   
   void generate_code(output_struct* target);
   void generate_class_skeleton(output_struct* target);
+
+  static FormalParList* get_object_method_fplist(const string& p_id);
+  static Common::Type* get_object_method_return_type(const string& p_id);
+  static void object_method_cleanup();
 };
 
 }
diff --git a/core/OOP.hh b/core/OOP.hh
index ecf8ffa5b..239ea48b3 100644
--- a/core/OOP.hh
+++ b/core/OOP.hh
@@ -17,51 +17,6 @@
 #include "Logger.hh"
 #include <functional>
 
-// OBJECT
-// ------
-
-class CLASS_BASE {
-  size_t ref_count;
-  boolean destructor; // true, if the destructor is currently running;
-  // also makes sure the object is not deleted again when inside the destructor
-
-  CLASS_BASE(const CLASS_BASE&); // copy disabled
-  CLASS_BASE operator=(const CLASS_BASE&); // assignment disabled
-  boolean operator==(const CLASS_BASE&); // equality operator disabled
-public:
-  CLASS_BASE(): ref_count(0), destructor(FALSE) {}
-  virtual ~CLASS_BASE() {
-    if (ref_count != 0) {
-      TTCN_error("Internal error: deleting an object with %lu reference(s) left.", ref_count);
-    }
-  }
-  void add_ref() { ++ref_count; }
-  boolean remove_ref() {
-    --ref_count;
-    if (destructor) {
-      return FALSE;
-    }
-    destructor = ref_count == 0;
-    return destructor;
-  }
-};
-
-class OBJECT : public CLASS_BASE {
-private:
-  OBJECT(const OBJECT&); // copy disabled
-  OBJECT operator=(const OBJECT&); // assignment disabled
-  boolean operator==(const OBJECT&); // equality operator disabled
-public:
-  OBJECT(): CLASS_BASE() { }
-  virtual ~OBJECT() { }
-  virtual void log() const {
-    TTCN_Logger::log_event_str("object: { }");
-  }
-  virtual UNIVERSAL_CHARSTRING toString() {
-    return UNIVERSAL_CHARSTRING("Object");
-  }
-  static const char* class_name() { return "object"; }
-};
 
 // OBJECT_REF
 // ----------
@@ -171,6 +126,10 @@ public:
     TTCN_error("Accessing a null reference.");
   }
   
+  const T* operator*() const { // de-referencing operator (for OBJECT::equals
+    return ptr;
+  }
+
   T* operator->() { // de-referencing operator (for methods)
     if (ptr != NULL) {
       return ptr;
@@ -195,11 +154,11 @@ public:
   }
   
   boolean is_bound() const {
-    return ptr != NULL;
+    return TRUE;
   }
   
   boolean is_value() const {
-    return ptr != NULL;
+    return TRUE;
   }
   
   boolean is_present() const {
@@ -241,6 +200,55 @@ boolean operator!=(null_type, const OBJECT_REF<T>& right_val) { // inequality op
   return right_val.ptr != NULL;
 }
 
+// OBJECT
+// ------
+
+class CLASS_BASE {
+  size_t ref_count;
+  boolean destructor; // true, if the destructor is currently running;
+  // also makes sure the object is not deleted again when inside the destructor
+
+  CLASS_BASE(const CLASS_BASE&); // copy disabled
+  CLASS_BASE operator=(const CLASS_BASE&); // assignment disabled
+  boolean operator==(const CLASS_BASE&); // equality operator disabled
+public:
+  CLASS_BASE(): ref_count(0), destructor(FALSE) {}
+  virtual ~CLASS_BASE() {
+    if (ref_count != 0) {
+      TTCN_error("Internal error: deleting an object with %lu reference(s) left.", ref_count);
+    }
+  }
+  void add_ref() { ++ref_count; }
+  boolean remove_ref() {
+    --ref_count;
+    if (destructor) {
+      return FALSE;
+    }
+    destructor = ref_count == 0;
+    return destructor;
+  }
+};
+
+class OBJECT : public CLASS_BASE {
+private:
+  OBJECT(const OBJECT&); // copy disabled
+  OBJECT operator=(const OBJECT&); // assignment disabled
+  boolean operator==(const OBJECT&); // equality operator disabled
+public:
+  OBJECT(): CLASS_BASE() { }
+  virtual ~OBJECT() { }
+  virtual void log() const {
+    TTCN_Logger::log_event_str("object: { }");
+  }
+  virtual UNIVERSAL_CHARSTRING toString() {
+    return UNIVERSAL_CHARSTRING("Object");
+  }
+  virtual BOOLEAN equals(const OBJECT_REF<OBJECT>& obj) {
+    return *obj == this;
+  }
+  static const char* class_name() { return "object"; }
+};
+
 // EXCEPTION
 // ---------
 
diff --git a/regression_test/oop/oop.ttcn b/regression_test/oop/oop.ttcn
index bca21d5a4..cf9812e7b 100644
--- a/regression_test/oop/oop.ttcn
+++ b/regression_test/oop/oop.ttcn
@@ -351,11 +351,21 @@ testcase tc_this() runs on CT {
   setverdict(pass);
 }
 
+
+type class Node {
+  var integer data;
+  var Node next;
+  public function get_next() return Node { return next; }
+  public function f() return integer { return 1; }
+}
+
 testcase tc_references() runs on CT {
   var BaseClass v_base := BaseClass.create(4, { 1, 2, 4 }, "a", 'FF'O, 1.0);
   var SubClass v_sub := SubClass.create(4, { 1, 2, 4 }, "a", 'FF'O, 1.0);
   var FinalClass v_final := FinalClass.create(4, { 1, 2, 4 }, "a", 'FF'O, 1.0, 8, "x", -1.5, *);
   var BaseClass v_null := null;
+  var Node v_node := Node.create(2, null);
+  var FinalClass v_null2 := null;
   
   var BaseClass v_ref1;
   if (v_ref1 != null) {
@@ -397,13 +407,13 @@ testcase tc_references() runs on CT {
   if (not isbound(v_sub)) {
     setverdict(fail, "#12");
   }
-  if (isbound(v_null)) {
+  if (not isbound(v_null)) {
     setverdict(fail, "#13");
   }
   if (not isvalue(v_final)) {
     setverdict(fail, "#14");
   }
-  if (isvalue(v_null)) {
+  if (not isvalue(v_null)) {
     setverdict(fail, "#15");
   }
   if (not ispresent(v_base)) {
@@ -416,17 +426,40 @@ testcase tc_references() runs on CT {
   if (v_base2 != null) {
     setverdict(fail, "#18");
   }
+  if (ispresent(v_null.get_var_temp())) {
+    setverdict(fail, "#19");
+  }
+  if (ispresent(v_node.get_next())) {
+    setverdict(fail, "#20");
+  }
+  if (ispresent(v_node.get_next().f())) {
+    setverdict(fail, "#21");
+  }
+  if (isbound(v_null.get_var_temp())) {
+    setverdict(fail, "#22");
+  }
+  if (not isbound(v_node.get_next())) {
+    setverdict(fail, "#23");
+  }
+  if (isbound(v_node.get_next().f())) {
+    setverdict(fail, "#24");
+  }
+  if (isvalue(v_null.get_var_temp())) {
+    setverdict(fail, "#25");
+  }
+  if (not isvalue(v_node.get_next())) {
+    setverdict(fail, "#26");
+  }
+  if (isvalue(v_node.get_next().f())) {
+    setverdict(fail, "#27");
+  }
+  if (ischosen(v_null2.get_uni().cs)) {
+    setverdict(fail, "#28");
+  }
   setverdict(pass);
 }
 
 
-type class Node {
-  var integer data;
-  var Node next;
-  public function get_next() return Node { return next; }
-  public function f() return integer { return 1; }
-}
-
 function f_test(in Node p1, inout Node p2, out Node p3,
                 in charstring p1_str, in charstring p2_str) return Node {
   if (log2str(p1) != p1_str) {
@@ -508,6 +541,9 @@ type class Something {
   public function toString() return universal charstring {
     return "Something";
   }
+  public function equals(object obj) return boolean { 
+    return true; 
+  }
 }
 
 testcase tc_object() runs on CT {
@@ -537,6 +573,16 @@ testcase tc_object() runs on CT {
   if (log2str(v_node2.get_data()) != "Something" or log2str(v_node2.get_next().get_data()) != "Node") {
     setverdict(fail, "v_node2: ", v_node2);
   }
+  var object v_obj3 := v_obj;
+  if (not v_obj3.equals(v_obj)) {
+    setverdict(fail, "v_obj3 is not equal to v_obj");
+  }
+  if (v_obj.equals(v_node)) {
+    setverdict(fail, "v_obj is equal to v_node");
+  }
+  if (not v_smthn.equals(v_node)) {
+    setverdict(fail, "overriden 'equals' failed");
+  }
   setverdict(pass);
 }
 
-- 
GitLab