Skip to content

Commit 4df4734

Browse files
committed
Merge dev: v3.6.1 security hardening, memory fixes, SMSD optimization
2 parents bd25a43 + d54974f commit 4df4734

11 files changed

Lines changed: 67 additions & 26 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,6 @@ hs_err_pid*
3838
/rdt/core/Output/
3939
/rdt/target/
4040
/rdt/Output/
41+
.claude/
42+
.env
43+
*.secret

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<groupId>com.bioinceptionlabs</groupId>
55
<artifactId>rdt</artifactId>
66
<description>Reaction Decoder Tool</description>
7-
<version>3.6.0</version>
7+
<version>3.6.1</version>
88
<packaging>jar</packaging>
99
<properties>
1010
<jdk.version>21</jdk.version>

src/main/java/com/bioinceptionlabs/aamtool/ChemicalFormatParser.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ class ChemicalFormatParser {
5858

5959
protected static IReaction parseCML(String input) throws FileNotFoundException, CDKException {
6060
File f = new File(input);
61+
try {
62+
f = f.getCanonicalFile();
63+
} catch (IOException e) {
64+
throw new FileNotFoundException("Invalid file path: " + input);
65+
}
6166
if (!f.isFile()) {
6267
throw new FileNotFoundException("CML file not found: " + f.getName());
6368
}
@@ -87,7 +92,13 @@ protected static List<IReaction> parseRXN(String fileNames) {
8792
continue;
8893
}
8994
String fileName = f[0].trim() + ".rxn";
90-
File filepath = new File(fileName);
95+
File filepath;
96+
try {
97+
filepath = new File(fileName).getCanonicalFile();
98+
} catch (IOException e) {
99+
LOGGER.error(WARNING, format("Invalid file path! %s", fileName));
100+
continue;
101+
}
91102
if (!filepath.isFile()) {
92103
LOGGER.error(WARNING, format("RXN file not found! %s", filepath.getName()));
93104
continue;

src/main/java/com/bioinceptionlabs/aamtool/ReactionDecoder.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,8 @@ private void FormatXMLToFile(Document doc, String fileName) throws TransformerCo
163163

164164
// write xml to file
165165
TransformerFactory transformerFactory = TransformerFactory.newInstance();
166+
transformerFactory.setAttribute(javax.xml.XMLConstants.ACCESS_EXTERNAL_DTD, "");
167+
transformerFactory.setAttribute(javax.xml.XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
166168

167169
Transformer transformer = transformerFactory.newTransformer();
168170
transformer.setOutputProperty(METHOD, "xml");
@@ -258,6 +260,7 @@ private void AAMTask(CommandLine aamLine, Options createAAMOptions,
258260

259261
if (writeFiles && aamLine.getOptionValue("f").equalsIgnoreCase("XML")) {
260262
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
263+
docFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
261264
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
262265
// root element
263266
org.w3c.dom.Document doc = docBuilder.newDocument();
@@ -275,6 +278,7 @@ private void AAMTask(CommandLine aamLine, Options createAAMOptions,
275278
} else if (writeFiles && aamLine.getOptionValue("f").equalsIgnoreCase("BOTH")) {
276279

277280
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
281+
docFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
278282
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
279283
// root element
280284
org.w3c.dom.Document doc = docBuilder.newDocument();

src/main/java/com/bioinceptionlabs/reactionblast/mapping/CallableAtomMappingTool.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,9 +172,9 @@ private void generateAtomAtomMapping(
172172
LOGGER.error(e);
173173
} finally {
174174
executor.shutdown();
175+
LOGGER.debug("!!!!Atom-Atom Mapping Done!!!!");
176+
ThreadSafeCache.getInstance().cleanup();
175177
}
176-
LOGGER.debug("!!!!Atom-Atom Mapping Done!!!!");
177-
ThreadSafeCache.getInstance().cleanup();
178178
}
179179

180180
/**

src/main/java/com/bioinceptionlabs/reactionblast/mapping/GraphMatcher.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,8 +297,18 @@ public static Collection<MCSSolution> matcher(Holder mh) throws Exception {
297297
int numberOfCyclesProduct = productCycleCache.getOrDefault(productIndex, 0);
298298
boolean ringSizeEqual = (numberOfCyclesEduct == numberOfCyclesProduct);
299299

300+
// Clone molecules for thread safety — CDK IAtomContainer is mutable and not thread-safe
301+
IAtomContainer eductClone;
302+
IAtomContainer productClone;
303+
try {
304+
eductClone = educt.clone();
305+
productClone = product.clone();
306+
} catch (CloneNotSupportedException e) {
307+
eductClone = educt;
308+
productClone = product;
309+
}
300310
MCSThread mcsThread = new MCSThread(mh.getTheory(),
301-
substrateIndex, productIndex, educt, product);
311+
substrateIndex, productIndex, eductClone, productClone);
302312
mcsThread.setHasPerfectRings(ringSizeEqual);
303313
mcsThread.setEductRingCount(numberOfCyclesEduct);
304314
mcsThread.setProductRingCount(numberOfCyclesProduct);

src/main/java/com/bioinceptionlabs/reactionblast/mapping/algorithm/GameTheoryEngine.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,7 @@ private int[] getCircularFP(IAtomContainer mol) throws CDKException {
506506
if (cached != null) {
507507
return cached;
508508
}
509-
long[] fp = SMSD.circularFingerprintFCFP(mol, 1, 1024);
509+
long[] fp = SMSD.circularFingerprintFCFP(mol, 1, 256);
510510
BitSet bs = SMSD.toBitSet(fp);
511511
int[] bits = new int[bs.cardinality()];
512512
int idx = 0;

src/main/java/com/bioinceptionlabs/reactionblast/mechanism/BondChangeCalculator.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -454,8 +454,7 @@ public String toString() {
454454
* @throws IOException
455455
*/
456456
public void writeBondChanges(File bondChangeInfoFile) throws IOException {
457-
FileWriter bcFW = new FileWriter(bondChangeInfoFile + ".txt");
458-
try (BufferedWriter bfw = new BufferedWriter(bcFW)) {
457+
try (BufferedWriter bfw = new BufferedWriter(new FileWriter(bondChangeInfoFile + ".txt"))) {
459458
bfw.newLine();
460459

461460
bfw.write(getLicenseHeader());

src/main/java/com/bioinceptionlabs/reactionblast/tools/StandardizeReaction.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ public IReaction filterReagents(IReaction reaction) {
177177
List<long[]> productFPs = new ArrayList<>();
178178
for (IAtomContainer prod : products.atomContainers()) {
179179
try {
180-
productFPs.add(SMSD.circularFingerprintECFP(prod, 2, 1024));
180+
productFPs.add(SMSD.circularFingerprintECFP(prod, 2, 256));
181181
} catch (Exception e) {
182182
productFPs.add(null);
183183
}
@@ -223,7 +223,7 @@ public IReaction filterReagents(IReaction reaction) {
223223

224224
// Check 3: Tanimoto fingerprint similarity
225225
if (!isReagent && !neededForBalance) {
226-
long[] reactantFP = SMSD.circularFingerprintECFP(reactant, 2, 1024);
226+
long[] reactantFP = SMSD.circularFingerprintECFP(reactant, 2, 256);
227227

228228
double maxSim = 0.0;
229229
for (long[] prodFP : productFPs) {

src/main/java/org/openscience/smsd/Isomorphism.java

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -104,28 +104,42 @@ private void mcsBuilder(IAtomContainer mol1, IAtomContainer mol2) throws CDKExce
104104
}
105105

106106
/**
107-
* Delegates MCS computation to SMSD 3.0.0.
107+
* Delegates MCS computation to SMSD.
108+
* First tries a fast substructure check; if that fails, falls back to full MCS.
108109
*/
109110
private void smsdMCSAlgorithm(IAtomContainer mol1, IAtomContainer mol2) throws CDKException {
110111
try {
111-
// First try substructure check
112-
if (mol1.getAtomCount() > 1 && mol2.getAtomCount() > 1) {
113-
Substructure sub;
114-
if (mol1 instanceof IQueryAtomContainer) {
115-
sub = new Substructure((IQueryAtomContainer) mol1, mol2, atomMatcher, bondMatcher, true);
116-
} else {
117-
sub = new Substructure(mol1, mol2, atomMatcher, bondMatcher, true);
118-
}
119-
if (sub.isSubgraph()) {
112+
ChemOptions chemOptions = buildChemOptions();
113+
SMSD smsd = new SMSD(mol1, mol2, chemOptions);
114+
115+
// Fast substructure check before expensive MCS
116+
if (smsd.isSubstructure(5000)) {
117+
java.util.List<Map<Integer, Integer>> subResults = smsd.findAllSubstructures(1, 5000);
118+
if (subResults != null && !subResults.isEmpty()) {
120119
clearMaps();
121-
getMCSList().addAll(sub.getAllAtomMapping());
120+
for (Map<Integer, Integer> mapping : subResults) {
121+
AtomAtomMapping aam = new AtomAtomMapping(mol1, mol2);
122+
for (Map.Entry<Integer, Integer> entry : mapping.entrySet()) {
123+
int qIdx = entry.getKey();
124+
int tIdx = entry.getValue();
125+
if (qIdx >= 0 && qIdx < mol1.getAtomCount()
126+
&& tIdx >= 0 && tIdx < mol2.getAtomCount()) {
127+
IAtom qAtom = mol1.getAtom(qIdx);
128+
IAtom tAtom = mol2.getAtom(tIdx);
129+
if (qAtom != null && tAtom != null) {
130+
aam.put(qAtom, tAtom);
131+
}
132+
}
133+
}
134+
if (!aam.isEmpty()) {
135+
getMCSList().add(aam);
136+
}
137+
}
122138
return;
123139
}
124140
}
125141

126-
// Fall back to MCS via SMSD 3.4.0
127-
ChemOptions chemOptions = new ChemOptions();
128-
SMSD smsd = new SMSD(mol1, mol2, chemOptions);
142+
// Fall back to full MCS
129143
Map<Integer, Integer> mcsResult = smsd.findMCS();
130144

131145
clearMaps();

0 commit comments

Comments
 (0)