]> code.delx.au - gnu-emacs/blobdiff - lisp/emacs-lisp/eieio-base.el
Merge from origin/emacs-24
[gnu-emacs] / lisp / emacs-lisp / eieio-base.el
index 139f5e6a4ce8365438db88d5e70490f5775563fe..a1c2cb54a9e7c0a66a25beed68d3cd26a7b0c743 100644 (file)
@@ -1,10 +1,9 @@
-;;; eieio-base.el --- Base classes for EIEIO.
+;;; eieio-base.el --- Base classes for EIEIO.  -*- lexical-binding:t -*-
 
 
-;;; Copyright (C) 2000-2002, 2004-2005, 2007-2011
-;;; Free Software Foundation, Inc.
+;;; Copyright (C) 2000-2002, 2004-2005, 2007-2014 Free Software
+;;; Foundation, Inc.
 
 ;; Author: Eric M. Ludlam  <zappo@gnu.org>
 
 ;; Author: Eric M. Ludlam  <zappo@gnu.org>
-;; Version: 0.2
 ;; Keywords: OO, lisp
 ;; Package: eieio
 
 ;; Keywords: OO, lisp
 ;; Package: eieio
 
@@ -32,6 +31,7 @@
 ;;; Code:
 
 (require 'eieio)
 ;;; Code:
 
 (require 'eieio)
+(eval-when-compile (require 'cl-lib))
 
 ;;; eieio-instance-inheritor
 ;;
 
 ;;; eieio-instance-inheritor
 ;;
@@ -52,9 +52,10 @@ a parent instance.  When a slot in the child is referenced, and has
 not been set, use values from the parent."
   :abstract t)
 
 not been set, use values from the parent."
   :abstract t)
 
-(defmethod slot-unbound ((object eieio-instance-inheritor) class slot-name fn)
+(defmethod slot-unbound ((object eieio-instance-inheritor)
+                         _class slot-name _fn)
   "If a slot OBJECT in this CLASS is unbound, try to inherit, or throw a signal.
   "If a slot OBJECT in this CLASS is unbound, try to inherit, or throw a signal.
-SLOT-NAME is the offending slot.  FN is the function signalling the error."
+SLOT-NAME is the offending slot.  FN is the function signaling the error."
   (if (slot-boundp object 'parent-instance)
       ;; It may not look like it, but this line recurses back into this
       ;; method if the parent instance's slot is unbound.
   (if (slot-boundp object 'parent-instance)
       ;; It may not look like it, but this line recurses back into this
       ;; method if the parent instance's slot is unbound.
@@ -66,19 +67,19 @@ SLOT-NAME is the offending slot.  FN is the function signalling the error."
   "Clone OBJ, initializing `:parent' to OBJ.
 All slots are unbound, except those initialized with PARAMS."
   (let ((nobj (make-vector (length obj) eieio-unbound))
   "Clone OBJ, initializing `:parent' to OBJ.
 All slots are unbound, except those initialized with PARAMS."
   (let ((nobj (make-vector (length obj) eieio-unbound))
-       (nm (aref obj object-name))
+       (nm (eieio--object-name obj))
        (passname (and params (stringp (car params))))
        (num 1))
     (aset nobj 0 'object)
        (passname (and params (stringp (car params))))
        (num 1))
     (aset nobj 0 'object)
-    (aset nobj object-class (aref obj object-class))
+    (setf (eieio--object-class nobj) (eieio--object-class obj))
     ;; The following was copied from the default clone.
     (if (not passname)
        (save-match-data
          (if (string-match "-\\([0-9]+\\)" nm)
              (setq num (1+ (string-to-number (match-string 1 nm)))
                    nm (substring nm 0 (match-beginning 0))))
     ;; The following was copied from the default clone.
     (if (not passname)
        (save-match-data
          (if (string-match "-\\([0-9]+\\)" nm)
              (setq num (1+ (string-to-number (match-string 1 nm)))
                    nm (substring nm 0 (match-beginning 0))))
-         (aset nobj object-name (concat nm "-" (int-to-string num))))
-      (aset nobj object-name (car params)))
+         (setf (eieio--object-name nobj) (concat nm "-" (int-to-string num))))
+      (setf (eieio--object-name nobj) (car params)))
     ;; Now initialize from params.
     (if params (shared-initialize nobj (if passname (cdr params) params)))
     (oset nobj parent-instance obj)
     ;; Now initialize from params.
     (if params (shared-initialize nobj (if passname (cdr params) params)))
     (oset nobj parent-instance obj)
@@ -118,7 +119,7 @@ a variable symbol used to store a list of all instances."
   :abstract t)
 
 (defmethod initialize-instance :AFTER ((this eieio-instance-tracker)
   :abstract t)
 
 (defmethod initialize-instance :AFTER ((this eieio-instance-tracker)
-                                      &rest slots)
+                                      &rest _slots)
   "Make sure THIS is in our master list of this class.
 Optional argument SLOTS are the initialization arguments."
   ;; Theoretically, this is never called twice for a given instance.
   "Make sure THIS is in our master list of this class.
 Optional argument SLOTS are the initialization arguments."
   ;; Theoretically, this is never called twice for a given instance.
@@ -154,7 +155,7 @@ Multiple calls to `make-instance' will return this object."))
 A singleton is a class which will only ever have one instance."
   :abstract t)
 
 A singleton is a class which will only ever have one instance."
   :abstract t)
 
-(defmethod constructor :STATIC ((class eieio-singleton) name &rest slots)
+(defmethod constructor :STATIC ((class eieio-singleton) _name &rest _slots)
   "Constructor for singleton CLASS.
 NAME and SLOTS initialize the new object.
 This constructor guarantees that no matter how many you request,
   "Constructor for singleton CLASS.
 NAME and SLOTS initialize the new object.
 This constructor guarantees that no matter how many you request,
@@ -178,7 +179,7 @@ only one object ever exists."
 ;; calculate path names relative to a given instance.  This will
 ;; make the saved object location independent by converting all file
 ;; references to be relative to the directory the object is saved to.
 ;; calculate path names relative to a given instance.  This will
 ;; make the saved object location independent by converting all file
 ;; references to be relative to the directory the object is saved to.
-;; You must call `eieio-peristent-path-relative' on each file name
+;; You must call `eieio-persistent-path-relative' on each file name
 ;; saved in your object.
 (defclass eieio-persistent ()
   ((file :initarg :file
 ;; saved in your object.
 (defclass eieio-persistent ()
   ((file :initarg :file
@@ -225,8 +226,15 @@ a file.  Optional argument NAME specifies a default file name."
                              ))))
   (oref this file))
 
                              ))))
   (oref this file))
 
-(defun eieio-persistent-read (filename)
-  "Read a persistent object from FILENAME, and return it."
+(defun eieio-persistent-read (filename &optional class allow-subclass)
+  "Read a persistent object from FILENAME, and return it.
+Signal an error if the object in FILENAME is not a constructor
+for CLASS.  Optional ALLOW-SUBCLASS says that it is ok for
+`eieio-persistent-read' to load in subclasses of class instead of
+being pedantic."
+  (unless class
+    (message "Unsafe call to `eieio-persistent-read'."))
+  (when class (eieio--check-type class-p class))
   (let ((ret nil)
        (buffstr nil))
     (unwind-protect
   (let ((ret nil)
        (buffstr nil))
     (unwind-protect
@@ -239,13 +247,171 @@ a file.  Optional argument NAME specifies a default file name."
          ;; so that any initialize-instance calls that depend on
          ;; the current buffer will work.
          (setq ret (read buffstr))
          ;; so that any initialize-instance calls that depend on
          ;; the current buffer will work.
          (setq ret (read buffstr))
-         (if (not (child-of-class-p (car ret) 'eieio-persistent))
-             (error "Corrupt object on disk"))
-         (setq ret (eval ret))
+         (when (not (child-of-class-p (car ret) 'eieio-persistent))
+           (error "Corrupt object on disk: Unknown saved object"))
+         (when (and class
+                    (not (or (eq (car ret) class ) ; same class
+                             (and allow-subclass
+                                  (child-of-class-p (car ret) class)) ; subclasses
+                             )))
+           (error "Corrupt object on disk: Invalid saved class"))
+         (setq ret (eieio-persistent-convert-list-to-object ret))
          (oset ret file filename))
       (kill-buffer " *tmp eieio read*"))
     ret))
 
          (oset ret file filename))
       (kill-buffer " *tmp eieio read*"))
     ret))
 
+(defun eieio-persistent-convert-list-to-object (inputlist)
+  "Convert the INPUTLIST, representing object creation to an object.
+While it is possible to just `eval' the INPUTLIST, this code instead
+validates the existing list, and explicitly creates objects instead of
+calling eval.  This avoids the possibility of accidentally running
+malicious code.
+
+Note: This function recurses when a slot of :type of some object is
+identified, and needing more object creation."
+  (let ((objclass (nth 0 inputlist))
+       (objname (nth 1 inputlist))
+       (slots (nthcdr 2 inputlist))
+       (createslots nil))
+
+    ;; If OBJCLASS is an eieio autoload object, then we need to load it.
+    (eieio-class-un-autoload objclass)
+
+    (while slots
+      (let ((name (car slots))
+           (value (car (cdr slots))))
+
+       ;; Make sure that the value proposed for SLOT is valid.
+       ;; In addition, strip out quotes, list functions, and update
+       ;; object constructors as needed.
+       (setq value (eieio-persistent-validate/fix-slot-value
+                    objclass name value))
+
+       (push name createslots)
+       (push value createslots)
+       )
+
+      (setq slots (cdr (cdr slots))))
+
+    (apply 'make-instance objclass objname (nreverse createslots))
+
+    ;;(eval inputlist)
+    ))
+
+(defun eieio-persistent-validate/fix-slot-value (class slot proposed-value)
+  "Validate that in CLASS, the SLOT with PROPOSED-VALUE is good, then fix.
+A limited number of functions, such as quote, list, and valid object
+constructor functions are considered valid.
+Second, any text properties will be stripped from strings."
+  (cond ((consp proposed-value)
+        ;; Lists with something in them need special treatment.
+        (let ((slot-idx (eieio-slot-name-index class nil slot))
+              (type nil)
+              (classtype nil))
+          (setq slot-idx (- slot-idx 3))
+          (setq type (aref (eieio--class-public-type (class-v class))
+                           slot-idx))
+
+          (setq classtype (eieio-persistent-slot-type-is-class-p
+                           type))
+
+          (cond ((eq (car proposed-value) 'quote)
+                 (car (cdr proposed-value)))
+
+                ;; An empty list sometimes shows up as (list), which is dumb, but
+                ;; we need to support it for backward compat.
+                ((and (eq (car proposed-value) 'list)
+                      (= (length proposed-value) 1))
+                 nil)
+
+                 ;; We have a slot with a single object that can be
+                 ;; saved here.  Recurse and evaluate that
+                 ;; sub-object.
+                ((and classtype (class-p classtype)
+                      (child-of-class-p (car proposed-value) classtype))
+                 (eieio-persistent-convert-list-to-object
+                  proposed-value))
+
+                ;; List of object constructors.
+                ((and (eq (car proposed-value) 'list)
+                      ;; 2nd item is a list.
+                      (consp (car (cdr proposed-value)))
+                      ;; 1st elt of 2nd item is a class name.
+                      (class-p (car (car (cdr proposed-value))))
+                      )
+
+                 ;; Check the value against the input class type.
+                 ;; If something goes wrong, issue a smart warning
+                 ;; about how a :type is needed for this to work.
+                 (unless (and
+                          ;; Do we have a type?
+                          (consp classtype) (class-p (car classtype)))
+                   (error "In save file, list of object constructors found, but no :type specified for slot %S"
+                          slot))
+
+                 ;; We have a predicate, but it doesn't satisfy the predicate?
+                 (dolist (PV (cdr proposed-value))
+                   (unless (child-of-class-p (car PV) (car classtype))
+                     (error "Corrupt object on disk")))
+
+                 ;; We have a list of objects here.  Lets load them
+                 ;; in.
+                 (let ((objlist nil))
+                   (dolist (subobj (cdr proposed-value))
+                     (push (eieio-persistent-convert-list-to-object subobj)
+                           objlist))
+                   ;; return the list of objects ... reversed.
+                   (nreverse objlist)))
+                (t
+                 proposed-value))))
+
+        ((stringp proposed-value)
+         ;; Else, check for strings, remove properties.
+         (substring-no-properties proposed-value))
+
+        (t
+         ;; Else, just return whatever the constant was.
+         proposed-value))
+  )
+
+(defun eieio-persistent-slot-type-is-class-p (type)
+  "Return the class refered to in TYPE.
+If no class is referenced there, then return nil."
+  (cond ((class-p type)
+        ;; If the type is a class, then return it.
+        type)
+
+       ((and (symbolp type) (string-match "-child$" (symbol-name type))
+             (class-p (intern-soft (substring (symbol-name type) 0
+                                              (match-beginning 0)))))
+        ;; If it is the predicate ending with -child, then return
+        ;; that class.  Unfortunately, in EIEIO, typep of just the
+        ;; class is the same as if we used -child, so no further work needed.
+        (intern-soft (substring (symbol-name type) 0
+                                (match-beginning 0))))
+
+       ((and (symbolp type) (string-match "-list$" (symbol-name type))
+             (class-p (intern-soft (substring (symbol-name type) 0
+                                              (match-beginning 0)))))
+        ;; If it is the predicate ending with -list, then return
+        ;; that class and the predicate to use.
+        (cons (intern-soft (substring (symbol-name type) 0
+                                      (match-beginning 0)))
+              type))
+
+       ((and (consp type) (eq (car type) 'or))
+        ;; If type is a list, and is an or, it is possibly something
+        ;; like (or null myclass), so check for that.
+        (let ((ans nil))
+          (dolist (subtype (cdr type))
+            (setq ans (eieio-persistent-slot-type-is-class-p
+                       subtype)))
+          ans))
+
+       (t
+        ;; No match, not a class.
+        nil)))
+
 (defmethod object-write ((this eieio-persistent) &optional comment)
   "Write persistent object THIS out to the current stream.
 Optional argument COMMENT is a header line comment."
 (defmethod object-write ((this eieio-persistent) &optional comment)
   "Write persistent object THIS out to the current stream.
 Optional argument COMMENT is a header line comment."
@@ -317,14 +483,13 @@ Argument SLOT-NAME is the slot that was attempted to be accessed.
 OPERATION is the type of access, such as `oref' or `oset'.
 NEW-VALUE is the value that was being set into SLOT if OPERATION were
 a set type."
 OPERATION is the type of access, such as `oref' or `oset'.
 NEW-VALUE is the value that was being set into SLOT if OPERATION were
 a set type."
-  (if (or (eq slot-name 'object-name)
-         (eq slot-name :object-name))
+  (if (memq slot-name '(object-name :object-name))
       (cond ((eq operation 'oset)
             (if (not (stringp new-value))
                 (signal 'invalid-slot-type
                         (list obj slot-name 'string new-value)))
       (cond ((eq operation 'oset)
             (if (not (stringp new-value))
                 (signal 'invalid-slot-type
                         (list obj slot-name 'string new-value)))
-            (object-set-name-string obj new-value))
-           (t (object-name-string obj)))
+            (eieio-object-set-name-string obj new-value))
+           (t (eieio-object-name-string obj)))
     (call-next-method)))
 
 (provide 'eieio-base)
     (call-next-method)))
 
 (provide 'eieio-base)