Move SSD storage null guards to typed Kotlin

ober

ad8ad9959385a8e1ef9dea01eb2bd744ee66eca6

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index dd72fee..7d90a80 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -1742,7 +1742,7 @@
                 TruthIndexEntry-truthTime betterTruthIndex
                 fileExists fileIsDirectory fileIsNotDirectory
                 fileIsRegular fileIsNotRegular
-                fileListFilesPresent
+                fileListFilesPresent fileParentFilePresent
                 fileIsSymbolicLink
                 fileSizeBytes
                 pathIsSymbolicLink pathNotSymbolicLink pathParentMatches
@@ -1778,6 +1778,7 @@
                 remoteTruthWriteSkipped
                 localFileMissing localTextMatchesRemote
                 shouldReplaceLocalByTimes
+                zipEntryPresent
                 zipEntryIsDirectory zipEntryIsFile zipEntryDeclaresExcessiveSize)
         (type File)
         (type Path)
@@ -1797,6 +1798,8 @@
           (kotlin-member-call toPath))
         (extern (fileListFilesRaw (file : File)) : (Nullable Any)
           (kotlin-member-call listFiles))
+        (extern (fileParentFileRaw (file : File)) : (Nullable File)
+          (kotlin-member-get parentFile))
         (extern (pathIsSymbolicLinkRaw (path : Path)) : Bool
           (kotlin-call Files isSymbolicLink))
         (extern (pathParent (path : Path)) : Path
@@ -1854,6 +1857,8 @@
           (not (fileIsRegular file)))
         (def (fileListFilesPresent (file : File)) : Bool
           (not (nullable-null? (fileListFilesRaw file))))
+        (def (fileParentFilePresent (file : File)) : Bool
+          (not (nullable-null? (fileParentFileRaw file))))
         (def (fileSizeBytes (file : File)) : Int
           (fileLength file))
         (def (pathIsSymbolicLink (path : Path)) : Bool
@@ -1958,6 +1963,8 @@
                                       (maxEntries : Int32)
                                       (maxTotalBytes : Int)) : Bool
           (or (> entries maxEntries) (> bytes maxTotalBytes)))
+        (def (zipEntryPresent (entry : (Nullable ZipEntry))) : Bool
+          (not (nullable-null? entry)))
         (def (byteArraySizeBytes (bytes : Bytes)) : Int
           (int (bytevector-length bytes)))
         (def (byteArrayBytesWithinLimit (bytes : Bytes) (maxBytes : Int)) : Bool
@@ -8766,7 +8773,8 @@
        ""
        "    private fun atomicWrite(destination: File, bytes: ByteArray) {"
        "        ensureStorageCapacity(destination, byteArraySizeBytes(bytes))"
-       "        val parent = destination.parentFile ?: throw IllegalArgumentException(\"Destination has no parent\")"
+       "        if (!fileParentFilePresent(destination)) throw IllegalArgumentException(\"Destination has no parent\")"
+       "        val parent = checkNotNull(destination.parentFile)"
        "        val parentPath = parent.toPath().toRealPath(LinkOption.NOFOLLOW_LINKS)"
        "        require(pathNotSymbolicLink(parentPath)) { \"Symlinked destination parent rejected\" }"
        "        val target = parentPath.resolve(destination.name).normalize()"
@@ -9017,7 +9025,9 @@
        "            val compressed = LimitedInputStream(input, MAX_REMOTE_ZIP_BYTES)"
        "            ZipInputStream(BufferedInputStream(compressed)).use { zip ->"
        "                while (true) {"
-       "                    val entry = zip.nextEntry ?: break"
+       "                    val nextEntry = zip.nextEntry"
+       "                    if (!zipEntryPresent(nextEntry)) break"
+       "                    val entry = checkNotNull(nextEntry)"
        "                    budget.entries += 1"
        "                    if (zipEntryCountExceeded(budget.entries, MAX_ZIP_ENTRIES)) throw IllegalStateException(zipEntryCountLimitExceededMessage(budget.entries, MAX_ZIP_ENTRIES))"
        "                    val name = entry.name"