diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index 1d05b4a..5300d76 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -514,7 +514,7 @@ function watchdog_exception($type, Exception $exception, $message = NULL, $varia
  * drupal_set_message(t('An error occurred and processing did not complete.'), 'error');
  * @endcode
  *
- * @param string $message
+ * @param string|\Drupal\Component\Utility\SafeStringInterface $message
  *   (optional) The translated message to be displayed to the user. For
  *   consistency with other messages, it should begin with a capital letter and
  *   end with a period.
@@ -561,12 +561,15 @@ function drupal_set_message($message = NULL, $type = 'status', $repeat = FALSE)
       $_SESSION['messages'][$type] = array();
     }
 
-    $new = array(
-      'safe' => SafeMarkup::isSafe($message),
-      'message' => (string) $message,
-    );
-    if ($repeat || !in_array($new, $_SESSION['messages'][$type])) {
-      $_SESSION['messages'][$type][] = $new;
+    // Convert strings which are in the safe markup list to SafeString objects.
+    if (is_string($message) && SafeMarkup::isSafe($message)) {
+      $message = \Drupal\Core\Render\SafeString::create($message);
+    }
+
+    // Do not use strict type checking so that equivalent string and
+    // SafeStringInterface objects are detected.
+    if ($repeat || !in_array($message, $_SESSION['messages'][$type])) {
+      $_SESSION['messages'][$type][] = $message;
     }
 
     // Mark this page as being uncacheable.
@@ -604,18 +607,6 @@ function drupal_set_message($message = NULL, $type = 'status', $repeat = FALSE)
  */
 function drupal_get_messages($type = NULL, $clear_queue = TRUE) {
   if ($messages = drupal_set_message()) {
-    foreach ($messages as $message_type => $message_typed_messages) {
-      foreach ($message_typed_messages as $key => $message) {
-        // Because the messages are stored in the session, the safe status of
-        // the messages also needs to be stored in the session. We retrieve the
-        // safe status here and determine whether to mark the string as safe or
-        // let autoescape do its thing. See drupal_set_message().
-        if ($message['safe']) {
-          $message['message'] = SafeMarkup::set($message['message']);
-        }
-        $messages[$message_type][$key] = $message['message'];
-      }
-    }
     if ($type) {
       if ($clear_queue) {
         unset($_SESSION['messages'][$type]);
diff --git a/core/modules/system/src/Tests/Bootstrap/DrupalSetMessageTest.php b/core/modules/system/src/Tests/Bootstrap/DrupalSetMessageTest.php
index 37a1663..1b63324 100644
--- a/core/modules/system/src/Tests/Bootstrap/DrupalSetMessageTest.php
+++ b/core/modules/system/src/Tests/Bootstrap/DrupalSetMessageTest.php
@@ -24,23 +24,30 @@ class DrupalSetMessageTest extends WebTestBase {
   public static $modules = array('system_test');
 
   /**
-   * Tests setting messages and removing one before it is displayed.
+   * Tests drupal_set_message().
    */
-  function testSetRemoveMessages() {
+  function testDrupalSetMessage() {
     // The page at system-test/drupal-set-message sets two messages and then
     // removes the first before it is displayed.
     $this->drupalGet('system-test/drupal-set-message');
     $this->assertNoText('First message (removed).');
-    $this->assertText('Second message (not removed).');
-  }
+    $this->assertRaw(t('Second message with <em>markup!</em> (not removed).'));
 
-  /**
-   * Tests setting duplicated messages.
-   */
-  function testDuplicatedMessages() {
-    $this->drupalGet('system-test/drupal-set-message');
+    // Ensure duplicate messages are handled as expected.
     $this->assertUniqueText('Non Duplicated message');
     $this->assertNoUniqueText('Duplicated message');
+
+    // Ensure SafeString objects are rendered as expected.
+    $this->assertRaw('SafeString with <em>markup!</em>');
+    $this->assertUniqueText('SafeString with markup!');
+    $this->assertRaw('SafeString2 with <em>markup!</em>');
+
+    // Ensure when the same message is of different types it is not duplicated.
+    $this->assertUniqueText('Non duplicate SafeString / string.');
+    $this->assertNoUniqueText('Duplicate SafeString / string.');
+
+    // Ensure that strings that are not marked as safe are escaped.
+    $this->assertEscaped('<em>This<span>markup will be</span> escaped</em>.');
   }
 
 }
diff --git a/core/modules/system/tests/modules/system_test/src/Controller/SystemTestController.php b/core/modules/system/tests/modules/system_test/src/Controller/SystemTestController.php
index 5ac7ca0..15ab478 100644
--- a/core/modules/system/tests/modules/system_test/src/Controller/SystemTestController.php
+++ b/core/modules/system/tests/modules/system_test/src/Controller/SystemTestController.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Controller\ControllerBase;
 use Drupal\Core\Render\RendererInterface;
+use Drupal\Core\Render\SafeString;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Url;
 use Symfony\Component\HttpFoundation\RedirectResponse;
@@ -101,7 +102,7 @@ public function mainContentFallback() {
   public function drupalSetMessageTest() {
     // Set two messages.
     drupal_set_message('First message (removed).');
-    drupal_set_message('Second message (not removed).');
+    drupal_set_message(t('Second message with <em>markup!</em> (not removed).'));
 
     // Remove the first.
     unset($_SESSION['messages']['status'][0]);
@@ -112,6 +113,23 @@ public function drupalSetMessageTest() {
 
     drupal_set_message('Duplicated message', 'status', TRUE);
     drupal_set_message('Duplicated message', 'status', TRUE);
+
+    // Add a SafeString message.
+    drupal_set_message(SafeString::create('SafeString with <em>markup!</em>'));
+    // Test duplicate SafeString messages.
+    drupal_set_message(SafeString::create('SafeString with <em>markup!</em>'));
+    // Ensure that multiple SafeString messages work.
+    drupal_set_message(SafeString::create('SafeString2 with <em>markup!</em>'));
+
+    // Test mixing of types.
+    drupal_set_message(SafeString::create('Non duplicate SafeString / string.'));
+    drupal_set_message('Non duplicate SafeString / string.');
+    drupal_set_message(SafeString::create('Duplicate SafeString / string.'), 'status', TRUE);
+    drupal_set_message('Duplicate SafeString / string.', 'status', TRUE);
+
+    // Test auto-escape of non safe strings.
+    drupal_set_message('<em>This<span>markup will be</span> escaped</em>.');
+
     return [];
   }
 
