--- old/src/share/classes/javax/security/auth/Subject.java 2014-06-04 14:13:11.651311957 -0700
+++ new/src/share/classes/javax/security/auth/Subject.java 2014-06-04 14:13:11.149248146 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1998, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2014, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -142,7 +142,9 @@
*
The newly constructed Sets check whether this {@code Subject}
* has been set read-only before permitting subsequent modifications.
* The newly created Sets also prevent illegal modifications
- * by ensuring that callers have sufficient permissions.
+ * by ensuring that callers have sufficient permissions. These Sets
+ * also prohibit null elements, and attempts to add or query a null
+ * element will result in a {@code NullPointerException}.
*
*
To modify the Principals Set, the caller must have
* {@code AuthPermission("modifyPrincipals")}.
@@ -170,7 +172,9 @@
* These newly created Sets check whether this {@code Subject}
* has been set read-only before permitting subsequent modifications.
* The newly created Sets also prevent illegal modifications
- * by ensuring that callers have sufficient permissions.
+ * by ensuring that callers have sufficient permissions. These Sets
+ * also prohibit null elements, and attempts to add or query a null
+ * element will result in a {@code NullPointerException}.
*
*
To modify the Principals Set, the caller must have
* {@code AuthPermission("modifyPrincipals")}.
@@ -194,17 +198,19 @@
*
* @exception NullPointerException if the specified
* {@code principals}, {@code pubCredentials},
- * or {@code privCredentials} are {@code null}.
+ * or {@code privCredentials} are {@code null},
+ * or a null value exists within any of these three
+ * Sets.
*/
public Subject(boolean readOnly, Set extends Principal> principals,
Set> pubCredentials, Set> privCredentials)
{
-
- if (principals == null ||
- pubCredentials == null ||
- privCredentials == null)
+ if (collectionNullClean(principals) == false ||
+ collectionNullClean(pubCredentials) == false ||
+ collectionNullClean(privCredentials) == false) {
throw new NullPointerException
(ResourcesMgr.getString("invalid.null.input.s."));
+ }
this.principals = Collections.synchronizedSet(new SecureSet
(this, PRINCIPAL_SET, principals));
@@ -970,10 +976,6 @@
Set inputPrincs = (Set)gf.get("principals", null);
// Rewrap the principals into a SecureSet
- if (inputPrincs == null) {
- throw new NullPointerException
- (ResourcesMgr.getString("invalid.null.input.s."));
- }
try {
principals = Collections.synchronizedSet(new SecureSet
(this, PRINCIPAL_SET, inputPrincs));
@@ -993,13 +995,36 @@
}
/**
+ * Tests for null-clean collections (both non-null reference and
+ * no null elements)
+ *
+ * @param coll A {@code Collection} to be tested for null references
+ *
+ * @return true if {@code coll} is non-null and contains no null
+ * elements, false otherwise.
+ */
+ private static boolean collectionNullClean(Collection> coll) {
+ boolean isClean = false;
+
+ try {
+ isClean = (coll != null) && !coll.contains(null);
+ } catch (NullPointerException npe) {
+ // A null-hostile collection may choose to return null if
+ // contains(null) is called on it rather than returning false.
+ // If so then we know no nulls are present.
+ isClean = true;
+ }
+
+ return isClean;
+ }
+
+ /**
* Prevent modifications unless caller has permission.
*
* @serial include
*/
private static class SecureSet
- extends AbstractSet
- implements java.io.Serializable {
+ implements Set, java.io.Serializable {
private static final long serialVersionUID = 7911754171111800359L;
@@ -1098,6 +1123,11 @@
public boolean add(E o) {
+ if (o == null) {
+ throw new NullPointerException
+ (ResourcesMgr.getString("invalid.null.input.s."));
+ }
+
if (subject.isReadOnly()) {
throw new IllegalStateException
(ResourcesMgr.getString("Subject.is.read.only"));
@@ -1139,6 +1169,11 @@
public boolean remove(Object o) {
+ if (o == null) {
+ throw new NullPointerException
+ (ResourcesMgr.getString("invalid.null.input.s."));
+ }
+
final Iterator e = iterator();
while (e.hasNext()) {
E next;
@@ -1153,12 +1188,7 @@
});
}
- if (next == null) {
- if (o == null) {
- e.remove();
- return true;
- }
- } else if (next.equals(o)) {
+ if (next.equals(o)) {
e.remove();
return true;
}
@@ -1167,6 +1197,12 @@
}
public boolean contains(Object o) {
+
+ if (o == null) {
+ throw new NullPointerException
+ (ResourcesMgr.getString("invalid.null.input.s."));
+ }
+
final Iterator e = iterator();
while (e.hasNext()) {
E next;
@@ -1194,19 +1230,34 @@
});
}
- if (next == null) {
- if (o == null) {
- return true;
- }
- } else if (next.equals(o)) {
+ if (next.equals(o)) {
return true;
}
}
return false;
}
+ public boolean addAll(Collection extends E> c) {
+ boolean result = false;
+
+ if (collectionNullClean(c) == false) {
+ throw new NullPointerException
+ (ResourcesMgr.getString("invalid.null.input.s."));
+ }
+
+ for (E item : c) {
+ result |= this.add(item);
+ }
+
+ return result;
+ }
+
public boolean removeAll(Collection> c) {
- Objects.requireNonNull(c);
+ if (collectionNullClean(c) == false) {
+ throw new NullPointerException
+ (ResourcesMgr.getString("invalid.null.input.s."));
+ }
+
boolean modified = false;
final Iterator e = iterator();
while (e.hasNext()) {
@@ -1224,14 +1275,7 @@
Iterator> ce = c.iterator();
while (ce.hasNext()) {
- Object o = ce.next();
- if (next == null) {
- if (o == null) {
- e.remove();
- modified = true;
- break;
- }
- } else if (next.equals(o)) {
+ if (next.equals(ce.next())) {
e.remove();
modified = true;
break;
@@ -1241,13 +1285,30 @@
return modified;
}
+ public boolean containsAll(Collection> c) {
+ if (collectionNullClean(c) == false) {
+ throw new NullPointerException
+ (ResourcesMgr.getString("invalid.null.input.s."));
+ }
+
+ for (Object item : c) {
+ if (this.contains(item) == false) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
public boolean retainAll(Collection> c) {
- Objects.requireNonNull(c);
+ if (collectionNullClean(c) == false) {
+ throw new NullPointerException
+ (ResourcesMgr.getString("invalid.null.input.s."));
+ }
+
boolean modified = false;
- boolean retain = false;
final Iterator e = iterator();
while (e.hasNext()) {
- retain = false;
E next;
if (which != Subject.PRIV_CREDENTIAL_SET) {
next = e.next();
@@ -1260,26 +1321,12 @@
});
}
- Iterator> ce = c.iterator();
- while (ce.hasNext()) {
- Object o = ce.next();
- if (next == null) {
- if (o == null) {
- retain = true;
- break;
- }
- } else if (next.equals(o)) {
- retain = true;
- break;
- }
- }
-
- if (!retain) {
+ if (c.contains(next) == false) {
e.remove();
- retain = false;
modified = true;
}
}
+
return modified;
}
@@ -1301,6 +1348,56 @@
}
}
+ public boolean isEmpty() {
+ return elements.isEmpty();
+ }
+
+ public Object[] toArray() {
+ final Iterator e = iterator();
+ while (e.hasNext()) {
+ // The next() method performs a security manager check
+ // on each element in the SecureSet. If we make it all
+ // the way through we should be able to simply return
+ // element's toArray results. Otherwise we'll let
+ // the SecurityException pass up the call stack.
+ e.next();
+ }
+
+ return elements.toArray();
+ }
+
+ public T[] toArray(T[] a) {
+ final Iterator e = iterator();
+ while (e.hasNext()) {
+ // The next() method performs a security manager check
+ // on each element in the SecureSet. If we make it all
+ // the way through we should be able to simply return
+ // element's toArray results. Otherwise we'll let
+ // the SecurityException pass up the call stack.
+ e.next();
+ }
+
+ return elements.toArray(a);
+ }
+
+ public boolean equals(Object o) {
+ if (o == this)
+ return true;
+
+ if (!(o instanceof Set))
+ return false;
+ Collection> c = (Collection>) o;
+ if (c.size() != size())
+ return false;
+ try {
+ return containsAll(c);
+ } catch (ClassCastException unused) {
+ return false;
+ } catch (NullPointerException unused) {
+ return false;
+ }
+ }
+
/**
* Writes this object out to a stream (i.e., serializes it).
*
@@ -1338,12 +1435,19 @@
which = fields.get("which", 0);
LinkedList tmp = (LinkedList) fields.get("elements", null);
+
+ if (Subject.collectionNullClean(tmp) == false) {
+ throw new NullPointerException
+ (ResourcesMgr.getString("invalid.null.input.s."));
+ }
+
if (tmp.getClass() != LinkedList.class) {
elements = new LinkedList(tmp);
} else {
elements = tmp;
}
}
+
}
/**
Binary files /dev/null and new/test/javax/security/auth/Subject/PrinNoNull.bin differ
Binary files /dev/null and new/test/javax/security/auth/Subject/PrinWithNull.bin differ
Binary files /dev/null and new/test/javax/security/auth/Subject/SubjNoNull.bin differ
Binary files /dev/null and new/test/javax/security/auth/Subject/SubjWithNull.bin differ
--- /dev/null 2014-05-30 09:27:22.741293751 -0700
+++ new/test/javax/security/auth/Subject/SubjectNullTests.java 2014-06-04 14:13:14.399661269 -0700
@@ -0,0 +1,760 @@
+/*
+ * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code 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
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8015081
+ * @summary javax.security.auth.Subject.toString() throws NPE
+ */
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.ObjectInputStream;
+import java.io.IOException;
+import java.lang.Exception;
+import java.security.Principal;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.TreeSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import javax.management.remote.JMXPrincipal;
+import javax.security.auth.Subject;
+import javax.security.auth.x500.X500Principal;
+import javax.security.auth.kerberos.KerberosPrincipal;
+
+public class SubjectNullTests {
+
+ // Value templates for the constructor
+ private static Principal[] princVals = {
+ new X500Principal("CN=Tom Sawyer, ST=Missouri, C=US"),
+ new JMXPrincipal("Huckleberry Finn"),
+ new KerberosPrincipal("mtwain/author@LITERATURE.US")
+ };
+ private static String[] pubVals = {"tsawyer", "hfinn", "mtwain"};
+ private static String[] privVals = {"th3R!v3r", "oNth3R4ft", "5Cl3M3nz"};
+
+ // Templates for collection-based modifiers for the Subject
+ private static Principal[] tmplAddPrincs = {
+ new X500Principal("CN=John Doe, O=Bogus Corp."),
+ new KerberosPrincipal("jdoe/admin@BOGUSCORP.COM")
+ };
+ private static String[] tmplAddPubVals = {"jdoe", "djoe"};
+ private static String[] tmplAddPrvVals = {"b4dpa55w0rd", "pass123"};
+
+ /**
+ * Construct a subject, and optionally place a null in any one
+ * of the three Sets used to initialize a Subject's values
+ */
+ private static Subject makeSubj(boolean nullPrinc, boolean nullPub,
+ boolean nullPriv) {
+ Set setPrinc =
+ new HashSet(Arrays.asList(princVals));
+ Set setPubCreds = new HashSet(Arrays.asList(pubVals));
+ Set setPrvCreds = new HashSet(Arrays.asList(privVals));
+
+ if (nullPrinc) {
+ setPrinc.add(null);
+ }
+
+ if (nullPub) {
+ setPubCreds.add(null);
+ }
+
+ if (nullPriv) {
+ setPrvCreds.add(null);
+ }
+
+ return (new Subject(false, setPrinc, setPubCreds, setPrvCreds));
+ }
+
+ /**
+ * Provide a simple interface for abstracting collection-on-collection
+ * functions
+ */
+ public interface Function {
+ boolean execCollection(Set> subjSet, Collection> actorData);
+ }
+
+ public static final Function methAdd = new Function() {
+ public boolean execCollection(Set> subjSet, Collection> actorData) {
+ return subjSet.addAll((Collection)actorData);
+ }
+ };
+
+ public static final Function methContains = new Function() {
+ public boolean execCollection(Set> subjSet, Collection> actorData) {
+ return subjSet.containsAll(actorData);
+ }
+ };
+
+ public static final Function methRemove = new Function() {
+ public boolean execCollection(Set> subjSet, Collection> actorData) {
+ return subjSet.removeAll(actorData);
+ }
+ };
+
+ public static final Function methRetain = new Function() {
+ public boolean execCollection(Set> subjSet, Collection> actorData) {
+ return subjSet.retainAll(actorData);
+ }
+ };
+
+ /**
+ * Run a test using a specified Collection method upon a Subject's
+ * SecureSet fields. This method expects NullPointerExceptions
+ * to be thrown, and throws RuntimeException when the operation
+ * succeeds
+ */
+ private static void nullTestCollection(Function meth, Set> subjSet,
+ Collection> actorData) {
+ try {
+ meth.execCollection(subjSet, actorData);
+ throw new RuntimeException("Failed to throw NullPointerException");
+ } catch (NullPointerException npe) {
+ System.out.println("Caught expected NullPointerException [PASS]");
+ }
+ }
+
+ /**
+ * Run a test using a specified Collection method upon a Subject's
+ * SecureSet fields. This method expects the function and arguments
+ * passed in to complete without exception. It returns false
+ * if either an exception occurs or the result of the operation is
+ * false.
+ */
+ private static boolean validTestCollection(Function meth, Set> subjSet,
+ Collection> actorData) {
+ boolean result = false;
+
+ try {
+ result = meth.execCollection(subjSet, actorData);
+ } catch (Exception exc) {
+ System.out.println("Caught exception " + exc);
+ }
+
+ return result;
+ }
+
+ /**
+ * Deserialize an object from a file.
+ *
+ * @param type The {@code Class} that the serialized file is supposed
+ * to contain.
+ * @param fileName The name of the file to be deserialized.
+ *
+ * @return An object of the type specified in the {@code type} parameter
+ */
+ private static T deserializeFile(Class type, String fileName)
+ throws IOException, ClassNotFoundException {
+ File fileObj = new File(System.getProperty("test.src", "."), fileName);
+ FileInputStream fis = new FileInputStream(fileObj);
+ ObjectInputStream ois = new ObjectInputStream(fis);
+
+ T newObj = (T)ois.readObject();
+ ois.close();
+ fis.close();
+
+ return newObj;
+ }
+
+ private static void testCTOR() {
+ System.out.println("------ constructor ------");
+
+ try {
+ // Case 1: Create a subject with a null principal
+ // Expected result: NullPointerException
+ Subject mtSubj = makeSubj(true, false, false);
+ throw new RuntimeException(
+ "constructor [principal w/ null]: Failed to throw NPE");
+ } catch (NullPointerException npe) {
+ System.out.println("constructor [principal w/ null]: " +
+ "NullPointerException [PASS]");
+ }
+
+ try {
+ // Case 2: Create a subject with a null public credential element
+ // Expected result: NullPointerException
+ Subject mtSubj = makeSubj(false, true, false);
+ throw new RuntimeException(
+ "constructor [pub cred w/ null]: Failed to throw NPE");
+ } catch (NullPointerException npe) {
+ System.out.println("constructor [pub cred w/ null]: " +
+ "NullPointerException [PASS]");
+ }
+
+ try {
+ // Case 3: Create a subject with a null private credential element
+ // Expected result: NullPointerException
+ Subject mtSubj = makeSubj(false, false, true);
+ throw new RuntimeException(
+ "constructor [priv cred w/ null]: Failed to throw NPE");
+ } catch (NullPointerException npe) {
+ System.out.println("constructor [priv cred w/ null]: " +
+ "NullPointerException [PASS]");
+ }
+
+ // Case 4: Create a new subject using the principals, public
+ // and private credentials from another well-formed subject
+ // Expected result: Successful construction
+ Subject srcSubj = makeSubj(false, false, false);
+ Subject mtSubj = new Subject(false, srcSubj.getPrincipals(),
+ srcSubj.getPublicCredentials(),
+ srcSubj.getPrivateCredentials());
+ System.out.println("Construction from another well-formed Subject's " +
+ "principals/creds [PASS]");
+
+
+
+ // XXX random tests here
+ System.out.println("------ Random Tests Here -----");
+ System.out.println("isEmpty test: " +
+ srcSubj.getPrincipals().isEmpty() + ", " +
+ srcSubj.getPublicCredentials().isEmpty() + ", " +
+ srcSubj.getPrivateCredentials().isEmpty());
+ Set pl = srcSubj.getPrincipals();
+ Object[] oar = pl.toArray();
+ System.out.println("toArray display: " + oar.length + " elements");
+ System.out.print("Subj.princ: ");
+ for (Object y : pl) {
+ System.out.print(y + " | ");
+ }
+ System.out.println();
+ System.out.print("Subj.princ.toArray: ");
+ for (Object y : oar) {
+ System.out.print(y + " | ");
+ }
+ System.out.println();
+
+ List emptyList = new LinkedList<>();
+ Set emptySet = new HashSet<>();
+ Set someSet = new HashSet(Arrays.asList(princVals));
+ System.out.println("What happens if you containsAll(emptyColl)? " +
+ someSet.containsAll(emptyList));
+ System.out.println("What about empty-on-empty? " +
+ emptySet.containsAll(emptyList));
+
+
+ }
+
+ private static void testDeserialize() throws Exception {
+ System.out.println("------ deserialize -----");
+
+ Subject subj = null;
+ Set prin = null;
+
+ // Case 1: positive deserialization test of a Subject
+ // Expected result: well-formed Subject
+ subj = deserializeFile(Subject.class, "SubjNoNull.bin");
+ System.out.println("Positive deserialization test (Subject) passed");
+
+ // Case 2: positive deserialization test of a SecureSet
+ // Expected result: well-formed Set
+ prin = deserializeFile(Set.class, "PrinNoNull.bin");
+ System.out.println("Positive deserialization test (SecureSet) passed");
+
+ System.out.println(
+ "* Testing deserialization with null-poisoned objects");
+ // Case 3: deserialization test of a null-poisoned Subject
+ // Expected result: NullPointerException
+ try {
+ subj = deserializeFile(Subject.class, "SubjWithNull.bin");
+ throw new RuntimeException("Failed to throw NullPointerException");
+ } catch (NullPointerException npe) {
+ System.out.println("Caught expected NullPointerException [PASS]");
+ }
+
+ // Case 4: deserialization test of a null-poisoned SecureSet
+ // Expected result: NullPointerException
+ try {
+ prin = deserializeFile(Set.class, "PrinWithNull.bin");
+ throw new RuntimeException("Failed to throw NullPointerException");
+ } catch (NullPointerException npe) {
+ System.out.println("Caught expected NullPointerException [PASS]");
+ }
+ }
+
+ private static void testAdd() {
+ System.out.println("------ add() ------");
+ // Create a well formed subject
+ Subject mtSubj = makeSubj(false, false, false);
+
+ try {
+ // Case 1: Attempt to add null values to principal
+ // Expected result: NullPointerException
+ mtSubj.getPrincipals().add(null);
+ throw new RuntimeException(
+ "PRINCIPAL add(null): Failed to throw NPE");
+ } catch (NullPointerException npe) {
+ System.out.println(
+ "PRINCIPAL add(null): NullPointerException [PASS]");
+ }
+
+ try {
+ // Case 2: Attempt to add null into the public creds
+ // Expected result: NullPointerException
+ mtSubj.getPublicCredentials().add(null);
+ throw new RuntimeException(
+ "PUB CRED add(null): Failed to throw NPE");
+ } catch (NullPointerException npe) {
+ System.out.println(
+ "PUB CRED add(null): NullPointerException [PASS]");
+ }
+
+ try {
+ // Case 3: Attempt to add null into the private creds
+ // Expected result: NullPointerException
+ mtSubj.getPrivateCredentials().add(null);
+ throw new RuntimeException(
+ "PRIV CRED add(null): Failed to throw NPE");
+ } catch (NullPointerException npe) {
+ System.out.println(
+ "PRIV CRED add(null): NullPointerException [PASS]");
+ }
+ }
+
+ private static void testRemove() {
+ System.out.println("------ remove() ------");
+ // Create a well formed subject
+ Subject mtSubj = makeSubj(false, false, false);
+
+ try {
+ // Case 1: Attempt to remove null values from principal
+ // Expected result: NullPointerException
+ mtSubj.getPrincipals().remove(null);
+ throw new RuntimeException(
+ "PRINCIPAL remove(null): Failed to throw NPE");
+ } catch (NullPointerException npe) {
+ System.out.println(
+ "PRINCIPAL remove(null): NullPointerException [PASS]");
+ }
+
+ try {
+ // Case 2: Attempt to remove null from the public creds
+ // Expected result: NullPointerException
+ mtSubj.getPublicCredentials().remove(null);
+ throw new RuntimeException(
+ "PUB CRED remove(null): Failed to throw NPE");
+ } catch (NullPointerException npe) {
+ System.out.println(
+ "PUB CRED remove(null): NullPointerException [PASS]");
+ }
+
+ try {
+ // Case 3: Attempt to remove null from the private creds
+ // Expected result: NullPointerException
+ mtSubj.getPrivateCredentials().remove(null);
+ throw new RuntimeException(
+ "PRIV CRED remove(null): Failed to throw NPE");
+ } catch (NullPointerException npe) {
+ System.out.println(
+ "PRIV CRED remove(null): NullPointerException [PASS]");
+ }
+ }
+
+ private static void testContains() {
+ System.out.println("------ contains() ------");
+ // Create a well formed subject
+ Subject mtSubj = makeSubj(false, false, false);
+
+ try {
+ // Case 1: Attempt to check for null values in principals
+ // Expected result: NullPointerException
+ mtSubj.getPrincipals().contains(null);
+ throw new RuntimeException(
+ "PRINCIPAL contains(null): Failed to throw NPE");
+ } catch (NullPointerException npe) {
+ System.out.println(
+ "PRINCIPAL contains(null): NullPointerException [PASS]");
+ }
+
+ try {
+ // Case 2: Attempt to check for null in public creds
+ // Expected result: NullPointerException
+ mtSubj.getPublicCredentials().contains(null);
+ throw new RuntimeException(
+ "PUB CRED contains(null): Failed to throw NPE");
+ } catch (NullPointerException npe) {
+ System.out.println(
+ "PUB CRED contains(null): NullPointerException [PASS]");
+ }
+
+ try {
+ // Case 3: Attempt to check for null in private creds
+ // Expected result: NullPointerException
+ mtSubj.getPrivateCredentials().contains(null);
+ throw new RuntimeException(
+ "PRIV CRED contains(null): Failed to throw NPE");
+ } catch (NullPointerException npe) {
+ System.out.println(
+ "PRIV CRED contains(null): NullPointerException [PASS]");
+ }
+ }
+
+ private static void testAddAll() {
+ // Create a well formed subject and additional collections
+ Subject mtSubj = makeSubj(false, false, false);
+ Set morePrincs = new HashSet<>(Arrays.asList(tmplAddPrincs));
+ Set