diff --git a/core/lib/Drupal/Core/GeneratedLink.php b/core/lib/Drupal/Core/GeneratedLink.php
index eb35bc6..bfd9769 100644
--- a/core/lib/Drupal/Core/GeneratedLink.php
+++ b/core/lib/Drupal/Core/GeneratedLink.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Core;
 
+use Drupal\Component\Utility\SafeStringInterface;
 use Drupal\Core\Render\BubbleableMetadata;
 
 /**
@@ -15,7 +16,7 @@
  * Note: not to be confused with \Drupal\Core\Link, which is for passing around
  *   ungenerated links (typically link text + route name + route parameters).
  */
-class GeneratedLink extends BubbleableMetadata {
+class GeneratedLink extends BubbleableMetadata implements SafeStringInterface {
 
   /**
    * The HTML string value containing a link.
@@ -46,4 +47,18 @@ public function setGeneratedLink($generated_link) {
     return $this;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function __toString() {
+    return (string) $this->generatedLink;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function jsonSerialize() {
+    return $this->__toString();
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Utility/LinkGenerator.php b/core/lib/Drupal/Core/Utility/LinkGenerator.php
index a595394..21f7617 100644
--- a/core/lib/Drupal/Core/Utility/LinkGenerator.php
+++ b/core/lib/Drupal/Core/Utility/LinkGenerator.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core\Utility;
 
 use Drupal\Component\Serialization\Json;
+use Drupal\Component\Utility\Html;
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Component\Utility\SafeStringInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
@@ -15,6 +16,7 @@
 use Drupal\Core\Link;
 use Drupal\Core\Path\AliasManagerInterface;
 use Drupal\Core\Render\RendererInterface;
+use Drupal\Core\Render\SafeString;
 use Drupal\Core\Routing\UrlGeneratorInterface;
 use Drupal\Core\Template\Attribute;
 use Drupal\Core\Url;
@@ -85,9 +87,15 @@ public function generate($text, Url $url, $collect_bubbleable_metadata = FALSE)
     // service from the container.
     $url->setUrlGenerator($this->urlGenerator);
 
+    if (is_array($text)) {
+      $text = $this->renderer->render($text);
+    }
+    elseif (!SafeMarkup::isSafe($text)) {
+      $text = Html::escape($text);
+    }
     // Start building a structured representation of our link to be altered later.
     $variables = array(
-      'text' => is_array($text) ? $this->renderer->render($text) : $text,
+      'text' => $text,
       'url' => $url,
       'options' => $url->getOptions(),
     );
@@ -151,6 +159,7 @@ public function generate($text, Url $url, $collect_bubbleable_metadata = FALSE)
 
     if (!$collect_bubbleable_metadata) {
       $url_string = $url->toString($collect_bubbleable_metadata);
+      $generated_link = new GeneratedLink();
     }
     else {
       $generated_url = $url->toString($collect_bubbleable_metadata);
@@ -161,9 +170,12 @@ public function generate($text, Url $url, $collect_bubbleable_metadata = FALSE)
     // attribute, and it is escaped by \Drupal\Core\Template\Attribute.
     $attributes['href'] = $url_string;
 
-    $result = SafeMarkup::format('<a@attributes>@text</a>', array('@attributes' => new Attribute($attributes), '@text' => $variables['text']));
+    $attributes = new Attribute($attributes);
+    // This is safe because Attribute does escaping and $variables['text'] is
+    // either rendered or escaped.
+    $result = SafeString::create('<a' . $attributes . '>' . $variables['text'] . '</a>');
 
-    return $collect_bubbleable_metadata ? $generated_link->setGeneratedLink($result) : $result;
+    return $generated_link->setGeneratedLink($result);
   }
 
 }
diff --git a/core/modules/views_ui/src/Controller/ViewsUIController.php b/core/modules/views_ui/src/Controller/ViewsUIController.php
index 2837508..be71932 100644
--- a/core/modules/views_ui/src/Controller/ViewsUIController.php
+++ b/core/modules/views_ui/src/Controller/ViewsUIController.php
@@ -121,13 +121,15 @@ public function reportFields() {
   public function reportPlugins() {
     $rows = Views::pluginList();
     foreach ($rows as &$row) {
+      $views = [];
       // Link each view name to the view itself.
       foreach ($row['views'] as $row_name => $view) {
-        $row['views'][$row_name] = $this->l($view, new Url('entity.view.edit_form', array('view' => $view)));
+        $views[] = $this->l($view, new Url('entity.view.edit_form', array('view' => $view)));
       }
+      unset($row['views']);
       $row['views']['data'] = [
         '#theme' => 'item_list',
-        '#items' => $row['views'],
+        '#items' => $views,
         '#context' => ['list_style' => 'comma-list'],
       ];
     }
diff --git a/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php b/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php
index 6659945..4d1cee0 100644
--- a/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php
+++ b/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Tests\Core\Utility {
 
+use Drupal\Component\Utility\SafeStringInterface;
 use Drupal\Core\GeneratedUrl;
 use Drupal\Core\Language\Language;
 use Drupal\Core\Link;
@@ -488,16 +489,16 @@ public function testGenerateBubbleableMetadata() {
     $expected_link_markup = '<a href="/test-route-1">Test</a>';
 
     // Test ::generate().
-    $this->assertSame($expected_link_markup, $this->linkGenerator->generate('Test', $url));
+    $this->assertSame($expected_link_markup, (string) $this->linkGenerator->generate('Test', $url));
     $generated_link = $this->linkGenerator->generate('Test', $url, TRUE);
-    $this->assertSame($expected_link_markup, $generated_link->getGeneratedLink());
+    $this->assertSame($expected_link_markup, (string) $generated_link->getGeneratedLink());
     $this->assertInstanceOf('\Drupal\Core\Render\BubbleableMetadata', $generated_link);
 
     // Test ::generateFromLink().
     $link = new Link('Test', $url);
-    $this->assertSame($expected_link_markup, $this->linkGenerator->generateFromLink($link));
+    $this->assertSame($expected_link_markup, (string) $this->linkGenerator->generateFromLink($link));
     $generated_link = $this->linkGenerator->generateFromLink($link, TRUE);
-    $this->assertSame($expected_link_markup, $generated_link->getGeneratedLink());
+    $this->assertSame($expected_link_markup, (string) $generated_link->getGeneratedLink());
     $this->assertInstanceOf('\Drupal\Core\Render\BubbleableMetadata', $generated_link);
   }
 
@@ -508,12 +509,12 @@ public function testGenerateBubbleableMetadata() {
    *   An associative array of link properties, with the following keys:
    *   - attributes: optional array of HTML attributes that should be present.
    *   - content: optional link content.
-   * @param string $html
+   * @param \Drupal\Component\Utility\SafeStringInterface $html
    *   The HTML to check.
    * @param int $count
    *   How many times the link should be present in the HTML. Defaults to 1.
    */
-  public static function assertLink(array $properties, $html, $count = 1) {
+  public static function assertLink(array $properties, SafeStringInterface $html, $count = 1) {
     // Provide default values.
     $properties += array('attributes' => array());
 
