diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php
index cbd9ca8..c30402e 100644
--- a/core/lib/Drupal/Core/Render/Renderer.php
+++ b/core/lib/Drupal/Core/Render/Renderer.php
@@ -511,7 +511,7 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
     $prefix = isset($elements['#prefix']) ? $this->xssFilterAdminIfUnsafe($elements['#prefix']) : '';
     $suffix = isset($elements['#suffix']) ? $this->xssFilterAdminIfUnsafe($elements['#suffix']) : '';
 
-    $elements['#markup'] = $prefix . $elements['#children'] . $suffix;
+    $elements['#markup'] = SafeString::create($prefix . $elements['#children'] . $suffix);
 
     // We've rendered this element (and its subtree!), now update the context.
     $context->update($elements);
@@ -546,7 +546,7 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
     $context->bubble();
 
     $elements['#printed'] = TRUE;
-    return SafeString::create($elements['#markup']);
+    return $elements['#markup'];
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Theme/ThemeManager.php b/core/lib/Drupal/Core/Theme/ThemeManager.php
index 31c10a7..f6c5714 100644
--- a/core/lib/Drupal/Core/Theme/ThemeManager.php
+++ b/core/lib/Drupal/Core/Theme/ThemeManager.php
@@ -7,12 +7,13 @@
 
 namespace Drupal\Core\Theme;
 
+use Drupal\Component\Utility\SafeStringInterface;
+use Drupal\Core\Render\SafeString;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Routing\StackedRouteMatchInterface;
 use Symfony\Component\HttpFoundation\RequestStack;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Template\Attribute;
-use Drupal\Component\Utility\SafeMarkup;
 
 /**
  * Provides the default implementation of a theme manager.
@@ -317,7 +318,10 @@ public function render($hook, array $variables) {
     $output = '';
     if (isset($info['function'])) {
       if (function_exists($info['function'])) {
-        $output = SafeMarkup::set($info['function']($variables));
+        // Theme functions do not render via the theme engine, so the output is
+        // not autoescaped. However, we can only presume that the theme function
+        // has been written correctly and that the markup is safe.
+        $output = SafeString::create($info['function']($variables));
       }
     }
     else {
@@ -387,7 +391,7 @@ public function render($hook, array $variables) {
       $output = $render_function($template_file, $variables);
     }
 
-    return (string) $output;
+    return ($output instanceof SafeStringInterface) ? $output : (string) $output;
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Theme/ThemeManagerInterface.php b/core/lib/Drupal/Core/Theme/ThemeManagerInterface.php
index ebbfcb5..b61ff84 100644
--- a/core/lib/Drupal/Core/Theme/ThemeManagerInterface.php
+++ b/core/lib/Drupal/Core/Theme/ThemeManagerInterface.php
@@ -26,8 +26,8 @@
    * @param array $variables
    *   An associative array of theme variables.
    *
-   * @return string
-   *   The rendered output.
+   * @return string|\Drupal\Component\Utility\SafeStringInterface
+   *   The rendered output, or a SafeString object.
    */
   public function render($hook, array $variables);
 
diff --git a/core/modules/comment/src/Tests/Views/CommentFieldNameTest.php b/core/modules/comment/src/Tests/Views/CommentFieldNameTest.php
index 2fcf468..42182c9 100644
--- a/core/modules/comment/src/Tests/Views/CommentFieldNameTest.php
+++ b/core/modules/comment/src/Tests/Views/CommentFieldNameTest.php
@@ -91,11 +91,11 @@ public function testCommentFieldName() {
     $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
       return $view->field['field_name']->advancedRender($view->result[0]);
     });
-    $this->assertIdentical($this->comment->getFieldName(), $output);
+    $this->assertEqual($this->comment->getFieldName(), $output);
     $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
       return $view->field['field_name']->advancedRender($view->result[1]);
     });
-    $this->assertIdentical($this->customComment->getFieldName(), $output);
+    $this->assertEqual($this->customComment->getFieldName(), $output);
   }
 
 }
diff --git a/core/modules/serialization/serialization.services.yml b/core/modules/serialization/serialization.services.yml
index 4c9397a..318a90c 100644
--- a/core/modules/serialization/serialization.services.yml
+++ b/core/modules/serialization/serialization.services.yml
@@ -30,6 +30,10 @@ services:
       arguments: ['Drupal\Core\Field\Plugin\Field\FieldType\PasswordItem']
       tags:
         - { name: normalizer, priority: 20 }
+  serializer.normalizer.safe_string:
+      class: Drupal\serialization\Normalizer\SafeStringNormalizer
+      tags:
+        - { name: normalizer }
   serializer.normalizer.typed_data:
     class: Drupal\serialization\Normalizer\TypedDataNormalizer
     tags:
diff --git a/core/modules/serialization/src/Normalizer/SafeStringNormalizer.php b/core/modules/serialization/src/Normalizer/SafeStringNormalizer.php
new file mode 100644
index 0000000..16b7fcd
--- /dev/null
+++ b/core/modules/serialization/src/Normalizer/SafeStringNormalizer.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\serialization\Normalizer\SafeStringNormalizer.
+ */
+
+namespace Drupal\serialization\Normalizer;
+
+/**
+ * Normalizes SafeStringInterface objects into a string.
+ */
+class SafeStringNormalizer extends NormalizerBase {
+
+  /**
+   * The interface or class that this Normalizer supports.
+   *
+   * @var array
+   */
+  protected $supportedInterfaceOrClass = array('Drupal\Component\Utility\SafeStringInterface');
+
+  /**
+   * {@inheritdoc}
+   */
+  public function normalize($object, $format = NULL, array $context = array()) {
+    return (string) $object;
+  }
+
+}
diff --git a/core/modules/simpletest/src/AssertContentTrait.php b/core/modules/simpletest/src/AssertContentTrait.php
index 1918d42..34e7628 100644
--- a/core/modules/simpletest/src/AssertContentTrait.php
+++ b/core/modules/simpletest/src/AssertContentTrait.php
@@ -812,7 +812,10 @@ protected function assertThemeOutput($callback, array $variables = array(), $exp
     /** @var \Drupal\Core\Render\RendererInterface $renderer */
     $renderer = \Drupal::service('renderer');
 
-    $output = $renderer->executeInRenderContext(new RenderContext(), function() use ($callback, $variables) {
+    // The string cast is necessary because theme functions return
+    // SafeStringInterface objects. This means we can assert that $expected
+    // matches the theme output without having to worry about 0 == ''.
+    $output = (string) $renderer->executeInRenderContext(new RenderContext(), function() use ($callback, $variables) {
       return \Drupal::theme()->render($callback, $variables);
     });
     $this->verbose(
diff --git a/core/modules/system/src/Tests/Theme/ThemeTest.php b/core/modules/system/src/Tests/Theme/ThemeTest.php
index c2c0e0b..a0095b8 100644
--- a/core/modules/system/src/Tests/Theme/ThemeTest.php
+++ b/core/modules/system/src/Tests/Theme/ThemeTest.php
@@ -13,6 +13,7 @@
 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\Routing\Route;
+use Drupal\Component\Utility\SafeStringInterface;
 
 /**
  * Tests low-level theme functions.
@@ -59,12 +60,19 @@ function testAttributeMerging() {
    * Test that _theme() returns expected data types.
    */
   function testThemeDataTypes() {
-    // theme_test_false is an implemented theme hook so \Drupal::theme() service should
-    // return a string, even though the theme function itself can return anything.
-    $foos = array('null' => NULL, 'false' => FALSE, 'integer' => 1, 'string' => 'foo');
+    // theme_test_false is an implemented theme hook so \Drupal::theme() service
+    // should return a string or an object that implements SafeStringInterface,
+    // even though the theme function itself can return anything.
+    $foos = array('null' => NULL, 'false' => FALSE, 'integer' => 1, 'string' => 'foo', 'empty_string' => '');
     foreach ($foos as $type => $example) {
       $output = \Drupal::theme()->render('theme_test_foo', array('foo' => $example));
-      $this->assertTrue(is_string($output), format_string('\Drupal::theme() returns a string for data type !type.', array('!type' => $type)));
+      $this->assertTrue($output instanceof SafeStringInterface || is_string($output), format_string('\Drupal::theme() returns an object that implements SafeStringInterface or a string for data type !type.', array('!type' => $type)));
+      if ($output instanceof SafeStringInterface) {
+        $this->assertIdentical((string) $example, $output->__toString());
+      }
+      elseif (is_string($output)) {
+        $this->assertIdentical($output, '', 'A string will be return when the theme returns an empty string.');
+      }
     }
 
     // suggestionnotimplemented is not an implemented theme hook so \Drupal::theme() service
diff --git a/core/modules/user/src/Tests/Views/HandlerFieldUserNameTest.php b/core/modules/user/src/Tests/Views/HandlerFieldUserNameTest.php
index 5db1e3e..42f9b429 100644
--- a/core/modules/user/src/Tests/Views/HandlerFieldUserNameTest.php
+++ b/core/modules/user/src/Tests/Views/HandlerFieldUserNameTest.php
@@ -63,7 +63,7 @@ public function testUserName() {
     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
       return $view->field['name']->advancedRender($view->result[0]);
     });
-    $this->assertIdentical($render, $username, 'If the user is not linked the username should be printed out for a normal user.');
+    $this->assertEqual($render, $username, 'If the user is not linked the username should be printed out for a normal user.');
 
   }
 
diff --git a/core/modules/views/src/Plugin/views/field/FieldHandlerInterface.php b/core/modules/views/src/Plugin/views/field/FieldHandlerInterface.php
index 6cd22ac..b25e419 100644
--- a/core/modules/views/src/Plugin/views/field/FieldHandlerInterface.php
+++ b/core/modules/views/src/Plugin/views/field/FieldHandlerInterface.php
@@ -167,8 +167,10 @@ public function preRender(&$values);
    * @param \Drupal\views\ResultRow $values
    *   The values retrieved from a single row of a view's query result.
    *
-   * @return string
-   *   The rendered output.
+   * @return string|\Drupal\Component\Utility\SafeStringInterface
+   *   The rendered output. If the output is safe it will be wrapped in a
+   *   \Drupal\Core\Render\SafeString object. If it is empty or unsafe it will
+   *   be a string.
    *
    */
   public function render(ResultRow $values);
@@ -202,8 +204,10 @@ public function postRender(ResultRow $row, $output);
    * @param \Drupal\views\ResultRow $values
    *   The values retrieved from a single row of a view's query result.
    *
-   * @return string
-   *   The advanced rendered output.
+   * @return string|\Drupal\Component\Utility\SafeStringInterface
+   *   The advanced rendered output. If the output is safe it will be wrapped in
+   *   a \Drupal\Core\Render\SafeString object. If it is empty or unsafe it will
+   *   be a string.
    *
    */
   public function advancedRender(ResultRow $values);
@@ -236,30 +240,14 @@ public function isValueEmpty($value, $empty_zero, $no_skip_empty = TRUE);
    *     - ellipsis: Show an ellipsis (…) at the end of the trimmed string.
    *     - html: Make sure that the html is correct.
    *
-   * @return string
-   *   The rendered string.
+   * @return string|\Drupal\Component\Utility\SafeStringInterface
+   *   The rendered output. If the output is safe it will be wrapped in a
+   *   \Drupal\Core\Render\SafeString object. If it is empty or unsafe it will
+   *   be a string.
    */
   public function renderText($alter);
 
   /**
-   * Trims the field down to the specified length.
-   *
-   * @param array $alter
-   *   The alter array of options to use.
-   *     - max_length: Maximum length of the string, the rest gets truncated.
-   *     - word_boundary: Trim only on a word boundary.
-   *     - ellipsis: Show an ellipsis (…) at the end of the trimmed string.
-   *     - html: Make sure that the html is correct.
-   *
-   * @param string $value
-   *   The string which should be trimmed.
-   *
-   * @return string
-   *   The rendered trimmed string.
-   */
-  public function renderTrimText($alter, $value);
-
-  /**
    * Gets the 'render' tokens to use for advanced rendering.
    *
    * This runs through all of the fields and arguments that
@@ -280,8 +268,10 @@ public function getRenderTokens($item);
    * @param \Drupal\views\ResultRow $values
    *   Holds single row of a view's result set.
    *
-   * @return string|false
-   *   Returns rendered output of the given theme implementation.
+   * @return string|\Drupal\Component\Utility\SafeStringInterface
+   *   Returns rendered output of the given theme implementation. If the output
+   *   is safe it will be wrapped in a \Drupal\Core\Render\SafeString object. If
+   *   it is empty or unsafe it will be a string.
    */
   function theme(ResultRow $values);
 
diff --git a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php
index ee16994..973916b 100644
--- a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php
+++ b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php
@@ -10,6 +10,7 @@
 use Drupal\Component\Utility\Html;
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Component\Utility\SafeStringInterface;
 use Drupal\Component\Utility\Unicode;
 use Drupal\Component\Utility\UrlHelper;
 use Drupal\Component\Utility\Xss;
@@ -1138,7 +1139,7 @@ public function advancedRender(ResultRow $values) {
     else {
       $value = $this->render($values);
       if (is_array($value)) {
-        $value = (string) $this->getRenderer()->render($value);
+        $value = $this->getRenderer()->render($value);
       }
       $this->last_render = $value;
       $this->original_value = $value;
@@ -1169,14 +1170,16 @@ public function advancedRender(ResultRow $values) {
       }
 
       if (is_array($value)) {
-        $value = (string) $this->getRenderer()->render($value);
+        $value = $this->getRenderer()->render($value);
       }
       // This happens here so that renderAsLink can get the unaltered value of
       // this field as a token rather than the altered value.
       $this->last_render = $value;
     }
 
-    if (empty($this->last_render)) {
+    // String cast is necessary to test emptiness of SafeStringInterface
+    // objects.
+    if (empty((string) $this->last_render)) {
       if ($this->isValueEmpty($this->last_render, $this->options['empty_zero'], FALSE)) {
         $alter = $this->options['alter'];
         $alter['alter_text'] = 1;
@@ -1185,9 +1188,6 @@ public function advancedRender(ResultRow $values) {
         $this->last_render = $this->renderText($alter);
       }
     }
-    // @todo Fix this in https://www.drupal.org/node/2280961.
-    $this->last_render = SafeMarkup::set($this->last_render);
-
 
     return $this->last_render;
   }
@@ -1196,6 +1196,10 @@ public function advancedRender(ResultRow $values) {
    * {@inheritdoc}
    */
   public function isValueEmpty($value, $empty_zero, $no_skip_empty = TRUE) {
+    // Convert SafeStringInterface to a string for checking.
+    if ($value instanceof SafeStringInterface) {
+      $value = (string) $value;
+    }
     if (!isset($value)) {
       $empty = TRUE;
     }
@@ -1213,7 +1217,14 @@ public function isValueEmpty($value, $empty_zero, $no_skip_empty = TRUE) {
    * {@inheritdoc}
    */
   public function renderText($alter) {
-    $value = $this->last_render;
+    // We need to preserve the safeness of the value regardless of the
+    // alterations made by this method. Any alterations or replacements made
+    // within this method need to ensure that at the minimum the result is
+    // Xss admin filtered. See self::renderAltered() as an exmaple that does.
+    $value_is_safe = SafeMarkup::isSafe($this->last_render);
+    // Cast to a string so that empty checks and string functions work as
+    // expected.
+    $value = (string) $this->last_render;
 
     if (!empty($alter['alter_text']) && $alter['text'] !== '') {
       $tokens = $this->getRenderTokens($alter);
@@ -1239,6 +1250,9 @@ public function renderText($alter) {
     if ($alter['phase'] == static::RENDER_TEXT_PHASE_EMPTY && $no_rewrite_for_empty) {
       // If we got here then $alter contains the value of "No results text"
       // and so there is nothing left to do.
+      if ($value_is_safe) {
+        $value = SafeString::create($value);
+      }
       return $value;
     }
 
@@ -1275,6 +1289,12 @@ public function renderText($alter) {
     if (!empty($alter['nl2br'])) {
       $value = nl2br($value);
     }
+
+    // Preserve whether or not the string is safe. Since $suffix comes from
+    // \Drupal::l(), it is safe to append.
+    if ($value_is_safe) {
+      $value = SafeString::create($value . $suffix);
+    }
     $this->last_render_text = $value;
 
     if (!empty($alter['make_link']) && (!empty($alter['path']) || !empty($alter['url']))) {
@@ -1284,20 +1304,42 @@ public function renderText($alter) {
       $value = $this->renderAsLink($alter, $value, $tokens);
     }
 
-    return $value . $suffix;
+    // Preserve whether or not the string is safe. Since $suffix comes from
+    // \Drupal::l(), it is safe to append.
+    if ($value_is_safe) {
+      return SafeString::create($value . $suffix);
+    }
+    else {
+      // If the string is not already marked safe, it is still OK to return it
+      // because it will be sanitized by Twig.
+      return $value . $suffix;
+    }
   }
 
   /**
    * Render this field as user-defined altered text.
    */
   protected function renderAltered($alter, $tokens) {
-    return SafeString::create($this->viewsTokenReplace($alter['text'], $tokens));
+    return $this->viewsTokenReplace($alter['text'], $tokens);
   }
 
   /**
-   * {@inheritdoc}
+   * Trims the field down to the specified length.
+   *
+   * @param array $alter
+   *   The alter array of options to use.
+   *     - max_length: Maximum length of the string, the rest gets truncated.
+   *     - word_boundary: Trim only on a word boundary.
+   *     - ellipsis: Show an ellipsis (…) at the end of the trimmed string.
+   *     - html: Make sure that the html is correct.
+   *
+   * @param string $value
+   *   The string which should be trimmed.
+   *
+   * @return string
+   *   The rendered trimmed string.
    */
-  public function renderTrimText($alter, $value) {
+  protected function renderTrimText($alter, $value) {
     if (!empty($alter['strip_tags'])) {
       // NOTE: It's possible that some external fields might override the
       // element type.
diff --git a/core/modules/views/src/Tests/Handler/FieldFileSizeTest.php b/core/modules/views/src/Tests/Handler/FieldFileSizeTest.php
index c2c9af5..70edf22 100644
--- a/core/modules/views/src/Tests/Handler/FieldFileSizeTest.php
+++ b/core/modules/views/src/Tests/Handler/FieldFileSizeTest.php
@@ -64,9 +64,9 @@ public function testFieldFileSize() {
     // Test with the bytes option.
     $view->field['age']->options['file_size_display'] = 'bytes';
     $this->assertEqual($view->field['age']->advancedRender($view->result[0]), '');
-    $this->assertEqual($view->field['age']->advancedRender($view->result[1]), 10);
-    $this->assertEqual($view->field['age']->advancedRender($view->result[2]), 1000);
-    $this->assertEqual($view->field['age']->advancedRender($view->result[3]), 10000);
+    $this->assertEqual($view->field['age']->advancedRender($view->result[1]), '10');
+    $this->assertEqual($view->field['age']->advancedRender($view->result[2]), '1000');
+    $this->assertEqual($view->field['age']->advancedRender($view->result[3]), '10000');
   }
 
 }
diff --git a/core/modules/views/src/Tests/Handler/FieldUnitTest.php b/core/modules/views/src/Tests/Handler/FieldUnitTest.php
index 85fabe3..7ad9cdc 100644
--- a/core/modules/views/src/Tests/Handler/FieldUnitTest.php
+++ b/core/modules/views/src/Tests/Handler/FieldUnitTest.php
@@ -324,7 +324,8 @@ function testEmpty() {
   /**
    * Tests the hide if empty functionality.
    *
-   * This tests alters the result to get easier and less coupled results.
+   * This tests alters the result to get easier and less coupled results. It is
+   * important that assertIdentical() is used in this test since in PHP 0 == ''.
    */
   function _testHideIfEmpty() {
     /** @var \Drupal\Core\Render\RendererInterface $renderer */
@@ -349,7 +350,7 @@ function _testHideIfEmpty() {
     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
       return $view->field['name']->advancedRender($view->result[0]);
     });
-    $this->assertIdentical($render, $random_name, 'By default, a string should not be treated as empty.');
+    $this->assertIdentical((string) $render, $random_name, 'By default, a string should not be treated as empty.');
 
     // Test an empty string.
     $view->result[0]->{$column_map_reversed['name']} = "";
@@ -363,14 +364,14 @@ function _testHideIfEmpty() {
     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
       return $view->field['name']->advancedRender($view->result[0]);
     });
-    $this->assertIdentical($render, '0', 'By default, 0 should not be treated as empty.');
+    $this->assertIdentical((string) $render, '0', 'By default, 0 should not be treated as empty.');
 
     // Test zero as a string.
     $view->result[0]->{$column_map_reversed['name']} = "0";
     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
       return $view->field['name']->advancedRender($view->result[0]);
     });
-    $this->assertIdentical($render, "0", 'By default, "0" should not be treated as empty.');
+    $this->assertIdentical((string) $render, "0", 'By default, "0" should not be treated as empty.');
 
     // Test when results are not rewritten and non-zero empty values are hidden.
     $view->field['name']->options['hide_alter_empty'] = TRUE;
@@ -382,7 +383,7 @@ function _testHideIfEmpty() {
     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
       return $view->field['name']->advancedRender($view->result[0]);
     });
-    $this->assertIdentical($render, $random_name, 'If hide_empty is checked, a string should not be treated as empty.');
+    $this->assertIdentical((string) $render, $random_name, 'If hide_empty is checked, a string should not be treated as empty.');
 
     // Test an empty string.
     $view->result[0]->{$column_map_reversed['name']} = "";
@@ -396,14 +397,14 @@ function _testHideIfEmpty() {
     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
       return $view->field['name']->advancedRender($view->result[0]);
     });
-    $this->assertIdentical($render, '0', 'If hide_empty is checked, but not empty_zero, 0 should not be treated as empty.');
+    $this->assertIdentical((string) $render, '0', 'If hide_empty is checked, but not empty_zero, 0 should not be treated as empty.');
 
     // Test zero as a string.
     $view->result[0]->{$column_map_reversed['name']} = "0";
     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
       return $view->field['name']->advancedRender($view->result[0]);
     });
-    $this->assertIdentical($render, "0", 'If hide_empty is checked, but not empty_zero, "0" should not be treated as empty.');
+    $this->assertIdentical((string) $render, "0", 'If hide_empty is checked, but not empty_zero, "0" should not be treated as empty.');
 
     // Test when results are not rewritten and all empty values are hidden.
     $view->field['name']->options['hide_alter_empty'] = TRUE;
@@ -437,28 +438,28 @@ function _testHideIfEmpty() {
     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
       return $view->field['name']->advancedRender($view->result[0]);
     });
-    $this->assertIdentical($render, $random_name, 'If the rewritten string is not empty, it should not be treated as empty.');
+    $this->assertIdentical((string) $render, $random_name, 'If the rewritten string is not empty, it should not be treated as empty.');
 
     // Test an empty string.
     $view->result[0]->{$column_map_reversed['name']} = "";
     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
       return $view->field['name']->advancedRender($view->result[0]);
     });
-    $this->assertIdentical($render, $random_name, 'If the rewritten string is not empty, "" should not be treated as empty.');
+    $this->assertIdentical((string) $render, $random_name, 'If the rewritten string is not empty, "" should not be treated as empty.');
 
     // Test zero as an integer.
     $view->result[0]->{$column_map_reversed['name']} = 0;
     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
       return $view->field['name']->advancedRender($view->result[0]);
     });
-    $this->assertIdentical($render, $random_name, 'If the rewritten string is not empty, 0 should not be treated as empty.');
+    $this->assertIdentical((string) $render, $random_name, 'If the rewritten string is not empty, 0 should not be treated as empty.');
 
     // Test zero as a string.
     $view->result[0]->{$column_map_reversed['name']} = "0";
     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
       return $view->field['name']->advancedRender($view->result[0]);
     });
-    $this->assertIdentical($render, $random_name, 'If the rewritten string is not empty, "0" should not be treated as empty.');
+    $this->assertIdentical((string) $render, $random_name, 'If the rewritten string is not empty, "0" should not be treated as empty.');
 
     // Test when results are rewritten to an empty string and non-zero empty results are hidden.
     $view->field['name']->options['hide_alter_empty'] = TRUE;
@@ -472,7 +473,7 @@ function _testHideIfEmpty() {
     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
       return $view->field['name']->advancedRender($view->result[0]);
     });
-    $this->assertIdentical($render, $random_name, 'If the rewritten string is empty, it should not be treated as empty.');
+    $this->assertIdentical((string) $render, $random_name, 'If the rewritten string is empty, it should not be treated as empty.');
 
     // Test an empty string.
     $view->result[0]->{$column_map_reversed['name']} = "";
@@ -486,14 +487,14 @@ function _testHideIfEmpty() {
     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
       return $view->field['name']->advancedRender($view->result[0]);
     });
-    $this->assertIdentical($render, '0', 'If the rewritten string is empty, 0 should not be treated as empty.');
+    $this->assertIdentical((string) $render, '0', 'If the rewritten string is empty, 0 should not be treated as empty.');
 
     // Test zero as a string.
     $view->result[0]->{$column_map_reversed['name']} = "0";
     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
       return $view->field['name']->advancedRender($view->result[0]);
     });
-    $this->assertIdentical($render, "0", 'If the rewritten string is empty, "0" should not be treated as empty.');
+    $this->assertIdentical((string) $render, "0", 'If the rewritten string is empty, "0" should not be treated as empty.');
 
     // Test when results are rewritten to zero as a string and non-zero empty
     // results are hidden.
@@ -508,28 +509,28 @@ function _testHideIfEmpty() {
     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
       return $view->field['name']->advancedRender($view->result[0]);
     });
-    $this->assertIdentical($render, "0", 'If the rewritten string is zero and empty_zero is not checked, the string rewritten as 0 should not be treated as empty.');
+    $this->assertIdentical((string) $render, "0", 'If the rewritten string is zero and empty_zero is not checked, the string rewritten as 0 should not be treated as empty.');
 
     // Test an empty string.
     $view->result[0]->{$column_map_reversed['name']} = "";
     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
       return $view->field['name']->advancedRender($view->result[0]);
     });
-    $this->assertIdentical($render, "0", 'If the rewritten string is zero and empty_zero is not checked, "" rewritten as 0 should not be treated as empty.');
+    $this->assertIdentical((string) $render, "0", 'If the rewritten string is zero and empty_zero is not checked, "" rewritten as 0 should not be treated as empty.');
 
     // Test zero as an integer.
     $view->result[0]->{$column_map_reversed['name']} = 0;
     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
       return $view->field['name']->advancedRender($view->result[0]);
     });
-    $this->assertIdentical($render, "0", 'If the rewritten string is zero and empty_zero is not checked, 0 should not be treated as empty.');
+    $this->assertIdentical((string) $render, "0", 'If the rewritten string is zero and empty_zero is not checked, 0 should not be treated as empty.');
 
     // Test zero as a string.
     $view->result[0]->{$column_map_reversed['name']} = "0";
     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
       return $view->field['name']->advancedRender($view->result[0]);
     });
-    $this->assertIdentical($render, "0", 'If the rewritten string is zero and empty_zero is not checked, "0" should not be treated as empty.');
+    $this->assertIdentical((string) $render, "0", 'If the rewritten string is zero and empty_zero is not checked, "0" should not be treated as empty.');
 
     // Test when results are rewritten to a valid string and non-zero empty
     // results are hidden.
@@ -544,7 +545,7 @@ function _testHideIfEmpty() {
     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
       return $view->field['name']->advancedRender($view->result[0]);
     });
-    $this->assertIdentical($render, $random_value, 'If the original and rewritten strings are valid, it should not be treated as empty.');
+    $this->assertIdentical((string) $render, $random_value, 'If the original and rewritten strings are valid, it should not be treated as empty.');
 
     // Test an empty string.
     $view->result[0]->{$column_map_reversed['name']} = "";
@@ -558,14 +559,14 @@ function _testHideIfEmpty() {
     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
       return $view->field['name']->advancedRender($view->result[0]);
     });
-    $this->assertIdentical($render, $random_value, 'If the original and rewritten strings are valid, 0 should not be treated as empty.');
+    $this->assertIdentical((string) $render, $random_value, 'If the original and rewritten strings are valid, 0 should not be treated as empty.');
 
     // Test zero as a string.
     $view->result[0]->{$column_map_reversed['name']} = "0";
     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
       return $view->field['name']->advancedRender($view->result[0]);
     });
-    $this->assertIdentical($render, $random_value, 'If the original and rewritten strings are valid, "0" should not be treated as empty.');
+    $this->assertIdentical((string) $render, $random_value, 'If the original and rewritten strings are valid, "0" should not be treated as empty.');
 
     // Test when results are rewritten to zero as a string and all empty
     // original values and results are hidden.
@@ -580,7 +581,7 @@ function _testHideIfEmpty() {
     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
       return $view->field['name']->advancedRender($view->result[0]);
     });
-    $this->assertIdentical($render, "", 'If the rewritten string is zero, it should be treated as empty.');
+    $this->assertIdentical((string) $render, "", 'If the rewritten string is zero, it should be treated as empty.');
 
     // Test an empty string.
     $view->result[0]->{$column_map_reversed['name']} = "";
@@ -623,20 +624,20 @@ function _testEmptyText() {
     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
       return $view->field['name']->advancedRender($view->result[0]);
     });
-    $this->assertIdentical($render, $empty_text, 'If a field is empty, the empty text should be used for the output.');
+    $this->assertIdentical((string) $render, $empty_text, 'If a field is empty, the empty text should be used for the output.');
 
     $view->result[0]->{$column_map_reversed['name']} = "0";
     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
       return $view->field['name']->advancedRender($view->result[0]);
     });
-    $this->assertIdentical($render, "0", 'If a field is 0 and empty_zero is not checked, the empty text should not be used for the output.');
+    $this->assertIdentical((string) $render, "0", 'If a field is 0 and empty_zero is not checked, the empty text should not be used for the output.');
 
     $view->result[0]->{$column_map_reversed['name']} = "0";
     $view->field['name']->options['empty_zero'] = TRUE;
     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
       return $view->field['name']->advancedRender($view->result[0]);
     });
-    $this->assertIdentical($render, $empty_text, 'If a field is 0 and empty_zero is checked, the empty text should be used for the output.');
+    $this->assertIdentical((string) $render, $empty_text, 'If a field is 0 and empty_zero is checked, the empty text should be used for the output.');
 
     $view->result[0]->{$column_map_reversed['name']} = "";
     $view->field['name']->options['alter']['alter_text'] = TRUE;
@@ -645,13 +646,13 @@ function _testEmptyText() {
     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
       return $view->field['name']->advancedRender($view->result[0]);
     });
-    $this->assertIdentical($render, $alter_text, 'If a field is empty, some rewrite text exists, but hide_alter_empty is not checked, render the rewrite text.');
+    $this->assertIdentical((string) $render, $alter_text, 'If a field is empty, some rewrite text exists, but hide_alter_empty is not checked, render the rewrite text.');
 
     $view->field['name']->options['hide_alter_empty'] = TRUE;
     $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
       return $view->field['name']->advancedRender($view->result[0]);
     });
-    $this->assertIdentical($render, $empty_text, 'If a field is empty, some rewrite text exists, and hide_alter_empty is checked, use the empty text.');
+    $this->assertIdentical((string) $render, $empty_text, 'If a field is empty, some rewrite text exists, and hide_alter_empty is checked, use the empty text.');
   }
 
   /**
diff --git a/core/themes/engines/twig/twig.engine b/core/themes/engines/twig/twig.engine
index af5ebe7..de085ff 100644
--- a/core/themes/engines/twig/twig.engine
+++ b/core/themes/engines/twig/twig.engine
@@ -6,6 +6,7 @@
  */
 
 use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Core\Render\SafeString;
 use Drupal\Core\Extension\Extension;
 
 /**
@@ -44,7 +45,7 @@ function twig_init(Extension $theme) {
  * @param array $variables
  *   A keyed array of variables that will appear in the output.
  *
- * @return string
+ * @return string|\Drupal\Component\Utility\SafeStringInterface
  *   The output generated by the template, plus any debug information.
  */
 function twig_render_template($template_file, array $variables) {
@@ -74,7 +75,7 @@ function twig_render_template($template_file, array $variables) {
   }
   if ($twig_service->isDebug()) {
     $output['debug_prefix'] .= "\n\n<!-- THEME DEBUG -->";
-    $output['debug_prefix'] .= "\n<!-- THEME HOOK: '" . SafeMarkup::checkPlain($variables['theme_hook_original']) . "' -->";
+    $output['debug_prefix'] .= "\n<!-- THEME HOOK: '" . htmlspecialchars($variables['theme_hook_original'], ENT_QUOTES, 'UTF-8') . "' -->";
     // If there are theme suggestions, reverse the array so more specific
     // suggestions are shown first.
     if (!empty($variables['theme_hook_suggestions'])) {
@@ -108,12 +109,13 @@ function twig_render_template($template_file, array $variables) {
         $prefix = ($template == $current_template) ? 'x' : '*';
         $suggestion = $prefix . ' ' . $template;
       }
-      $output['debug_info'] .= "\n<!-- FILE NAME SUGGESTIONS:\n   " . SafeMarkup::checkPlain(implode("\n   ", $suggestions)) . "\n-->";
+      $output['debug_info'] .= "\n<!-- FILE NAME SUGGESTIONS:\n   " . htmlspecialchars(implode("\n   ", $suggestions), ENT_QUOTES, 'UTF-8') . "\n-->";
     }
-    $output['debug_info']   .= "\n<!-- BEGIN OUTPUT from '" . SafeMarkup::checkPlain($template_file) . "' -->\n";
-    $output['debug_suffix'] .= "\n<!-- END OUTPUT from '" . SafeMarkup::checkPlain($template_file) . "' -->\n\n";
+    $output['debug_info']   .= "\n<!-- BEGIN OUTPUT from '" . htmlspecialchars($template_file, ENT_QUOTES, 'UTF-8') . "' -->\n";
+    $output['debug_suffix'] .= "\n<!-- END OUTPUT from '" . htmlspecialchars($template_file, ENT_QUOTES, 'UTF-8') . "' -->\n\n";
   }
-  return SafeMarkup::set(implode('', $output));
+  // This output has already been rendered and is therefore considered safe.
+  return SafeString::create(implode('', $output));
 }
 
 /**
