view libinterp/octave-value/cdef-object.cc @ 30564:796f54d4ddbf stable

update Octave Project Developers copyright for the new year In files that have the "Octave Project Developers" copyright notice, update for 2021. In all .txi and .texi files except gpl.txi and gpl.texi in the doc/liboctave and doc/interpreter directories, change the copyright to "Octave Project Developers", the same as used for other source files. Update copyright notices for 2022 (not done since 2019). For gpl.txi and gpl.texi, change the copyright notice to be "Free Software Foundation, Inc." and leave the date at 2007 only because this file only contains the text of the GPL, not anything created by the Octave Project Developers. Add Paul Thomas to contributors.in.
author John W. Eaton <jwe@octave.org>
date Tue, 28 Dec 2021 18:22:40 -0500
parents a61e1a0f6024
children 83f9f8bda883
line wrap: on
line source

////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2012-2022 The Octave Project Developers
//
// See the file COPYRIGHT.md in the top-level directory of this
// distribution or <https://octave.org/copyright/>.
//
// This file is part of Octave.
//
// Octave 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 3 of the License, or
// (at your option) any later version.
//
// Octave 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 Octave; see the file COPYING.  If not, see
// <https://www.gnu.org/licenses/>.
//
////////////////////////////////////////////////////////////////////////

#if defined (HAVE_CONFIG_H)
#  include "config.h"
#endif

#include "cdef-class.h"
#include "cdef-object.h"
#include "cdef-property.h"
#include "cdef-utils.h"
#include "interpreter.h"
#include "interpreter-private.h"
#include "ov-classdef.h"

// Define to 1 to enable debugging statements.
#define DEBUG_TRACE 0

namespace octave
{
  void
  cdef_object_rep::release (const cdef_object& obj)
  {
    // We need to be careful to keep a reference to the object if we are
    // calling the delete method.  The object is passed to the delete
    // method as an argument and if the count is already zero when we
    // do that, then we will increment the count while creating the
    // argument list for the delete method and then it will be decremented
    // back to zero and we'll find ourselves in an infinite loop.

    if (m_count - 1 > static_count ())
      {
        --m_count;
        return;
      }

    if (is_handle_object () && ! is_meta_object ())
      {
        unwind_protect frame;

        // Clear interrupts.
        frame.protect_var (octave_interrupt_state);
        octave_interrupt_state = 0;

        // Disallow quit().
        frame.protect_var (quit_allowed);
        quit_allowed = false;

        interpreter& interp = __get_interpreter__ ("cdef_object_rep::release");

        interpreter_try (frame);

        try
          {
            // Call classdef "delete()" method on object
            get_class ().delete_object (obj);
          }
        catch (const interrupt_exception&)
          {
            interp.recover_from_exception ();

            warning ("interrupt occurred in handle class delete method");
          }
        catch (const execution_exception& ee)
          {
            interp.recover_from_exception ();

            std::string msg = ee.message ();

            warning ("error caught while executing handle class delete method:\n%s\n",
                     msg.c_str ());
          }
        catch (const exit_exception&)
          {
            // This shouldn't happen since we disabled quit above.
            warning ("exit disabled while executing handle class delete method");
          }
        catch (...) // Yes, the black hole.  We're in a d-tor.
          {
            // This shouldn't happen, in theory.
            warning ("internal error: unhandled exception in handle class delete method");
          }
      }

    // Now it is safe to set the count to zero.
    m_count--;

    destroy ();
  }

  cdef_class
  cdef_object_rep::get_class (void) const
  {
    err_invalid_object ("get_class");
  }

  std::string
  cdef_object_rep::class_name (void) const
  {
    return get_class ().get_name ();
  }

  string_vector
  cdef_object_rep::map_keys (void) const
  {
    cdef_class cls = get_class ();

    if (cls.ok ())
      return cls.get_names ();

    return string_vector ();
  }

  octave_map
  cdef_object::map_value (void) const
  {
    octave_map retval;

    warning_with_id ("Octave:classdef-to-struct",
                     "struct: converting a classdef object into a struct "
                     "overrides the access restrictions defined for properties. "
                     "All properties are returned, including private and "
                     "protected ones.");

    cdef_class cls = get_class ();

    if (cls.ok ())
      {
        std::map<std::string, cdef_property> props;

        props = cls.get_property_map (cdef_class::property_all);

        // FIXME: Why not const here?
        for (auto& prop_val : props)
          {
            if (is_array ())
              {
                Array<cdef_object> a_obj = array_value ();

                Cell cvalue (a_obj.dims ());

                for (octave_idx_type i = 0; i < a_obj.numel (); i++)
                  cvalue (i) = prop_val.second.get_value (a_obj(i), false);

                retval.setfield (prop_val.first, cvalue);
              }
            else
              {
                Cell cvalue (dim_vector (1, 1),
                             prop_val.second.get_value (*this, false));

                retval.setfield (prop_val.first, cvalue);
              }
          }
      }

    return retval;
  }

  cdef_class
  cdef_object::get_class (void) const
  {
    return m_rep->get_class ();
  }

  cdef_class
  cdef_object_base::get_class (void) const
  {
    return cdef_class (m_klass);
  }

  void
  cdef_object_base::set_class (const cdef_class& cls)
  {
    if ((m_klass.ok () && cls.ok () && cls != get_class ())
        || (m_klass.ok () && ! cls.ok ())
        || (! m_klass.ok () && cls.ok ()))
      {
        m_klass = cls;
      }
  }

  cdef_object_rep *
  cdef_object_base::make_array (void) const
  {
    cdef_object_rep *r = new cdef_object_array ();

    r->set_class (get_class ());

    return r;
  }

  octave_value_list
  cdef_object_array::subsref (const std::string& type,
                              const std::list<octave_value_list>& idx,
                              int /* nargout */, std::size_t& skip,
                              const cdef_class& /* context */, bool auto_add)
  {
    octave_value_list retval;

    skip = 1;

    switch (type[0])
      {
      case '(':
        {
          const octave_value_list& ival = idx.front ();

          if (ival.empty ())
            {
              m_count++;
              retval(0) = to_ov (cdef_object (this));
              break;
            }

          bool is_scalar = true;
          Array<idx_vector> iv (dim_vector (1, ival.length ()));

          for (int i = 0; i < ival.length (); i++)
            {
              try
                {
                  iv(i) = ival(i).index_vector ();
                }
              catch (index_exception& ie)
                {
                  // Rethrow to allow more info to be reported later.
                  ie.set_pos_if_unset (ival.length (), i+1);
                  throw;
                }

              is_scalar = is_scalar && iv(i).is_scalar ();
            }

          Array<cdef_object> ires = m_array.index (iv, auto_add);

          // If resizing is enabled (auto_add = true), it's possible
          // indexing was out-of-bound and the result array contains
          // invalid cdef_objects.

          if (auto_add)
            fill_empty_values (ires);

          if (is_scalar)
            retval(0) = to_ov (ires(0));
          else
            {
              cdef_object array_obj (new cdef_object_array (ires));

              array_obj.set_class (get_class ());

              retval(0) = to_ov (array_obj);
            }
        }
        break;

      case '.':
        if (type.size () == 1 && idx.size () == 1)
          {
            Cell c (dims ());

            octave_idx_type n = m_array.numel ();

            // dummy variables
            std::size_t dummy_skip;
            cdef_class dummy_cls;

            for (octave_idx_type i = 0; i < n; i++)
              {
                octave_value_list r = m_array(i).subsref (type, idx, 1,
                                                          dummy_skip,
                                                          dummy_cls);

                if (r.length () > 0)
                  c(i) = r(0);
              }

            retval(0) = octave_value (c, true);

            break;
          }
        OCTAVE_FALLTHROUGH;

      default:
        error ("can't perform indexing operation on array of %s objects",
               class_name ().c_str ());
        break;
      }

    return retval;
  }

  octave_value
  cdef_object_array::subsasgn (const std::string& type,
                               const std::list<octave_value_list>& idx,
                               const octave_value& rhs)
  {
    octave_value retval;

    switch (type[0])
      {
      case '(':
        if (type.length () == 1)
          {
            cdef_object rhs_obj = to_cdef (rhs);

            if (rhs_obj.get_class () != get_class ())
              error ("can't assign %s object into array of %s objects",
                     rhs_obj.class_name ().c_str (),
                     class_name ().c_str ());

            const octave_value_list& ival = idx.front ();
            bool is_scalar = true;
            Array<idx_vector> iv (dim_vector (1, ival.length ()));

            for (int i = 0; i < ival.length (); i++)
              {
                try
                  {
                    iv(i) = ival(i).index_vector ();
                  }
                catch (index_exception& ie)
                  {
                    ie.set_pos_if_unset (ival.length (), i+1);
                    throw;   // var name set in pt-idx.cc / pt-assign.cc
                  }

                is_scalar = is_scalar && iv(i).is_scalar ();
              }

            Array<cdef_object> rhs_mat;

            if (! rhs_obj.is_array ())
              {
                rhs_mat = Array<cdef_object> (dim_vector (1, 1));
                rhs_mat(0) = rhs_obj;
              }
            else
              rhs_mat = rhs_obj.array_value ();

            octave_idx_type n = m_array.numel ();

            m_array.assign (iv, rhs_mat, cdef_object ());

            if (m_array.numel () > n)
              fill_empty_values ();

            m_count++;
            retval = to_ov (cdef_object (this));
          }
        else
          {
            const octave_value_list& ivl = idx.front ();

            // Fill in trailing singleton dimensions so that
            // array.index doesn't create a new blank entry (bug #46660).
            const octave_idx_type one = static_cast<octave_idx_type> (1);
            const octave_value_list& ival = ivl.length () >= 2
                                            ? ivl : ((m_array.dims ()(0) == 1)
                                                     ? ovl (one, ivl(0))
                                                     : ovl (ivl(0), one));

            bool is_scalar = true;

            Array<idx_vector> iv (dim_vector (1, ival.length ()));

            for (int i = 0; i < ival.length (); i++)
              {
                try
                  {
                    iv(i) = ival(i).index_vector ();
                  }
                catch (index_exception& ie)
                  {
                    // Rethrow to allow more info to be reported later.
                    ie.set_pos_if_unset (ival.length (), i+1);
                    throw;
                  }

                is_scalar = is_scalar && iv(i).is_scalar ();

                if (! is_scalar)
                  error ("subsasgn: invalid indexing for object array assignment"
                         ", the index must reference a single object in the "
                         "array.");
              }

            Array<cdef_object> a = m_array.index (iv, true);

            if (a.numel () != 1)
              error ("subsasgn: invalid indexing for object array assignment");

            cdef_object obj = a(0);

            int ignore_copies = 0;

            // If the object in 'a' is not valid, this means the index
            // was out-of-bound and we need to create a new object.

            if (! obj.ok ())
              obj = get_class ().construct_object (octave_value_list ());
            else
              // Optimize the subsasgn call to come.  There are 2 copies
              // that we can safely ignore:
              // - 1 in "array"
              // - 1 in "a"
              ignore_copies = 2;

            std::list<octave_value_list> next_idx (idx);

            next_idx.erase (next_idx.begin ());

            octave_value tmp = obj.subsasgn (type.substr (1), next_idx,
                                             rhs, ignore_copies);

            cdef_object robj = to_cdef (tmp);

            if (! robj.ok ()
                || robj.is_array ()
                || robj.get_class () != get_class ())
              error ("subsasgn: invalid assignment into array of %s objects",
                     class_name ().c_str ());

            // Small optimization, when dealing with handle
            // objects, we don't need to re-assign the result
            // of subsasgn back into the array.

            if (! robj.is (a(0)))
              {
                Array<cdef_object> rhs_a (dim_vector (1, 1),
                                          robj);

                octave_idx_type n = m_array.numel ();

                m_array.assign (iv, rhs_a);

                if (m_array.numel () > n)
                  fill_empty_values ();
              }

            m_count++;

            retval = to_ov (cdef_object (this));
          }
        break;

      default:
        error ("can't perform indexing operation on array of %s objects",
               class_name ().c_str ());
        break;
      }

    return retval;
  }

  void
  cdef_object_array::fill_empty_values (Array<cdef_object>& arr)
  {
    cdef_class cls = get_class ();

    cdef_object obj;

    int n = arr.numel ();

    for (int i = 0; i < n; i++)
      {
        if (! arr.xelem (i).ok ())
          {
            if (! obj.ok ())
              {
                obj = cls.construct_object (octave_value_list ());

                arr.xelem (i) = obj;
              }
            else
              arr.xelem (i) = obj.copy ();
          }
      }
  }

  void
  cdef_object_scalar::break_closure_cycles (const std::shared_ptr<stack_frame>& frame)
  {
    for (octave_idx_type i = 0; i < m_map.nfields (); i++)
      m_map.contents(i).break_closure_cycles (frame);
  }

  octave_value_list
  cdef_object_scalar::subsref (const std::string& type,
                               const std::list<octave_value_list>& idx,
                               int nargout, std::size_t& skip,
                               const cdef_class& context, bool auto_add)
  {
    skip = 0;

    cdef_class cls = (context.ok () ? context : get_class ());

    octave_value_list retval;

    if (! cls.ok ())
      return retval;

    switch (type[0])
      {
      case '.':
        {
          std::string name = (idx.front ())(0).string_value ();

          cdef_method meth = cls.find_method (name);

          if (meth.ok ())
            {
              int _nargout = (type.length () > 2 ? 1 : nargout);

              octave_value_list args;

              skip = 1;

              if (type.length () > 1 && type[1] == '(')
                {
                  auto it = idx.begin ();

                  args = *++it;

                  skip++;
                }

              if (meth.is_static ())
                retval = meth.execute (args, _nargout, true, "subsref");
              else
                {
                  m_count++;
                  retval = meth.execute (cdef_object (this), args, _nargout,
                                         true, "subsref");
                }
            }

          if (skip == 0)
            {
              cdef_property prop = cls.find_property (name);

              if (! prop.ok ())
                error ("subsref: unknown method or property: %s", name.c_str ());

              if (prop.is_constant ())
                retval(0) = prop.get_value (true, "subsref");
              else
                {
                  m_count++;
                  retval(0) = prop.get_value (cdef_object (this),
                                              true, "subsref");
                }

              skip = 1;
            }
          break;
        }

      case '(':
        {
          const octave_value_list& ival = idx.front ();

          m_count++;
          cdef_object this_obj (this);

          if (ival.empty ())
            {
              skip++;
              retval(0) = to_ov (this_obj);
            }
          else
            {
              Array<cdef_object> arr (dim_vector (1, 1), this_obj);

              cdef_object new_obj = cdef_object (new cdef_object_array (arr));

              new_obj.set_class (get_class ());

              retval = new_obj.subsref (type, idx, nargout, skip, cls, auto_add);
            }
        }
        break;

      default:
        error ("object cannot be indexed with '%c'", type[0]);
        break;
      }

    return retval;
  }

  octave_value
  cdef_object_scalar::subsasgn (const std::string& type,
                                const std::list<octave_value_list>& idx,
                                const octave_value& rhs)
  {
    octave_value retval;

    cdef_class cls = get_class ();

    switch (type[0])
      {
      case '.':
        {
          std::string name = (idx.front ())(0).string_value ();

          cdef_property prop = cls.find_property (name);

          if (! prop.ok ())
            error ("subsasgn: unknown property: %s", name.c_str ());

          if (prop.is_constant ())
            error ("subsasgn: cannot assign constant property: %s",
                   name.c_str ());

          m_count++;

          cdef_object obj (this);

          if (type.length () == 1)
            {
              prop.set_value (obj, rhs, true, "subsasgn");

              retval = to_ov (obj);
            }
          else
            {
              octave_value val = prop.get_value (obj, true, "subsasgn");

              std::list<octave_value_list> args (idx);

              args.erase (args.begin ());

              val = val.assign (octave_value::op_asn_eq,
                                type.substr (1), args, rhs);

              if (val.class_name () != "object"
                  || ! to_cdef (val).is_handle_object ())
                prop.set_value (obj, val, true, "subsasgn");

              retval = to_ov (obj);
            }
        }
        break;

      case '(':
        {
          m_count++;

          cdef_object this_obj (this);

          Array<cdef_object> arr (dim_vector (1, 1), this_obj);

          cdef_object new_obj = cdef_object (new cdef_object_array (arr));

          new_obj.set_class (get_class ());

          octave_value tmp = new_obj.subsasgn (type, idx, rhs);

          retval = tmp;
        }
        break;

      default:
        error ("subsasgn: object cannot be index with '%c'", type[0]);
        break;
      }

    return retval;
  }

  void
  cdef_object_scalar::mark_for_construction (const cdef_class& cls)
  {
    std::string cls_name = cls.get_name ();

    Cell supcls = cls.get ("SuperClasses").cell_value ();

    std::list<cdef_class> supcls_list = lookup_classes (supcls);

    m_ctor_list[cls] = supcls_list;
  }

  bool
  cdef_object_scalar::is_constructed_for (const cdef_class& cls) const
  {
    return (is_constructed ()
            || m_ctor_list.find (cls) == m_ctor_list.end ());
  }

  bool
  cdef_object_scalar::is_partially_constructed_for (const cdef_class& cls) const
  {
    if (is_constructed ())
      return true;

    std::map<cdef_class, std::list<cdef_class>>::const_iterator it
      = m_ctor_list.find (cls);

    if (it == m_ctor_list.end () || it->second.empty ())
      return true;

    for (const auto& cdef_cls : it->second)
      if (! is_partially_constructed_for (cdef_cls))
        return false;

    return true;
  }

  void
  cdef_object_scalar::mark_as_constructed (const cdef_class& cls)
  {
    m_ctor_list.erase (cls);
  }

  handle_cdef_object::~handle_cdef_object (void)
  {
#if DEBUG_TRACE
    std::cerr << "deleting " << get_class ().get_name ()
              << " object (handle)" << std::endl;
#endif
  }

  value_cdef_object::~value_cdef_object (void)
  {
#if DEBUG_TRACE
    std::cerr << "deleting " << get_class ().get_name ()
              << " object (value)" << std::endl;
#endif
  }
}