Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ public class TrustifyExample {
<li><a href="https://go.dev//">Golang</a> - <a href="https://go.dev/blog/using-go-modules//">Go Modules</a></li>
<li><a href="https://go.dev//">Python</a> - <a href="https://pypi.org/project/pip//">pip Installer</a></li>
<li><a href="https://gradle.org//">Gradle</a> - <a href="https://gradle.org/install//">Gradle Installation</a></li>
<li><a href="https://www.rust-lang.org/">Rust</a> - <a href="https://doc.rust-lang.org/cargo/">Cargo</a></li>

</ul>

Expand Down Expand Up @@ -315,6 +316,19 @@ test {
```
</li>

<li>
<em>Rust Cargo</em> users can add a comment with #trustify-da-ignore next to the package to be ignored in <em>Cargo.toml</em>:

```toml
[dependencies]
serde = "1.0.136" # trustify-da-ignore
tokio = { version = "1.0", features = ["full"] }

[workspace.dependencies]
regex = "1.5.4" # trustify-da-ignore
```
</li>

</ul>

#### Ignore Strategies - experimental
Expand All @@ -337,6 +351,7 @@ System.setProperty("TRUSTIFY_DA_PNPM_PATH", "/path/to/custom/pnpm");
System.setProperty("TRUSTIFY_DA_YARN_PATH", "/path/to/custom/yarn");
System.setProperty("TRUSTIFY_DA_GO_PATH", "/path/to/custom/go");
System.setProperty("TRUSTIFY_DA_GRADLE_PATH", "/path/to/custom/gradle");
System.setProperty("TRUSTIFY_DA_CARGO_PATH", "/path/to/custom/cargo");
//python - python3, pip3 take precedence if python version > 3 installed
System.setProperty("TRUSTIFY_DA_PYTHON3_PATH", "/path/to/python3");
System.setProperty("TRUSTIFY_DA_PIP3_PATH", "/path/to/pip3");
Expand Down Expand Up @@ -452,6 +467,11 @@ following keys for setting custom paths for the said executables.
<td><em>pip</em></td>
<td>TRUSTIFY_DA_PIP_PATH</td>
</tr>
<tr>
<td><a href="https://doc.rust-lang.org/cargo/">Cargo Package Manager</a></td>
<td><em>cargo</em></td>
<td>TRUSTIFY_DA_CARGO_PATH</td>
</tr>

</table>

Expand Down Expand Up @@ -653,6 +673,9 @@ java -jar trustify-da-java-client-cli.jar component /path/to/requirements.txt
# Component analysis with summary
java -jar trustify-da-java-client-cli.jar component /path/to/go.mod --summary

# Rust Cargo analysis
java -jar trustify-da-java-client-cli.jar stack /path/to/Cargo.toml --summary

Comment thread
soul2zimate marked this conversation as resolved.
# Container image analysis with JSON output (default)
java -jar trustify-da-java-client-cli.jar image nginx:latest

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,12 @@
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Logger;
import org.tomlj.Toml;
import org.tomlj.TomlParseResult;
Expand Down Expand Up @@ -341,23 +346,64 @@ private CargoMetadata executeCargoMetadata() throws IOException, InterruptedExce
.directory(workingDir.toFile())
.start();

// Use bounded executor to read streams concurrently to avoid two potential deadlocks:
// 1. buffer deadlock (process blocks on write when output buffers fill up while Java waits for
// process completion)
// 2. stalled process deadlock (readAllBytes() hangs forever when cargo process stalls
// completely with no timeout protection)
// Bounded executor allows proper cancellation and cleanup vs CompletableFuture common pool
ExecutorService streamExecutor = Executors.newFixedThreadPool(2);
String output;
try (InputStream is = process.getInputStream()) {
output = new String(is.readAllBytes(), StandardCharsets.UTF_8);
}
String errorOutput;

try {
Future<String> outputFuture =
streamExecutor.submit(
() -> {
try (InputStream is = process.getInputStream()) {
return new String(is.readAllBytes(), StandardCharsets.UTF_8);
} catch (IOException e) {
log.warning("Failed to read stdout from cargo metadata: " + e.getMessage());
return "";
}
});

Future<String> errorFuture =
streamExecutor.submit(
() -> {
try (InputStream is = process.getErrorStream()) {
return new String(is.readAllBytes(), StandardCharsets.UTF_8);
} catch (IOException e) {
log.warning("Failed to read stderr from cargo metadata: " + e.getMessage());
return "";
}
});

boolean finished = process.waitFor(TIMEOUT, TimeUnit.SECONDS);

if (!finished) {
process.destroyForcibly();
outputFuture.cancel(true);
errorFuture.cancel(true);
throw new InterruptedException("cargo metadata timed out after " + TIMEOUT + " seconds");
}

boolean finished = process.waitFor(TIMEOUT, TimeUnit.SECONDS);
try {
// Short timeout since process already finished
output = outputFuture.get(1, TimeUnit.SECONDS);
errorOutput = errorFuture.get(1, TimeUnit.SECONDS);
} catch (ExecutionException | TimeoutException e) {
log.warning("Failed to read process output: " + e.getMessage());
return null;
}
} finally {
streamExecutor.shutdownNow();
}

// Safe to call exitValue() - we confirmed the process finished
int exitCode = process.exitValue();

if (exitCode != 0) {
String errorOutput = "";
try (InputStream errorStream = process.getErrorStream()) {
errorOutput = new String(errorStream.readAllBytes(), StandardCharsets.UTF_8);
} catch (IOException e) {
log.warning("Failed to read error stream: " + e.getMessage());
}

String errorMessage = "cargo metadata failed with exit code: " + exitCode;
if (!errorOutput.isEmpty()) {
errorMessage += ". Error: " + errorOutput.trim();
Expand All @@ -373,11 +419,6 @@ private CargoMetadata executeCargoMetadata() throws IOException, InterruptedExce
return null;
}

if (!finished) {
process.destroyForcibly();
throw new InterruptedException("cargo metadata timed out after " + TIMEOUT + " seconds");
}

try {
CargoMetadata metadata = MAPPER.readValue(output, CargoMetadata.class);
if (debugLoggingIsNeeded()) {
Expand Down Expand Up @@ -552,12 +593,14 @@ private DependencyInfo getPackageInfo(String packageId, Map<String, CargoPackage
public CargoProvider(Path manifest) {
super(Type.CARGO, manifest);
this.cargoExecutable = Operations.getExecutable("cargo", "--version");
if (cargoExecutable != null) {
log.info("Found cargo executable: " + cargoExecutable);
} else {
log.warning("Cargo executable not found - dependency analysis will not work");
if (debugLoggingIsNeeded()) {
if (cargoExecutable != null) {
log.info("Found cargo executable: " + cargoExecutable);
} else {
log.warning("Cargo executable not found - dependency analysis will not work");
}
log.info("Initialized RustProvider for manifest: " + manifest);
}
log.info("Initialized RustProvider for manifest: " + manifest);
}

@Override
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/cli_help.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ EXAMPLES:
java -jar trustify-da-java-client-cli.jar stack /path/to/package.json --summary
java -jar trustify-da-java-client-cli.jar stack /path/to/build.gradle --html
java -jar trustify-da-java-client-cli.jar component /path/to/requirements.txt
java -jar trustify-da-java-client-cli.jar stack /path/to/Cargo.toml

# Container image analysis
java -jar trustify-da-java-client-cli.jar image nginx:latest
Expand Down
Loading