diff --git a/core/lib/Drupal/Core/Theme/ThemeManager.php b/core/lib/Drupal/Core/Theme/ThemeManager.php
index 31c10a7..5b37a70 100644
--- a/core/lib/Drupal/Core/Theme/ThemeManager.php
+++ b/core/lib/Drupal/Core/Theme/ThemeManager.php
@@ -7,12 +7,12 @@
 
 namespace Drupal\Core\Theme;
 
+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 +317,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 +390,7 @@ public function render($hook, array $variables) {
       $output = $render_function($template_file, $variables);
     }
 
-    return (string) $output;
+    return ($output instanceof SafeString) ? $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/simpletest/src/AssertContentTrait.php b/core/modules/simpletest/src/AssertContentTrait.php
index 1918d42..bc66e27 100644
--- a/core/modules/simpletest/src/AssertContentTrait.php
+++ b/core/modules/simpletest/src/AssertContentTrait.php
@@ -812,7 +812,7 @@ 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) {
+    $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..4697c2f 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,18 @@ 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.
+    // 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');
     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)));
+      if (is_string($output)) {
+        $this->assertIdentical($output, '', format_string('\Drupal::theme() returns a string for data type !type.', array('!type' => $type)));
+      }
+      else {
+        $this->assertTrue($output instanceof SafeStringInterface, format_string('\Drupal::theme() returns an object that implements SafeStringInterface for data type !type.', array('!type' => $type)));
+      }
     }
 
     // suggestionnotimplemented is not an implemented theme hook so \Drupal::theme() service
diff --git a/core/modules/system/templates/dropbutton-wrapper.html.twig b/core/modules/system/templates/dropbutton-wrapper.html.twig
index ca0ff7e..d4c8d90 100644
--- a/core/modules/system/templates/dropbutton-wrapper.html.twig
+++ b/core/modules/system/templates/dropbutton-wrapper.html.twig
@@ -12,7 +12,7 @@
  * @ingroup themeable
  */
 #}
-{% if children %}
+{% if children|length %}
   {% spaceless %}
     <div class="dropbutton-wrapper">
       <div class="dropbutton-widget">
diff --git a/core/modules/views/src/Plugin/views/field/FieldHandlerInterface.php b/core/modules/views/src/Plugin/views/field/FieldHandlerInterface.php
index 6cd22ac..494c333 100644
--- a/core/modules/views/src/Plugin/views/field/FieldHandlerInterface.php
+++ b/core/modules/views/src/Plugin/views/field/FieldHandlerInterface.php
@@ -236,8 +236,8 @@ 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, or a SafeString object.
    */
   public function renderText($alter);
 
diff --git a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php
index ee16994..54e89ea 100644
--- a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php
+++ b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php
@@ -1173,7 +1173,7 @@ public function advancedRender(ResultRow $values) {
       }
       // 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;
+      $this->last_render = (string) $value;
     }
 
     if (empty($this->last_render)) {
@@ -1214,6 +1214,7 @@ public function isValueEmpty($value, $empty_zero, $no_skip_empty = TRUE) {
    */
   public function renderText($alter) {
     $value = $this->last_render;
+    $value_is_safe = SafeMarkup::isSafe($value);
 
     if (!empty($alter['alter_text']) && $alter['text'] !== '') {
       $tokens = $this->getRenderTokens($alter);
@@ -1239,6 +1240,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 +1279,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,7 +1294,16 @@ 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;
+    }
   }
 
   /**
diff --git a/core/themes/engines/twig/twig.engine b/core/themes/engines/twig/twig.engine
index af5ebe7..7f8e6eb 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;
 
 /**
@@ -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));
 }
 
 /**
