rustpython_vm/protocol/
object.rs

1//! Object Protocol
2//! https://docs.python.org/3/c-api/object.html
3
4use crate::{
5    builtins::{
6        pystr::AsPyStr, PyAsyncGen, PyBytes, PyDict, PyDictRef, PyGenericAlias, PyInt, PyList,
7        PyStr, PyStrRef, PyTuple, PyTupleRef, PyType, PyTypeRef,
8    },
9    bytesinner::ByteInnerNewOptions,
10    common::{hash::PyHash, str::to_ascii},
11    convert::{ToPyObject, ToPyResult},
12    dictdatatype::DictKey,
13    function::{Either, OptionalArg, PyArithmeticValue, PySetterValue},
14    object::PyPayload,
15    protocol::{PyIter, PyMapping, PySequence},
16    types::{Constructor, PyComparisonOp},
17    AsObject, Py, PyObject, PyObjectRef, PyResult, TryFromObject, VirtualMachine,
18};
19
20// RustPython doesn't need these items
21// PyObject *Py_NotImplemented
22// Py_RETURN_NOTIMPLEMENTED
23
24impl PyObjectRef {
25    // int PyObject_Print(PyObject *o, FILE *fp, int flags)
26
27    // PyObject *PyObject_GenericGetDict(PyObject *o, void *context)
28    // int PyObject_GenericSetDict(PyObject *o, PyObject *value, void *context)
29
30    #[inline(always)]
31    pub fn rich_compare(self, other: Self, opid: PyComparisonOp, vm: &VirtualMachine) -> PyResult {
32        self._cmp(&other, opid, vm).map(|res| res.to_pyobject(vm))
33    }
34
35    pub fn bytes(self, vm: &VirtualMachine) -> PyResult {
36        let bytes_type = vm.ctx.types.bytes_type;
37        match self.downcast_exact::<PyInt>(vm) {
38            Ok(int) => Err(vm.new_downcast_type_error(bytes_type, &int)),
39            Err(obj) => PyBytes::py_new(
40                bytes_type.to_owned(),
41                ByteInnerNewOptions {
42                    source: OptionalArg::Present(obj),
43                    encoding: OptionalArg::Missing,
44                    errors: OptionalArg::Missing,
45                },
46                vm,
47            ),
48        }
49    }
50
51    // const hash_not_implemented: fn(&PyObject, &VirtualMachine) ->PyResult<PyHash> = crate::types::Unhashable::slot_hash;
52
53    pub fn is_true(self, vm: &VirtualMachine) -> PyResult<bool> {
54        self.try_to_bool(vm)
55    }
56
57    pub fn not(self, vm: &VirtualMachine) -> PyResult<bool> {
58        self.is_true(vm).map(|x| !x)
59    }
60
61    pub fn length_hint(self, defaultvalue: usize, vm: &VirtualMachine) -> PyResult<usize> {
62        Ok(vm.length_hint_opt(self)?.unwrap_or(defaultvalue))
63    }
64
65    // PyObject *PyObject_Dir(PyObject *o)
66    pub fn dir(self, vm: &VirtualMachine) -> PyResult<PyList> {
67        let attributes = self.class().get_attributes();
68
69        let dict = PyDict::from_attributes(attributes, vm)?.into_ref(&vm.ctx);
70
71        if let Some(object_dict) = self.dict() {
72            vm.call_method(
73                dict.as_object(),
74                identifier!(vm, update).as_str(),
75                (object_dict,),
76            )?;
77        }
78
79        let attributes: Vec<_> = dict.into_iter().map(|(k, _v)| k).collect();
80
81        Ok(PyList::from(attributes))
82    }
83}
84
85impl PyObject {
86    /// Takes an object and returns an iterator for it.
87    /// This is typically a new iterator but if the argument is an iterator, this
88    /// returns itself.
89    pub fn get_iter(&self, vm: &VirtualMachine) -> PyResult<PyIter> {
90        // PyObject_GetIter
91        PyIter::try_from_object(vm, self.to_owned())
92    }
93
94    // PyObject *PyObject_GetAIter(PyObject *o)
95    pub fn get_aiter(&self, vm: &VirtualMachine) -> PyResult {
96        if self.payload_is::<PyAsyncGen>() {
97            vm.call_special_method(self, identifier!(vm, __aiter__), ())
98        } else {
99            Err(vm.new_type_error("wrong argument type".to_owned()))
100        }
101    }
102
103    pub fn has_attr<'a>(&self, attr_name: impl AsPyStr<'a>, vm: &VirtualMachine) -> PyResult<bool> {
104        self.get_attr(attr_name, vm).map(|o| !vm.is_none(&o))
105    }
106
107    pub fn get_attr<'a>(&self, attr_name: impl AsPyStr<'a>, vm: &VirtualMachine) -> PyResult {
108        let attr_name = attr_name.as_pystr(&vm.ctx);
109        self.get_attr_inner(attr_name, vm)
110    }
111
112    // get_attribute should be used for full attribute access (usually from user code).
113    #[cfg_attr(feature = "flame-it", flame("PyObjectRef"))]
114    #[inline]
115    pub(crate) fn get_attr_inner(&self, attr_name: &Py<PyStr>, vm: &VirtualMachine) -> PyResult {
116        vm_trace!("object.__getattribute__: {:?} {:?}", self, attr_name);
117        let getattro = self
118            .class()
119            .mro_find_map(|cls| cls.slots.getattro.load())
120            .unwrap();
121        getattro(self, attr_name, vm).inspect_err(|exc| {
122            vm.set_attribute_error_context(exc, self.to_owned(), attr_name.to_owned());
123        })
124    }
125
126    pub fn call_set_attr(
127        &self,
128        vm: &VirtualMachine,
129        attr_name: &Py<PyStr>,
130        attr_value: PySetterValue,
131    ) -> PyResult<()> {
132        let setattro = {
133            let cls = self.class();
134            cls.mro_find_map(|cls| cls.slots.setattro.load())
135                .ok_or_else(|| {
136                    let has_getattr = cls.mro_find_map(|cls| cls.slots.getattro.load()).is_some();
137                    vm.new_type_error(format!(
138                        "'{}' object has {} attributes ({} {})",
139                        cls.name(),
140                        if has_getattr { "only read-only" } else { "no" },
141                        if attr_value.is_assign() {
142                            "assign to"
143                        } else {
144                            "del"
145                        },
146                        attr_name
147                    ))
148                })?
149        };
150        setattro(self, attr_name, attr_value, vm)
151    }
152
153    pub fn set_attr<'a>(
154        &self,
155        attr_name: impl AsPyStr<'a>,
156        attr_value: impl Into<PyObjectRef>,
157        vm: &VirtualMachine,
158    ) -> PyResult<()> {
159        let attr_name = attr_name.as_pystr(&vm.ctx);
160        let attr_value = attr_value.into();
161        self.call_set_attr(vm, attr_name, PySetterValue::Assign(attr_value))
162    }
163
164    // int PyObject_GenericSetAttr(PyObject *o, PyObject *name, PyObject *value)
165    #[cfg_attr(feature = "flame-it", flame)]
166    pub fn generic_setattr(
167        &self,
168        attr_name: &Py<PyStr>,
169        value: PySetterValue,
170        vm: &VirtualMachine,
171    ) -> PyResult<()> {
172        vm_trace!("object.__setattr__({:?}, {}, {:?})", self, attr_name, value);
173        if let Some(attr) = vm
174            .ctx
175            .interned_str(attr_name)
176            .and_then(|attr_name| self.get_class_attr(attr_name))
177        {
178            let descr_set = attr.class().mro_find_map(|cls| cls.slots.descr_set.load());
179            if let Some(descriptor) = descr_set {
180                return descriptor(&attr, self.to_owned(), value, vm);
181            }
182        }
183
184        if let Some(dict) = self.dict() {
185            if let PySetterValue::Assign(value) = value {
186                dict.set_item(attr_name, value, vm)?;
187            } else {
188                dict.del_item(attr_name, vm).map_err(|e| {
189                    if e.fast_isinstance(vm.ctx.exceptions.key_error) {
190                        vm.new_attribute_error(format!(
191                            "'{}' object has no attribute '{}'",
192                            self.class().name(),
193                            attr_name.as_str(),
194                        ))
195                    } else {
196                        e
197                    }
198                })?;
199            }
200            Ok(())
201        } else {
202            Err(vm.new_attribute_error(format!(
203                "'{}' object has no attribute '{}'",
204                self.class().name(),
205                attr_name.as_str(),
206            )))
207        }
208    }
209
210    pub fn generic_getattr(&self, name: &Py<PyStr>, vm: &VirtualMachine) -> PyResult {
211        self.generic_getattr_opt(name, None, vm)?.ok_or_else(|| {
212            vm.new_attribute_error(format!(
213                "'{}' object has no attribute '{}'",
214                self.class().name(),
215                name
216            ))
217        })
218    }
219
220    /// CPython _PyObject_GenericGetAttrWithDict
221    pub fn generic_getattr_opt(
222        &self,
223        name_str: &Py<PyStr>,
224        dict: Option<PyDictRef>,
225        vm: &VirtualMachine,
226    ) -> PyResult<Option<PyObjectRef>> {
227        let name = name_str.as_str();
228        let obj_cls = self.class();
229        let cls_attr_name = vm.ctx.interned_str(name_str);
230        let cls_attr = match cls_attr_name.and_then(|name| obj_cls.get_attr(name)) {
231            Some(descr) => {
232                let descr_cls = descr.class();
233                let descr_get = descr_cls.mro_find_map(|cls| cls.slots.descr_get.load());
234                if let Some(descr_get) = descr_get {
235                    if descr_cls
236                        .mro_find_map(|cls| cls.slots.descr_set.load())
237                        .is_some()
238                    {
239                        let cls = obj_cls.to_owned().into();
240                        return descr_get(descr, Some(self.to_owned()), Some(cls), vm).map(Some);
241                    }
242                }
243                Some((descr, descr_get))
244            }
245            None => None,
246        };
247
248        let dict = dict.or_else(|| self.dict());
249
250        let attr = if let Some(dict) = dict {
251            dict.get_item_opt(name, vm)?
252        } else {
253            None
254        };
255
256        if let Some(obj_attr) = attr {
257            Ok(Some(obj_attr))
258        } else if let Some((attr, descr_get)) = cls_attr {
259            match descr_get {
260                Some(descr_get) => {
261                    let cls = obj_cls.to_owned().into();
262                    descr_get(attr, Some(self.to_owned()), Some(cls), vm).map(Some)
263                }
264                None => Ok(Some(attr)),
265            }
266        } else {
267            Ok(None)
268        }
269    }
270
271    pub fn del_attr<'a>(&self, attr_name: impl AsPyStr<'a>, vm: &VirtualMachine) -> PyResult<()> {
272        let attr_name = attr_name.as_pystr(&vm.ctx);
273        self.call_set_attr(vm, attr_name, PySetterValue::Delete)
274    }
275
276    // Perform a comparison, raising TypeError when the requested comparison
277    // operator is not supported.
278    // see: CPython PyObject_RichCompare
279    #[inline] // called by ExecutingFrame::execute_compare with const op
280    fn _cmp(
281        &self,
282        other: &Self,
283        op: PyComparisonOp,
284        vm: &VirtualMachine,
285    ) -> PyResult<Either<PyObjectRef, bool>> {
286        let swapped = op.swapped();
287        let call_cmp = |obj: &PyObject, other: &PyObject, op| {
288            let cmp = obj
289                .class()
290                .mro_find_map(|cls| cls.slots.richcompare.load())
291                .unwrap();
292            let r = match cmp(obj, other, op, vm)? {
293                Either::A(obj) => PyArithmeticValue::from_object(vm, obj).map(Either::A),
294                Either::B(arithmetic) => arithmetic.map(Either::B),
295            };
296            Ok(r)
297        };
298
299        let mut checked_reverse_op = false;
300        let is_strict_subclass = {
301            let self_class = self.class();
302            let other_class = other.class();
303            !self_class.is(other_class) && other_class.fast_issubclass(self_class)
304        };
305        if is_strict_subclass {
306            let res = vm.with_recursion("in comparison", || call_cmp(other, self, swapped))?;
307            checked_reverse_op = true;
308            if let PyArithmeticValue::Implemented(x) = res {
309                return Ok(x);
310            }
311        }
312        if let PyArithmeticValue::Implemented(x) =
313            vm.with_recursion("in comparison", || call_cmp(self, other, op))?
314        {
315            return Ok(x);
316        }
317        if !checked_reverse_op {
318            let res = vm.with_recursion("in comparison", || call_cmp(other, self, swapped))?;
319            if let PyArithmeticValue::Implemented(x) = res {
320                return Ok(x);
321            }
322        }
323        match op {
324            PyComparisonOp::Eq => Ok(Either::B(self.is(&other))),
325            PyComparisonOp::Ne => Ok(Either::B(!self.is(&other))),
326            _ => Err(vm.new_unsupported_binop_error(self, other, op.operator_token())),
327        }
328    }
329    #[inline(always)]
330    pub fn rich_compare_bool(
331        &self,
332        other: &Self,
333        opid: PyComparisonOp,
334        vm: &VirtualMachine,
335    ) -> PyResult<bool> {
336        match self._cmp(other, opid, vm)? {
337            Either::A(obj) => obj.try_to_bool(vm),
338            Either::B(other) => Ok(other),
339        }
340    }
341
342    pub fn repr(&self, vm: &VirtualMachine) -> PyResult<PyStrRef> {
343        vm.with_recursion("while getting the repr of an object", || {
344            match self.class().slots.repr.load() {
345                Some(slot) => slot(self, vm),
346                None => vm
347                    .call_special_method(self, identifier!(vm, __repr__), ())?
348                    .try_into_value(vm), // TODO: remove magic method call once __repr__ is fully ported to slot
349            }
350        })
351    }
352
353    pub fn ascii(&self, vm: &VirtualMachine) -> PyResult<ascii::AsciiString> {
354        let repr = self.repr(vm)?;
355        let ascii = to_ascii(repr.as_str());
356        Ok(ascii)
357    }
358
359    // Container of the virtual machine state:
360    pub fn str(&self, vm: &VirtualMachine) -> PyResult<PyStrRef> {
361        let obj = match self.to_owned().downcast_exact::<PyStr>(vm) {
362            Ok(s) => return Ok(s.into_pyref()),
363            Err(obj) => obj,
364        };
365        // TODO: replace to obj.class().slots.str
366        let str_method = match vm.get_special_method(&obj, identifier!(vm, __str__))? {
367            Some(str_method) => str_method,
368            None => return obj.repr(vm),
369        };
370        let s = str_method.invoke((), vm)?;
371        s.downcast::<PyStr>().map_err(|obj| {
372            vm.new_type_error(format!(
373                "__str__ returned non-string (type {})",
374                obj.class().name()
375            ))
376        })
377    }
378
379    // Equivalent to check_class. Masks Attribute errors (into TypeErrors) and lets everything
380    // else go through.
381    fn check_cls<F>(&self, cls: &PyObject, vm: &VirtualMachine, msg: F) -> PyResult
382    where
383        F: Fn() -> String,
384    {
385        cls.get_attr(identifier!(vm, __bases__), vm).map_err(|e| {
386            // Only mask AttributeErrors.
387            if e.class().is(vm.ctx.exceptions.attribute_error) {
388                vm.new_type_error(msg())
389            } else {
390                e
391            }
392        })
393    }
394
395    fn abstract_issubclass(&self, cls: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
396        let mut derived = self;
397        let mut first_item: PyObjectRef;
398        loop {
399            if derived.is(cls) {
400                return Ok(true);
401            }
402
403            let bases = derived.get_attr(identifier!(vm, __bases__), vm)?;
404            let tuple = PyTupleRef::try_from_object(vm, bases)?;
405
406            let n = tuple.len();
407            match n {
408                0 => {
409                    return Ok(false);
410                }
411                1 => {
412                    first_item = tuple.fast_getitem(0).clone();
413                    derived = &first_item;
414                    continue;
415                }
416                _ => {
417                    if let Some(i) = (0..n).next() {
418                        let check = vm.with_recursion("in abstract_issubclass", || {
419                            tuple.fast_getitem(i).abstract_issubclass(cls, vm)
420                        })?;
421                        if check {
422                            return Ok(true);
423                        }
424                    }
425                }
426            }
427
428            return Ok(false);
429        }
430    }
431
432    fn recursive_issubclass(&self, cls: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
433        if let (Ok(obj), Ok(cls)) = (self.try_to_ref::<PyType>(vm), cls.try_to_ref::<PyType>(vm)) {
434            Ok(obj.fast_issubclass(cls))
435        } else {
436            self.check_cls(self, vm, || {
437                format!("issubclass() arg 1 must be a class, not {}", self.class())
438            })
439            .and(self.check_cls(cls, vm, || {
440                format!(
441                    "issubclass() arg 2 must be a class, a tuple of classes, or a union, not {}",
442                    cls.class()
443                )
444            }))
445            .and(self.abstract_issubclass(cls, vm))
446        }
447    }
448
449    /// Determines if `self` is a subclass of `cls`, either directly, indirectly or virtually
450    /// via the __subclasscheck__ magic method.
451    pub fn is_subclass(&self, cls: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
452        if cls.class().is(vm.ctx.types.type_type) {
453            if self.is(cls) {
454                return Ok(true);
455            }
456            return self.recursive_issubclass(cls, vm);
457        }
458
459        if let Ok(tuple) = cls.try_to_value::<&Py<PyTuple>>(vm) {
460            for typ in tuple {
461                if vm.with_recursion("in __subclasscheck__", || self.is_subclass(typ, vm))? {
462                    return Ok(true);
463                }
464            }
465            return Ok(false);
466        }
467
468        if let Some(meth) = vm.get_special_method(cls, identifier!(vm, __subclasscheck__))? {
469            let ret = vm.with_recursion("in __subclasscheck__", || {
470                meth.invoke((self.to_owned(),), vm)
471            })?;
472            return ret.try_to_bool(vm);
473        }
474
475        self.recursive_issubclass(cls, vm)
476    }
477
478    fn abstract_isinstance(&self, cls: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
479        let r = if let Ok(typ) = cls.try_to_ref::<PyType>(vm) {
480            if self.class().fast_issubclass(typ) {
481                true
482            } else if let Ok(icls) =
483                PyTypeRef::try_from_object(vm, self.get_attr(identifier!(vm, __class__), vm)?)
484            {
485                if icls.is(self.class()) {
486                    false
487                } else {
488                    icls.fast_issubclass(typ)
489                }
490            } else {
491                false
492            }
493        } else {
494            self.check_cls(cls, vm, || {
495                format!(
496                    "isinstance() arg 2 must be a type or tuple of types, not {}",
497                    cls.class()
498                )
499            })?;
500            let icls: PyObjectRef = self.get_attr(identifier!(vm, __class__), vm)?;
501            if vm.is_none(&icls) {
502                false
503            } else {
504                icls.abstract_issubclass(cls, vm)?
505            }
506        };
507        Ok(r)
508    }
509
510    /// Determines if `self` is an instance of `cls`, either directly, indirectly or virtually via
511    /// the __instancecheck__ magic method.
512    pub fn is_instance(&self, cls: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
513        // cpython first does an exact check on the type, although documentation doesn't state that
514        // https://github.com/python/cpython/blob/a24107b04c1277e3c1105f98aff5bfa3a98b33a0/Objects/abstract.c#L2408
515        if self.class().is(cls) {
516            return Ok(true);
517        }
518
519        if cls.class().is(vm.ctx.types.type_type) {
520            return self.abstract_isinstance(cls, vm);
521        }
522
523        if let Ok(tuple) = cls.try_to_ref::<PyTuple>(vm) {
524            for typ in tuple {
525                if vm.with_recursion("in __instancecheck__", || self.is_instance(typ, vm))? {
526                    return Ok(true);
527                }
528            }
529            return Ok(false);
530        }
531
532        if let Some(meth) = vm.get_special_method(cls, identifier!(vm, __instancecheck__))? {
533            let ret = vm.with_recursion("in __instancecheck__", || {
534                meth.invoke((self.to_owned(),), vm)
535            })?;
536            return ret.try_to_bool(vm);
537        }
538
539        self.abstract_isinstance(cls, vm)
540    }
541
542    pub fn hash(&self, vm: &VirtualMachine) -> PyResult<PyHash> {
543        let hash = self.get_class_attr(identifier!(vm, __hash__)).unwrap();
544        if vm.is_none(&hash) {
545            return Err(vm.new_exception_msg(
546                vm.ctx.exceptions.type_error.to_owned(),
547                format!("unhashable type: '{}'", self.class().name()),
548            ));
549        }
550
551        let hash = self
552            .class()
553            .mro_find_map(|cls| cls.slots.hash.load())
554            .unwrap();
555
556        hash(self, vm)
557    }
558
559    // type protocol
560    // PyObject *PyObject_Type(PyObject *o)
561    pub fn obj_type(&self) -> PyObjectRef {
562        self.class().to_owned().into()
563    }
564
565    // int PyObject_TypeCheck(PyObject *o, PyTypeObject *type)
566    pub fn type_check(&self, typ: &Py<PyType>) -> bool {
567        self.fast_isinstance(typ)
568    }
569
570    pub fn length_opt(&self, vm: &VirtualMachine) -> Option<PyResult<usize>> {
571        self.to_sequence()
572            .length_opt(vm)
573            .or_else(|| self.to_mapping().length_opt(vm))
574    }
575
576    pub fn length(&self, vm: &VirtualMachine) -> PyResult<usize> {
577        self.length_opt(vm).ok_or_else(|| {
578            vm.new_type_error(format!(
579                "object of type '{}' has no len()",
580                self.class().name()
581            ))
582        })?
583    }
584
585    pub fn get_item<K: DictKey + ?Sized>(&self, needle: &K, vm: &VirtualMachine) -> PyResult {
586        if let Some(dict) = self.downcast_ref_if_exact::<PyDict>(vm) {
587            return dict.get_item(needle, vm);
588        }
589
590        let needle = needle.to_pyobject(vm);
591
592        if let Ok(mapping) = PyMapping::try_protocol(self, vm) {
593            mapping.subscript(&needle, vm)
594        } else if let Ok(seq) = PySequence::try_protocol(self, vm) {
595            let i = needle.key_as_isize(vm)?;
596            seq.get_item(i, vm)
597        } else {
598            if self.class().fast_issubclass(vm.ctx.types.type_type) {
599                if self.is(vm.ctx.types.type_type) {
600                    return PyGenericAlias::new(self.class().to_owned(), needle, vm)
601                        .to_pyresult(vm);
602                }
603
604                if let Some(class_getitem) =
605                    vm.get_attribute_opt(self.to_owned(), identifier!(vm, __class_getitem__))?
606                {
607                    return class_getitem.call((needle,), vm);
608                }
609            }
610            Err(vm.new_type_error(format!("'{}' object is not subscriptable", self.class())))
611        }
612    }
613
614    pub fn set_item<K: DictKey + ?Sized>(
615        &self,
616        needle: &K,
617        value: PyObjectRef,
618        vm: &VirtualMachine,
619    ) -> PyResult<()> {
620        if let Some(dict) = self.downcast_ref_if_exact::<PyDict>(vm) {
621            return dict.set_item(needle, value, vm);
622        }
623
624        let mapping = self.to_mapping();
625        if let Some(f) = mapping.methods.ass_subscript.load() {
626            let needle = needle.to_pyobject(vm);
627            return f(mapping, &needle, Some(value), vm);
628        }
629
630        let seq = self.to_sequence();
631        if let Some(f) = seq.methods.ass_item.load() {
632            let i = needle.key_as_isize(vm)?;
633            return f(seq, i, Some(value), vm);
634        }
635
636        Err(vm.new_type_error(format!(
637            "'{}' does not support item assignment",
638            self.class()
639        )))
640    }
641
642    pub fn del_item<K: DictKey + ?Sized>(&self, needle: &K, vm: &VirtualMachine) -> PyResult<()> {
643        if let Some(dict) = self.downcast_ref_if_exact::<PyDict>(vm) {
644            return dict.del_item(needle, vm);
645        }
646
647        let mapping = self.to_mapping();
648        if let Some(f) = mapping.methods.ass_subscript.load() {
649            let needle = needle.to_pyobject(vm);
650            return f(mapping, &needle, None, vm);
651        }
652        let seq = self.to_sequence();
653        if let Some(f) = seq.methods.ass_item.load() {
654            let i = needle.key_as_isize(vm)?;
655            return f(seq, i, None, vm);
656        }
657
658        Err(vm.new_type_error(format!("'{}' does not support item deletion", self.class())))
659    }
660}
Morty Proxy This is a proxified and sanitized view of the page, visit original site.