diff --git a/core/lib/Drupal/Core/Utility/LinkGenerator.php b/core/lib/Drupal/Core/Utility/LinkGenerator.php
index a595394..fd282e9 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(),
     );
@@ -161,7 +169,10 @@ 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;
   }
diff --git a/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php b/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php
index 6659945..08bb2c8 100644
--- a/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php
+++ b/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php
@@ -488,16 +488,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);
   }
 
