diff --git a/java/kotlin-extractor/src/main/java/com/semmle/extractor/java/OdasaOutput.java b/java/kotlin-extractor/src/main/java/com/semmle/extractor/java/OdasaOutput.java index 01861bf5ca6..0da4b0acda1 100644 --- a/java/kotlin-extractor/src/main/java/com/semmle/extractor/java/OdasaOutput.java +++ b/java/kotlin-extractor/src/main/java/com/semmle/extractor/java/OdasaOutput.java @@ -4,10 +4,14 @@ import java.lang.reflect.*; import java.io.File; import java.io.IOException; import java.util.Arrays; +import java.util.Enumeration; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; import java.util.regex.Pattern; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; import com.github.codeql.Logger; import static com.github.codeql.ClassNamesKt.getIrDeclBinaryName; @@ -547,6 +551,49 @@ public class OdasaOutput { (tcv.majorVersion == majorVersion && tcv.minorVersion == minorVersion && tcv.lastModified < lastModified); } + + private static Map> jarFileEntryTimeStamps = new HashMap<>(); + + private static Map getZipFileEntryTimeStamps(String path, Logger log) { + try { + Map result = new HashMap<>(); + ZipFile zf = new ZipFile(path); + Enumeration entries = zf.entries(); + while (entries.hasMoreElements()) { + ZipEntry ze = entries.nextElement(); + result.put(ze.getName(), ze.getLastModifiedTime().toMillis()); + } + return result; + } catch(IOException e) { + log.warn("Failed to get entry timestamps from " + path, e); + return null; + } + } + + private static long getVirtualFileTimeStamp(VirtualFile vf, Logger log) { + if (vf.getFileSystem().getProtocol().equals("jar")) { + String[] parts = vf.getPath().split("!/"); + if (parts.length == 2) { + String jarFilePath = parts[0]; + String entryPath = parts[1]; + if (!jarFileEntryTimeStamps.containsKey(jarFilePath)) { + jarFileEntryTimeStamps.put(jarFilePath, getZipFileEntryTimeStamps(jarFilePath, log)); + } + Map entryTimeStamps = jarFileEntryTimeStamps.get(jarFilePath); + if (entryTimeStamps != null) { + Long entryTimeStamp = entryTimeStamps.get(entryPath); + if (entryTimeStamp != null) + return entryTimeStamp; + else + log.warn("Couldn't find timestamp for jar file " + jarFilePath + " entry " + entryPath); + } + } + } + + // For all files except for jar files, and a fallback in case of I/O problems reading a jar file: + return vf.getTimeStamp(); + } + private static TrapClassVersion fromSymbol(IrDeclaration sym, Logger log) { VirtualFile vf = sym instanceof IrClass ? getIrClassVirtualFile((IrClass)sym) : sym.getParent() instanceof IrClass ? getIrClassVirtualFile((IrClass)sym.getParent()) : @@ -583,7 +630,7 @@ public class OdasaOutput { }; (new ClassReader(vf.contentsToByteArray())).accept(versionGetter, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); - return new TrapClassVersion(versionStore[0] & 0xffff, versionStore[0] >> 16, vf.getTimeStamp(), "kotlin"); + return new TrapClassVersion(versionStore[0] & 0xffff, versionStore[0] >> 16, getVirtualFileTimeStamp(vf, log), "kotlin"); } catch(IllegalAccessException e) { log.warn("Failed to read class file version information", e);