diff --git a/core/modules/update/tests/modules/aaa_update_test/aaa_update_test.info.yml b/core/modules/update/tests/modules/aaa_update_test/aaa_update_test.info.yml
index 19367eac8cd577f4c0fa120d2aa9da8e87646d57..ee90601b12de491bd9c7c351080e63a9e3f2e230 100644
--- a/core/modules/update/tests/modules/aaa_update_test/aaa_update_test.info.yml
+++ b/core/modules/update/tests/modules/aaa_update_test/aaa_update_test.info.yml
@@ -2,4 +2,3 @@ name: 'AAA Update test'
 type: module
 description: 'Support module for update module testing.'
 package: Testing
-version: VERSION
diff --git a/core/modules/update/tests/src/Functional/UpdateContribTest.php b/core/modules/update/tests/src/Functional/UpdateContribTest.php
index 7c50fafd89cdb3315efc08984b87d75f90d8f12b..0fff16789608cab0d5e48387be0251e675b3ab66 100644
--- a/core/modules/update/tests/src/Functional/UpdateContribTest.php
+++ b/core/modules/update/tests/src/Functional/UpdateContribTest.php
@@ -794,6 +794,43 @@ public function testUnsupportedRelease() {
     $this->confirmUnsupportedStatus('8.x-1.1', '8.x-2.0', 'Recommended version:');
   }
 
+  /**
+   * Tests messages for invalid, empty and missing version strings.
+   */
+  public function testNonStandardVersionStrings() {
+    $version_infos = [
+      'invalid' => [
+        'version' => 'llama',
+        'expected' => 'Invalid version: llama',
+      ],
+      'empty' => [
+        'version' => '',
+        'expected' => 'Empty version',
+      ],
+      'null' => [
+        'expected' => 'Invalid version: Unknown',
+      ],
+    ];
+    foreach ($version_infos as $version_info) {
+      $system_info = [
+        'aaa_update_test' => [
+          'project' => 'aaa_update_test',
+          'hidden' => FALSE,
+        ],
+      ];
+      if (isset($version_info['version'])) {
+        $system_info['aaa_update_test']['version'] = $version_info['version'];
+      }
+      $this->config('update_test.settings')->set('system_info', $system_info)->save();
+      $this->refreshUpdateStatus([
+        'drupal' => '0.0',
+        $this->updateProject => '1_0-supported',
+      ]);
+      $this->standardTests();
+      $this->assertSession()->elementTextContains('css', $this->updateTableLocator, $version_info['expected']);
+    }
+  }
+
   /**
    * Asserts that a core compatibility message is correct for an update.
    *
diff --git a/core/modules/update/update.compare.inc b/core/modules/update/update.compare.inc
index d318e6c486332def7e5d4d48830bab055f6fae01..1eadbed71f314cde66c235b00c52c53cba8a8605 100644
--- a/core/modules/update/update.compare.inc
+++ b/core/modules/update/update.compare.inc
@@ -196,6 +196,9 @@ function update_calculate_project_data($available) {
  * version (e.g., 5.x-1.5-beta1, 5.x-1.5-beta2, and 5.x-1.5). Development
  * snapshots for a given major version are always listed last.
  *
+ * NOTE: This function *must* set a value for $project_data['status'] before
+ * returning, or the rest of the Update Manager will break in unexpected ways.
+ *
  * @param $project_data
  *   An array containing information about a specific project.
  * @param $available
@@ -263,11 +266,19 @@ function update_calculate_project_update_status(&$project_data, $available) {
   }
 
   // Figure out the target major version.
+  // Off Drupal.org, '0' could be a valid version string, so don't use empty().
+  if (!isset($project_data['existing_version']) || $project_data['existing_version'] === '') {
+    $project_data['status'] = UpdateFetcherInterface::UNKNOWN;
+    $project_data['reason'] = t('Empty version');
+    return;
+  }
   try {
     $existing_major = ModuleVersion::createFromVersionString($project_data['existing_version'])->getMajorVersion();
   }
   catch (UnexpectedValueException $exception) {
     // If the version has an unexpected value we can't determine updates.
+    $project_data['status'] = UpdateFetcherInterface::UNKNOWN;
+    $project_data['reason'] = t('Invalid version: @existing_version', ['@existing_version' => $project_data['existing_version']]);
     return;
   }
   $supported_branches = [];