2020-04-10 08:11:40 +03:00
|
|
|
#ifndef INTERNAL_CLASS_H /*-*-C-*-vi:se ft=c:*/
|
|
|
|
#define INTERNAL_CLASS_H
|
|
|
|
/**
|
2020-04-08 07:28:13 +03:00
|
|
|
* @author Ruby developers <ruby-core@ruby-lang.org>
|
2019-11-29 09:18:34 +03:00
|
|
|
* @copyright This file is a part of the programming language Ruby.
|
|
|
|
* Permission is hereby granted, to either redistribute and/or
|
|
|
|
* modify this file, provided that the conditions mentioned in the
|
|
|
|
* file COPYING are met. Consult the file for details.
|
2020-04-08 07:28:13 +03:00
|
|
|
* @brief Internal header for Class.
|
2019-11-29 09:18:34 +03:00
|
|
|
*/
|
2023-02-15 12:42:52 +03:00
|
|
|
#include "id.h"
|
2019-12-03 07:49:14 +03:00
|
|
|
#include "id_table.h" /* for struct rb_id_table */
|
|
|
|
#include "internal/serial.h" /* for rb_serial_t */
|
2023-01-11 17:19:11 +03:00
|
|
|
#include "internal/static_assert.h"
|
2023-02-15 12:42:52 +03:00
|
|
|
#include "internal/variable.h" /* for rb_class_ivar_set */
|
2020-05-08 12:31:09 +03:00
|
|
|
#include "ruby/internal/stdbool.h" /* for bool */
|
2019-12-03 07:49:14 +03:00
|
|
|
#include "ruby/intern.h" /* for rb_alloc_func_t */
|
|
|
|
#include "ruby/ruby.h" /* for struct RBasic */
|
2022-10-03 18:14:32 +03:00
|
|
|
#include "shape.h"
|
2023-06-02 21:25:19 +03:00
|
|
|
#include "ruby_assert.h"
|
|
|
|
#include "vm_core.h"
|
2023-10-26 02:52:37 +03:00
|
|
|
#include "vm_sync.h"
|
2023-06-02 21:25:19 +03:00
|
|
|
#include "method.h" /* for rb_cref_t */
|
2019-12-03 07:49:14 +03:00
|
|
|
|
2019-12-04 11:16:30 +03:00
|
|
|
#ifdef RCLASS_SUPER
|
|
|
|
# undef RCLASS_SUPER
|
|
|
|
#endif
|
2019-11-29 09:18:34 +03:00
|
|
|
|
|
|
|
struct rb_subclass_entry {
|
|
|
|
VALUE klass;
|
2019-12-03 07:49:14 +03:00
|
|
|
struct rb_subclass_entry *next;
|
2021-11-16 00:09:10 +03:00
|
|
|
struct rb_subclass_entry *prev;
|
2019-11-29 09:18:34 +03:00
|
|
|
};
|
2023-01-11 17:19:11 +03:00
|
|
|
typedef struct rb_subclass_entry rb_subclass_entry_t;
|
2019-11-29 09:18:34 +03:00
|
|
|
|
2021-06-01 20:34:06 +03:00
|
|
|
struct rb_cvar_class_tbl_entry {
|
|
|
|
uint32_t index;
|
|
|
|
rb_serial_t global_cvar_state;
|
2023-06-02 21:25:19 +03:00
|
|
|
const rb_cref_t * cref;
|
2021-06-01 20:34:06 +03:00
|
|
|
VALUE class_value;
|
|
|
|
};
|
|
|
|
|
2019-11-29 09:18:34 +03:00
|
|
|
struct rb_classext_struct {
|
2022-11-01 00:05:37 +03:00
|
|
|
VALUE *iv_ptr;
|
2019-11-29 09:18:34 +03:00
|
|
|
struct rb_id_table *const_tbl;
|
|
|
|
struct rb_id_table *callable_m_tbl;
|
2024-02-12 08:43:38 +03:00
|
|
|
struct rb_id_table *cc_tbl; /* ID -> [[ci1, cc1], [ci2, cc2] ...] */
|
2021-06-01 20:34:06 +03:00
|
|
|
struct rb_id_table *cvc_tbl;
|
2022-01-26 06:16:57 +03:00
|
|
|
size_t superclass_depth;
|
|
|
|
VALUE *superclasses;
|
2019-12-03 07:49:14 +03:00
|
|
|
struct rb_subclass_entry *subclasses;
|
2021-11-16 00:09:10 +03:00
|
|
|
struct rb_subclass_entry *subclass_entry;
|
2019-11-29 09:18:34 +03:00
|
|
|
/**
|
|
|
|
* In the case that this is an `ICLASS`, `module_subclasses` points to the link
|
|
|
|
* in the module's `subclasses` list that indicates that the klass has been
|
|
|
|
* included. Hopefully that makes sense.
|
|
|
|
*/
|
2021-11-16 00:09:10 +03:00
|
|
|
struct rb_subclass_entry *module_subclass_entry;
|
2019-11-29 09:18:34 +03:00
|
|
|
const VALUE origin_;
|
|
|
|
const VALUE refined_class;
|
2023-02-15 17:18:01 +03:00
|
|
|
union {
|
|
|
|
struct {
|
|
|
|
rb_alloc_func_t allocator;
|
|
|
|
} class;
|
|
|
|
struct {
|
|
|
|
VALUE attached_object;
|
|
|
|
} singleton_class;
|
|
|
|
} as;
|
2019-11-29 09:18:34 +03:00
|
|
|
const VALUE includer;
|
2023-04-11 17:14:45 +03:00
|
|
|
attr_index_t max_iv_count;
|
2023-01-11 17:19:11 +03:00
|
|
|
unsigned char variation_count;
|
2023-04-14 23:25:06 +03:00
|
|
|
bool permanent_classpath : 1;
|
|
|
|
bool cloned : 1;
|
2023-01-11 17:23:03 +03:00
|
|
|
VALUE classpath;
|
2019-11-29 09:18:34 +03:00
|
|
|
};
|
2023-01-11 17:19:11 +03:00
|
|
|
typedef struct rb_classext_struct rb_classext_t;
|
|
|
|
|
|
|
|
STATIC_ASSERT(shape_max_variations, SHAPE_MAX_VARIATIONS < (1 << (sizeof(((rb_classext_t *)0)->variation_count) * CHAR_BIT)));
|
2019-11-29 09:18:34 +03:00
|
|
|
|
|
|
|
struct RClass {
|
|
|
|
struct RBasic basic;
|
|
|
|
VALUE super;
|
2022-10-21 19:58:51 +03:00
|
|
|
struct rb_id_table *m_tbl;
|
2019-11-29 09:18:34 +03:00
|
|
|
};
|
|
|
|
|
Rename size_pool -> heap
Now that we've inlined the eden_heap into the size_pool, we should
rename the size_pool to heap. So that Ruby contains multiple heaps, with
different sized objects.
The term heap as a collection of memory pages is more in memory
management nomenclature, whereas size_pool was a name chosen out of
necessity during the development of the Variable Width Allocation
features of Ruby.
The concept of size pools was introduced in order to facilitate
different sized objects (other than the default 40 bytes). They wrapped
the eden heap and the tomb heap, and some related state, and provided a
reasonably simple way of duplicating all related concerns, to provide
multiple pools that all shared the same structure but held different
objects.
Since then various changes have happend in Ruby's memory layout:
* The concept of tomb heaps has been replaced by a global free pages list,
with each page having it's slot size reconfigured at the point when it
is resurrected
* the eden heap has been inlined into the size pool itself, so that now
the size pool directly controls the free_pages list, the sweeping
page, the compaction cursor and the other state that was previously
being managed by the eden heap.
Now that there is no need for a heap wrapper, we should refer to the
collection of pages containing Ruby objects as a heap again rather than
a size pool
2024-10-03 15:53:49 +03:00
|
|
|
// Assert that classes can be embedded in heaps[2] (which has 160B slot size)
|
2023-01-11 17:23:03 +03:00
|
|
|
STATIC_ASSERT(sizeof_rb_classext_t, sizeof(struct RClass) + sizeof(rb_classext_t) <= 4 * RVALUE_SIZE);
|
2019-11-29 09:18:34 +03:00
|
|
|
|
2023-10-15 08:53:14 +03:00
|
|
|
struct RClass_and_rb_classext_t {
|
|
|
|
struct RClass rclass;
|
|
|
|
rb_classext_t classext;
|
|
|
|
};
|
|
|
|
|
|
|
|
#define RCLASS_EXT(c) (&((struct RClass_and_rb_classext_t*)(c))->classext)
|
2019-11-29 09:18:34 +03:00
|
|
|
#define RCLASS_CONST_TBL(c) (RCLASS_EXT(c)->const_tbl)
|
2022-10-21 19:58:51 +03:00
|
|
|
#define RCLASS_M_TBL(c) (RCLASS(c)->m_tbl)
|
2022-11-01 00:05:37 +03:00
|
|
|
#define RCLASS_IVPTR(c) (RCLASS_EXT(c)->iv_ptr)
|
2019-11-29 09:18:34 +03:00
|
|
|
#define RCLASS_CALLABLE_M_TBL(c) (RCLASS_EXT(c)->callable_m_tbl)
|
2020-01-08 10:14:01 +03:00
|
|
|
#define RCLASS_CC_TBL(c) (RCLASS_EXT(c)->cc_tbl)
|
2021-06-01 20:34:06 +03:00
|
|
|
#define RCLASS_CVC_TBL(c) (RCLASS_EXT(c)->cvc_tbl)
|
2019-11-29 09:18:34 +03:00
|
|
|
#define RCLASS_ORIGIN(c) (RCLASS_EXT(c)->origin_)
|
|
|
|
#define RCLASS_REFINED_CLASS(c) (RCLASS_EXT(c)->refined_class)
|
|
|
|
#define RCLASS_INCLUDER(c) (RCLASS_EXT(c)->includer)
|
2021-11-16 00:09:10 +03:00
|
|
|
#define RCLASS_SUBCLASS_ENTRY(c) (RCLASS_EXT(c)->subclass_entry)
|
|
|
|
#define RCLASS_MODULE_SUBCLASS_ENTRY(c) (RCLASS_EXT(c)->module_subclass_entry)
|
2021-01-26 19:49:57 +03:00
|
|
|
#define RCLASS_SUBCLASSES(c) (RCLASS_EXT(c)->subclasses)
|
2022-01-26 06:16:57 +03:00
|
|
|
#define RCLASS_SUPERCLASS_DEPTH(c) (RCLASS_EXT(c)->superclass_depth)
|
|
|
|
#define RCLASS_SUPERCLASSES(c) (RCLASS_EXT(c)->superclasses)
|
2023-02-15 17:18:01 +03:00
|
|
|
#define RCLASS_ATTACHED_OBJECT(c) (RCLASS_EXT(c)->as.singleton_class.attached_object)
|
2019-11-29 09:18:34 +03:00
|
|
|
|
2024-03-03 12:46:46 +03:00
|
|
|
#define RCLASS_IS_ROOT FL_USER0
|
2022-05-26 22:36:41 +03:00
|
|
|
#define RICLASS_IS_ORIGIN FL_USER0
|
|
|
|
#define RCLASS_SUPERCLASSES_INCLUDE_SELF FL_USER2
|
|
|
|
#define RICLASS_ORIGIN_SHARED_MTBL FL_USER3
|
2019-11-29 09:18:34 +03:00
|
|
|
|
2023-10-26 02:52:37 +03:00
|
|
|
static inline st_table *
|
|
|
|
RCLASS_IV_HASH(VALUE obj)
|
|
|
|
{
|
|
|
|
RUBY_ASSERT(RB_TYPE_P(obj, RUBY_T_CLASS) || RB_TYPE_P(obj, RUBY_T_MODULE));
|
|
|
|
RUBY_ASSERT(rb_shape_obj_too_complex(obj));
|
|
|
|
return (st_table *)RCLASS_IVPTR(obj);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void
|
|
|
|
RCLASS_SET_IV_HASH(VALUE obj, const st_table *tbl)
|
|
|
|
{
|
|
|
|
RUBY_ASSERT(RB_TYPE_P(obj, RUBY_T_CLASS) || RB_TYPE_P(obj, RUBY_T_MODULE));
|
|
|
|
RUBY_ASSERT(rb_shape_obj_too_complex(obj));
|
|
|
|
RCLASS_IVPTR(obj) = (VALUE *)tbl;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline uint32_t
|
|
|
|
RCLASS_IV_COUNT(VALUE obj)
|
|
|
|
{
|
|
|
|
RUBY_ASSERT(RB_TYPE_P(obj, RUBY_T_CLASS) || RB_TYPE_P(obj, RUBY_T_MODULE));
|
|
|
|
if (rb_shape_obj_too_complex(obj)) {
|
|
|
|
uint32_t count;
|
|
|
|
|
|
|
|
// "Too complex" classes could have their IV hash mutated in
|
|
|
|
// parallel, so lets lock around getting the hash size.
|
|
|
|
RB_VM_LOCK_ENTER();
|
|
|
|
{
|
|
|
|
count = (uint32_t)rb_st_table_size(RCLASS_IV_HASH(obj));
|
|
|
|
}
|
|
|
|
RB_VM_LOCK_LEAVE();
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return rb_shape_get_shape_by_id(RCLASS_SHAPE_ID(obj))->next_iv_index;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-19 22:38:57 +03:00
|
|
|
static inline void
|
|
|
|
RCLASS_SET_M_TBL(VALUE klass, struct rb_id_table *table)
|
|
|
|
{
|
|
|
|
RUBY_ASSERT(!RB_OBJ_PROMOTED(klass));
|
|
|
|
RCLASS_M_TBL(klass) = table;
|
|
|
|
}
|
|
|
|
|
2019-12-03 07:49:14 +03:00
|
|
|
/* class.c */
|
|
|
|
void rb_class_subclass_add(VALUE super, VALUE klass);
|
|
|
|
void rb_class_remove_from_super_subclasses(VALUE);
|
2022-01-26 06:16:57 +03:00
|
|
|
void rb_class_update_superclasses(VALUE);
|
2022-02-27 03:05:06 +03:00
|
|
|
size_t rb_class_superclasses_memsize(VALUE);
|
2021-11-16 00:09:10 +03:00
|
|
|
void rb_class_remove_subclass_head(VALUE);
|
2019-12-03 07:49:14 +03:00
|
|
|
int rb_singleton_class_internal_p(VALUE sklass);
|
|
|
|
VALUE rb_class_boot(VALUE);
|
2020-07-26 05:52:19 +03:00
|
|
|
VALUE rb_class_s_alloc(VALUE klass);
|
|
|
|
VALUE rb_module_s_alloc(VALUE klass);
|
2021-09-23 18:55:11 +03:00
|
|
|
void rb_module_set_initialized(VALUE module);
|
2021-09-19 16:39:18 +03:00
|
|
|
void rb_module_check_initializable(VALUE module);
|
2019-12-03 07:49:14 +03:00
|
|
|
VALUE rb_make_metaclass(VALUE, VALUE);
|
|
|
|
VALUE rb_include_class_new(VALUE, VALUE);
|
|
|
|
void rb_class_foreach_subclass(VALUE klass, void (*f)(VALUE, VALUE), VALUE);
|
|
|
|
void rb_class_detach_subclasses(VALUE);
|
|
|
|
void rb_class_detach_module_subclasses(VALUE);
|
|
|
|
void rb_class_remove_from_module_subclasses(VALUE);
|
2024-02-29 15:17:22 +03:00
|
|
|
VALUE rb_define_class_id_under_no_pin(VALUE outer, ID id, VALUE super);
|
2019-12-03 07:49:14 +03:00
|
|
|
VALUE rb_obj_methods(int argc, const VALUE *argv, VALUE obj);
|
|
|
|
VALUE rb_obj_protected_methods(int argc, const VALUE *argv, VALUE obj);
|
|
|
|
VALUE rb_obj_private_methods(int argc, const VALUE *argv, VALUE obj);
|
|
|
|
VALUE rb_obj_public_methods(int argc, const VALUE *argv, VALUE obj);
|
2022-06-06 19:57:32 +03:00
|
|
|
VALUE rb_class_undefined_instance_methods(VALUE mod);
|
2019-12-03 07:49:14 +03:00
|
|
|
VALUE rb_special_singleton_class(VALUE);
|
|
|
|
VALUE rb_singleton_class_clone_and_attach(VALUE obj, VALUE attach);
|
|
|
|
VALUE rb_singleton_class_get(VALUE obj);
|
|
|
|
void rb_undef_methods_from(VALUE klass, VALUE super);
|
Ensure origins for all included, prepended, and refined modules
This fixes various issues when a module is included in or prepended
to a module or class, and then refined, or refined and then included
or prepended to a module or class.
Implement by renaming ensure_origin to rb_ensure_origin, making it
non-static, and calling it when refining a module.
Fix Module#initialize_copy to handle origins correctly. Previously,
Module#initialize_copy did not handle origins correctly. For example,
this code:
```ruby
module B; end
class A
def b; 2 end
prepend B
end
a = A.dup.new
class A
def b; 1 end
end
p a.b
```
Printed 1 instead of 2. This is because the super chain for
a.singleton_class was:
```
a.singleton_class
A.dup
B(iclass)
B(iclass origin)
A(origin) # not A.dup(origin)
```
The B iclasses would not be modified, so the includer entry would be
still be set to A and not A.dup.
This modifies things so that if the class/module has an origin,
all iclasses between the class/module and the origin are duplicated
and have the correct includer entry set, and the correct origin
is created.
This requires other changes to make sure all tests still pass:
* rb_undef_methods_from doesn't automatically handle classes with
origins, so pass it the origin for Comparable when undefing
methods in Complex. This fixed a failure in the Complex tests.
* When adding a method, the method cache was not cleared
correctly if klass has an origin. Clear the method cache for
the klass before switching to the origin of klass. This fixed
failures in the autoload tests related to overridding require,
without breaking the optimization tests. Also clear the method
cache for both the module and origin when removing a method.
* Module#include? is fixed to skip origin iclasses.
* Refinements are fixed to use the origin class of the module that
has an origin.
* RCLASS_REFINED_BY_ANY is removed as it was only used in a single
place and is no longer needed.
* Marshal#dump is fixed to skip iclass origins.
* rb_method_entry_make is fixed to handled overridden optimized
methods for modules that have origins.
Fixes [Bug #16852]
2020-05-24 06:16:27 +03:00
|
|
|
|
2019-12-03 07:49:14 +03:00
|
|
|
static inline void RCLASS_SET_ORIGIN(VALUE klass, VALUE origin);
|
2020-04-05 22:10:42 +03:00
|
|
|
static inline void RICLASS_SET_ORIGIN_SHARED_MTBL(VALUE iclass);
|
2019-12-03 07:49:14 +03:00
|
|
|
static inline VALUE RCLASS_SUPER(VALUE klass);
|
|
|
|
static inline VALUE RCLASS_SET_SUPER(VALUE klass, VALUE super);
|
|
|
|
static inline void RCLASS_SET_INCLUDER(VALUE iclass, VALUE klass);
|
|
|
|
|
|
|
|
VALUE rb_class_inherited(VALUE, VALUE);
|
|
|
|
VALUE rb_keyword_error_new(const char *, VALUE);
|
|
|
|
|
2024-03-06 19:04:22 +03:00
|
|
|
static inline bool
|
|
|
|
RCLASS_SINGLETON_P(VALUE klass)
|
|
|
|
{
|
|
|
|
return RB_TYPE_P(klass, T_CLASS) && FL_TEST_RAW(klass, FL_SINGLETON);
|
|
|
|
}
|
|
|
|
|
2023-02-15 17:18:01 +03:00
|
|
|
static inline rb_alloc_func_t
|
|
|
|
RCLASS_ALLOCATOR(VALUE klass)
|
|
|
|
{
|
2024-03-06 19:04:22 +03:00
|
|
|
if (RCLASS_SINGLETON_P(klass)) {
|
2023-06-22 21:15:55 +03:00
|
|
|
return 0;
|
2023-02-15 17:18:01 +03:00
|
|
|
}
|
|
|
|
return RCLASS_EXT(klass)->as.class.allocator;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void
|
|
|
|
RCLASS_SET_ALLOCATOR(VALUE klass, rb_alloc_func_t allocator)
|
|
|
|
{
|
2024-03-06 19:04:22 +03:00
|
|
|
assert(!RCLASS_SINGLETON_P(klass));
|
2023-02-15 17:18:01 +03:00
|
|
|
RCLASS_EXT(klass)->as.class.allocator = allocator;
|
|
|
|
}
|
|
|
|
|
2019-11-29 09:18:34 +03:00
|
|
|
static inline void
|
|
|
|
RCLASS_SET_ORIGIN(VALUE klass, VALUE origin)
|
|
|
|
{
|
|
|
|
RB_OBJ_WRITE(klass, &RCLASS_ORIGIN(klass), origin);
|
|
|
|
if (klass != origin) FL_SET(origin, RICLASS_IS_ORIGIN);
|
|
|
|
}
|
|
|
|
|
2020-04-05 22:10:42 +03:00
|
|
|
static inline void
|
|
|
|
RICLASS_SET_ORIGIN_SHARED_MTBL(VALUE iclass)
|
|
|
|
{
|
|
|
|
FL_SET(iclass, RICLASS_ORIGIN_SHARED_MTBL);
|
|
|
|
}
|
|
|
|
|
2020-08-11 01:19:17 +03:00
|
|
|
static inline bool
|
|
|
|
RICLASS_OWNS_M_TBL_P(VALUE iclass)
|
|
|
|
{
|
|
|
|
return FL_TEST_RAW(iclass, RICLASS_IS_ORIGIN | RICLASS_ORIGIN_SHARED_MTBL) == RICLASS_IS_ORIGIN;
|
|
|
|
}
|
|
|
|
|
2019-11-29 09:18:34 +03:00
|
|
|
static inline void
|
|
|
|
RCLASS_SET_INCLUDER(VALUE iclass, VALUE klass)
|
|
|
|
{
|
|
|
|
RB_OBJ_WRITE(iclass, &RCLASS_INCLUDER(iclass), klass);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline VALUE
|
|
|
|
RCLASS_SUPER(VALUE klass)
|
|
|
|
{
|
|
|
|
return RCLASS(klass)->super;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline VALUE
|
|
|
|
RCLASS_SET_SUPER(VALUE klass, VALUE super)
|
|
|
|
{
|
|
|
|
if (super) {
|
|
|
|
rb_class_remove_from_super_subclasses(klass);
|
|
|
|
rb_class_subclass_add(super, klass);
|
|
|
|
}
|
|
|
|
RB_OBJ_WRITE(klass, &RCLASS(klass)->super, super);
|
2022-01-26 06:16:57 +03:00
|
|
|
rb_class_update_superclasses(klass);
|
2019-11-29 09:18:34 +03:00
|
|
|
return super;
|
|
|
|
}
|
|
|
|
|
2023-01-11 17:23:03 +03:00
|
|
|
static inline void
|
|
|
|
RCLASS_SET_CLASSPATH(VALUE klass, VALUE classpath, bool permanent)
|
|
|
|
{
|
2023-09-19 07:56:06 +03:00
|
|
|
assert(BUILTIN_TYPE(klass) == T_CLASS || BUILTIN_TYPE(klass) == T_MODULE);
|
2023-01-11 17:23:03 +03:00
|
|
|
assert(classpath == 0 || BUILTIN_TYPE(classpath) == T_STRING);
|
|
|
|
|
|
|
|
RB_OBJ_WRITE(klass, &(RCLASS_EXT(klass)->classpath), classpath);
|
|
|
|
RCLASS_EXT(klass)->permanent_classpath = permanent;
|
|
|
|
}
|
|
|
|
|
2023-02-15 12:42:52 +03:00
|
|
|
static inline VALUE
|
|
|
|
RCLASS_SET_ATTACHED_OBJECT(VALUE klass, VALUE attached_object)
|
|
|
|
{
|
2024-03-06 19:04:22 +03:00
|
|
|
assert(RCLASS_SINGLETON_P(klass));
|
2023-02-15 12:42:52 +03:00
|
|
|
|
2023-02-15 17:18:01 +03:00
|
|
|
RB_OBJ_WRITE(klass, &RCLASS_EXT(klass)->as.singleton_class.attached_object, attached_object);
|
2023-02-15 12:42:52 +03:00
|
|
|
return attached_object;
|
|
|
|
}
|
|
|
|
|
2019-11-29 09:18:34 +03:00
|
|
|
#endif /* INTERNAL_CLASS_H */
|