up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
This commit is contained in:
@@ -1,88 +1,88 @@
|
||||
using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Tests.Core;
|
||||
|
||||
public sealed class LanguageAnalyzerResultTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task MergesDuplicateComponentsDeterministicallyAsync()
|
||||
{
|
||||
var analyzer = new DuplicateComponentAnalyzer();
|
||||
var engine = new LanguageAnalyzerEngine(new[] { analyzer });
|
||||
var root = TestPaths.CreateTemporaryDirectory();
|
||||
try
|
||||
{
|
||||
var context = new LanguageAnalyzerContext(root, TimeProvider.System);
|
||||
var result = await engine.AnalyzeAsync(context, CancellationToken.None);
|
||||
|
||||
var component = Assert.Single(result.Components);
|
||||
Assert.Equal("purl::pkg:example/acme@2.0.0", component.ComponentKey);
|
||||
Assert.Equal("pkg:example/acme@2.0.0", component.Purl);
|
||||
Assert.True(component.UsedByEntrypoint);
|
||||
Assert.Equal(2, component.Evidence.Count);
|
||||
Assert.Equal(3, component.Metadata.Count);
|
||||
|
||||
// Metadata retains stable ordering (sorted by key)
|
||||
var keys = component.Metadata.Keys.ToArray();
|
||||
Assert.Equal(new[] { "artifactId", "groupId", "path" }, keys);
|
||||
|
||||
// Evidence de-duplicates via comparison key
|
||||
Assert.Equal(2, component.Evidence.Count);
|
||||
}
|
||||
finally
|
||||
{
|
||||
TestPaths.SafeDelete(root);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class DuplicateComponentAnalyzer : ILanguageAnalyzer
|
||||
{
|
||||
public string Id => "duplicate";
|
||||
|
||||
public string DisplayName => "Duplicate Analyzer";
|
||||
|
||||
public async ValueTask AnalyzeAsync(LanguageAnalyzerContext context, LanguageComponentWriter writer, CancellationToken cancellationToken)
|
||||
{
|
||||
await Task.Yield();
|
||||
|
||||
var metadataA = new[]
|
||||
{
|
||||
new KeyValuePair<string, string?>("groupId", "example"),
|
||||
new KeyValuePair<string, string?>("artifactId", "acme")
|
||||
};
|
||||
|
||||
var metadataB = new[]
|
||||
{
|
||||
new KeyValuePair<string, string?>("artifactId", "acme"),
|
||||
new KeyValuePair<string, string?>("path", ".")
|
||||
};
|
||||
|
||||
var evidence = new[]
|
||||
{
|
||||
new LanguageComponentEvidence(LanguageEvidenceKind.File, "manifest", "META-INF/MANIFEST.MF", null, null),
|
||||
new LanguageComponentEvidence(LanguageEvidenceKind.Metadata, "pom", "pom.xml", "groupId=example", null)
|
||||
};
|
||||
|
||||
writer.AddFromPurl(
|
||||
analyzerId: Id,
|
||||
purl: "pkg:example/acme@2.0.0",
|
||||
name: "acme",
|
||||
version: "2.0.0",
|
||||
type: "example",
|
||||
metadata: metadataA,
|
||||
evidence: evidence,
|
||||
usedByEntrypoint: true);
|
||||
|
||||
// duplicate insert with different metadata ordering
|
||||
writer.AddFromPurl(
|
||||
analyzerId: Id,
|
||||
purl: "pkg:example/acme@2.0.0",
|
||||
name: "acme",
|
||||
version: "2.0.0",
|
||||
type: "example",
|
||||
metadata: metadataB,
|
||||
evidence: evidence,
|
||||
usedByEntrypoint: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Tests.Core;
|
||||
|
||||
public sealed class LanguageAnalyzerResultTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task MergesDuplicateComponentsDeterministicallyAsync()
|
||||
{
|
||||
var analyzer = new DuplicateComponentAnalyzer();
|
||||
var engine = new LanguageAnalyzerEngine(new[] { analyzer });
|
||||
var root = TestPaths.CreateTemporaryDirectory();
|
||||
try
|
||||
{
|
||||
var context = new LanguageAnalyzerContext(root, TimeProvider.System);
|
||||
var result = await engine.AnalyzeAsync(context, CancellationToken.None);
|
||||
|
||||
var component = Assert.Single(result.Components);
|
||||
Assert.Equal("purl::pkg:example/acme@2.0.0", component.ComponentKey);
|
||||
Assert.Equal("pkg:example/acme@2.0.0", component.Purl);
|
||||
Assert.True(component.UsedByEntrypoint);
|
||||
Assert.Equal(2, component.Evidence.Count);
|
||||
Assert.Equal(3, component.Metadata.Count);
|
||||
|
||||
// Metadata retains stable ordering (sorted by key)
|
||||
var keys = component.Metadata.Keys.ToArray();
|
||||
Assert.Equal(new[] { "artifactId", "groupId", "path" }, keys);
|
||||
|
||||
// Evidence de-duplicates via comparison key
|
||||
Assert.Equal(2, component.Evidence.Count);
|
||||
}
|
||||
finally
|
||||
{
|
||||
TestPaths.SafeDelete(root);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class DuplicateComponentAnalyzer : ILanguageAnalyzer
|
||||
{
|
||||
public string Id => "duplicate";
|
||||
|
||||
public string DisplayName => "Duplicate Analyzer";
|
||||
|
||||
public async ValueTask AnalyzeAsync(LanguageAnalyzerContext context, LanguageComponentWriter writer, CancellationToken cancellationToken)
|
||||
{
|
||||
await Task.Yield();
|
||||
|
||||
var metadataA = new[]
|
||||
{
|
||||
new KeyValuePair<string, string?>("groupId", "example"),
|
||||
new KeyValuePair<string, string?>("artifactId", "acme")
|
||||
};
|
||||
|
||||
var metadataB = new[]
|
||||
{
|
||||
new KeyValuePair<string, string?>("artifactId", "acme"),
|
||||
new KeyValuePair<string, string?>("path", ".")
|
||||
};
|
||||
|
||||
var evidence = new[]
|
||||
{
|
||||
new LanguageComponentEvidence(LanguageEvidenceKind.File, "manifest", "META-INF/MANIFEST.MF", null, null),
|
||||
new LanguageComponentEvidence(LanguageEvidenceKind.Metadata, "pom", "pom.xml", "groupId=example", null)
|
||||
};
|
||||
|
||||
writer.AddFromPurl(
|
||||
analyzerId: Id,
|
||||
purl: "pkg:example/acme@2.0.0",
|
||||
name: "acme",
|
||||
version: "2.0.0",
|
||||
type: "example",
|
||||
metadata: metadataA,
|
||||
evidence: evidence,
|
||||
usedByEntrypoint: true);
|
||||
|
||||
// duplicate insert with different metadata ordering
|
||||
writer.AddFromPurl(
|
||||
analyzerId: Id,
|
||||
purl: "pkg:example/acme@2.0.0",
|
||||
name: "acme",
|
||||
version: "2.0.0",
|
||||
type: "example",
|
||||
metadata: metadataB,
|
||||
evidence: evidence,
|
||||
usedByEntrypoint: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,70 +1,70 @@
|
||||
using StellaOps.Scanner.Core.Contracts;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Tests.Core;
|
||||
|
||||
public sealed class LanguageComponentMapperTests
|
||||
{
|
||||
[Fact]
|
||||
public void ToComponentRecordsProjectsDeterministicComponents()
|
||||
{
|
||||
// Arrange
|
||||
var analyzerId = "node";
|
||||
var records = new[]
|
||||
{
|
||||
LanguageComponentRecord.FromPurl(
|
||||
analyzerId: analyzerId,
|
||||
purl: "pkg:npm/example@1.0.0",
|
||||
name: "example",
|
||||
version: "1.0.0",
|
||||
type: "npm",
|
||||
metadata: new Dictionary<string, string?>()
|
||||
{
|
||||
["path"] = "packages/app",
|
||||
["license"] = "MIT"
|
||||
},
|
||||
evidence: new[]
|
||||
{
|
||||
new LanguageComponentEvidence(LanguageEvidenceKind.File, "package.json", "packages/app/package.json", null, "abc123")
|
||||
},
|
||||
usedByEntrypoint: true),
|
||||
LanguageComponentRecord.FromExplicitKey(
|
||||
analyzerId: analyzerId,
|
||||
componentKey: "bin::sha256:deadbeef",
|
||||
purl: null,
|
||||
name: "app-binary",
|
||||
version: null,
|
||||
type: "binary",
|
||||
metadata: new Dictionary<string, string?>()
|
||||
{
|
||||
["description"] = "Utility binary"
|
||||
},
|
||||
evidence: new[]
|
||||
{
|
||||
new LanguageComponentEvidence(LanguageEvidenceKind.Derived, "entrypoint", "/usr/local/bin/app", "ENTRYPOINT", null)
|
||||
})
|
||||
};
|
||||
|
||||
// Act
|
||||
var layerDigest = LanguageComponentMapper.ComputeLayerDigest(analyzerId);
|
||||
var results = LanguageComponentMapper.ToComponentRecords(analyzerId, records, layerDigest);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, results.Length);
|
||||
Assert.All(results, component => Assert.Equal(layerDigest, component.LayerDigest));
|
||||
|
||||
var first = results[0];
|
||||
Assert.Equal("bin::sha256:deadbeef", first.Identity.Key);
|
||||
Assert.Equal("Utility binary", first.Metadata!.Properties!["stellaops.lang.meta.description"]);
|
||||
Assert.Equal("derived", first.Evidence.Single().Kind);
|
||||
|
||||
var second = results[1];
|
||||
Assert.Equal("pkg:npm/example@1.0.0", second.Identity.Key); // prefix removed
|
||||
Assert.True(second.Usage.UsedByEntrypoint);
|
||||
Assert.Contains("MIT", second.Metadata!.Licenses!);
|
||||
Assert.Equal("packages/app", second.Metadata.Properties!["stellaops.lang.meta.path"]);
|
||||
Assert.Equal("abc123", second.Metadata.Properties!["stellaops.lang.evidence.0.sha256"]);
|
||||
Assert.Equal("file", second.Evidence.Single().Kind);
|
||||
Assert.Equal("packages/app/package.json", second.Evidence.Single().Value);
|
||||
Assert.Equal("package.json", second.Evidence.Single().Source);
|
||||
}
|
||||
}
|
||||
using StellaOps.Scanner.Core.Contracts;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Tests.Core;
|
||||
|
||||
public sealed class LanguageComponentMapperTests
|
||||
{
|
||||
[Fact]
|
||||
public void ToComponentRecordsProjectsDeterministicComponents()
|
||||
{
|
||||
// Arrange
|
||||
var analyzerId = "node";
|
||||
var records = new[]
|
||||
{
|
||||
LanguageComponentRecord.FromPurl(
|
||||
analyzerId: analyzerId,
|
||||
purl: "pkg:npm/example@1.0.0",
|
||||
name: "example",
|
||||
version: "1.0.0",
|
||||
type: "npm",
|
||||
metadata: new Dictionary<string, string?>()
|
||||
{
|
||||
["path"] = "packages/app",
|
||||
["license"] = "MIT"
|
||||
},
|
||||
evidence: new[]
|
||||
{
|
||||
new LanguageComponentEvidence(LanguageEvidenceKind.File, "package.json", "packages/app/package.json", null, "abc123")
|
||||
},
|
||||
usedByEntrypoint: true),
|
||||
LanguageComponentRecord.FromExplicitKey(
|
||||
analyzerId: analyzerId,
|
||||
componentKey: "bin::sha256:deadbeef",
|
||||
purl: null,
|
||||
name: "app-binary",
|
||||
version: null,
|
||||
type: "binary",
|
||||
metadata: new Dictionary<string, string?>()
|
||||
{
|
||||
["description"] = "Utility binary"
|
||||
},
|
||||
evidence: new[]
|
||||
{
|
||||
new LanguageComponentEvidence(LanguageEvidenceKind.Derived, "entrypoint", "/usr/local/bin/app", "ENTRYPOINT", null)
|
||||
})
|
||||
};
|
||||
|
||||
// Act
|
||||
var layerDigest = LanguageComponentMapper.ComputeLayerDigest(analyzerId);
|
||||
var results = LanguageComponentMapper.ToComponentRecords(analyzerId, records, layerDigest);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, results.Length);
|
||||
Assert.All(results, component => Assert.Equal(layerDigest, component.LayerDigest));
|
||||
|
||||
var first = results[0];
|
||||
Assert.Equal("bin::sha256:deadbeef", first.Identity.Key);
|
||||
Assert.Equal("Utility binary", first.Metadata!.Properties!["stellaops.lang.meta.description"]);
|
||||
Assert.Equal("derived", first.Evidence.Single().Kind);
|
||||
|
||||
var second = results[1];
|
||||
Assert.Equal("pkg:npm/example@1.0.0", second.Identity.Key); // prefix removed
|
||||
Assert.True(second.Usage.UsedByEntrypoint);
|
||||
Assert.Contains("MIT", second.Metadata!.Licenses!);
|
||||
Assert.Equal("packages/app", second.Metadata.Properties!["stellaops.lang.meta.path"]);
|
||||
Assert.Equal("abc123", second.Metadata.Properties!["stellaops.lang.evidence.0.sha256"]);
|
||||
Assert.Equal("file", second.Evidence.Single().Kind);
|
||||
Assert.Equal("packages/app/package.json", second.Evidence.Single().Value);
|
||||
Assert.Equal("package.json", second.Evidence.Single().Source);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,102 +1,102 @@
|
||||
using StellaOps.Scanner.Analyzers.Lang;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Tests.Harness;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Tests.Determinism;
|
||||
|
||||
public sealed class LanguageAnalyzerHarnessTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task HarnessProducesDeterministicOutputAsync()
|
||||
{
|
||||
var fixturePath = TestPaths.ResolveFixture("determinism", "basic", "input");
|
||||
var goldenPath = TestPaths.ResolveFixture("determinism", "basic", "expected.json");
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
|
||||
var analyzers = new ILanguageAnalyzer[]
|
||||
{
|
||||
new FakeLanguageAnalyzer(
|
||||
"fake-java",
|
||||
LanguageComponentRecord.FromPurl(
|
||||
analyzerId: "fake-java",
|
||||
purl: "pkg:maven/org.example/example-lib@1.2.3",
|
||||
name: "example-lib",
|
||||
version: "1.2.3",
|
||||
type: "maven",
|
||||
metadata: new Dictionary<string, string?>
|
||||
{
|
||||
["groupId"] = "org.example",
|
||||
["artifactId"] = "example-lib",
|
||||
},
|
||||
evidence: new []
|
||||
{
|
||||
new LanguageComponentEvidence(LanguageEvidenceKind.File, "pom.properties", "META-INF/maven/org.example/example-lib/pom.properties", null, "abc123"),
|
||||
}),
|
||||
LanguageComponentRecord.FromExplicitKey(
|
||||
analyzerId: "fake-java",
|
||||
componentKey: "bin::sha256:deadbeef",
|
||||
purl: null,
|
||||
name: "example-cli",
|
||||
version: null,
|
||||
type: "bin",
|
||||
metadata: new Dictionary<string, string?>
|
||||
{
|
||||
["sha256"] = "deadbeef",
|
||||
},
|
||||
evidence: new []
|
||||
{
|
||||
new LanguageComponentEvidence(LanguageEvidenceKind.File, "binary", "usr/local/bin/example", null, "deadbeef"),
|
||||
})),
|
||||
new FakeLanguageAnalyzer(
|
||||
"fake-node",
|
||||
LanguageComponentRecord.FromPurl(
|
||||
analyzerId: "fake-node",
|
||||
purl: "pkg:npm/example-package@4.5.6",
|
||||
name: "example-package",
|
||||
version: "4.5.6",
|
||||
type: "npm",
|
||||
metadata: new Dictionary<string, string?>
|
||||
{
|
||||
["workspace"] = "packages/example",
|
||||
},
|
||||
evidence: new []
|
||||
{
|
||||
new LanguageComponentEvidence(LanguageEvidenceKind.File, "package.json", "packages/example/package.json", null, null),
|
||||
},
|
||||
usedByEntrypoint: true)),
|
||||
};
|
||||
|
||||
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(fixturePath, goldenPath, analyzers, cancellationToken);
|
||||
|
||||
var first = await LanguageAnalyzerTestHarness.RunToJsonAsync(fixturePath, analyzers, cancellationToken);
|
||||
var second = await LanguageAnalyzerTestHarness.RunToJsonAsync(fixturePath, analyzers, cancellationToken);
|
||||
Assert.Equal(first, second);
|
||||
}
|
||||
|
||||
private sealed class FakeLanguageAnalyzer : ILanguageAnalyzer
|
||||
{
|
||||
private readonly IReadOnlyList<LanguageComponentRecord> _components;
|
||||
|
||||
public FakeLanguageAnalyzer(string id, params LanguageComponentRecord[] components)
|
||||
{
|
||||
Id = id;
|
||||
DisplayName = id;
|
||||
_components = components ?? Array.Empty<LanguageComponentRecord>();
|
||||
}
|
||||
|
||||
public string Id { get; }
|
||||
|
||||
public string DisplayName { get; }
|
||||
|
||||
public async ValueTask AnalyzeAsync(LanguageAnalyzerContext context, LanguageComponentWriter writer, CancellationToken cancellationToken)
|
||||
{
|
||||
await Task.Delay(5, cancellationToken).ConfigureAwait(false); // ensure asynchrony is handled
|
||||
|
||||
// Intentionally add in reverse order to prove determinism.
|
||||
foreach (var component in _components.Reverse())
|
||||
{
|
||||
writer.Add(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
using StellaOps.Scanner.Analyzers.Lang;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Tests.Harness;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Tests.Determinism;
|
||||
|
||||
public sealed class LanguageAnalyzerHarnessTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task HarnessProducesDeterministicOutputAsync()
|
||||
{
|
||||
var fixturePath = TestPaths.ResolveFixture("determinism", "basic", "input");
|
||||
var goldenPath = TestPaths.ResolveFixture("determinism", "basic", "expected.json");
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
|
||||
var analyzers = new ILanguageAnalyzer[]
|
||||
{
|
||||
new FakeLanguageAnalyzer(
|
||||
"fake-java",
|
||||
LanguageComponentRecord.FromPurl(
|
||||
analyzerId: "fake-java",
|
||||
purl: "pkg:maven/org.example/example-lib@1.2.3",
|
||||
name: "example-lib",
|
||||
version: "1.2.3",
|
||||
type: "maven",
|
||||
metadata: new Dictionary<string, string?>
|
||||
{
|
||||
["groupId"] = "org.example",
|
||||
["artifactId"] = "example-lib",
|
||||
},
|
||||
evidence: new []
|
||||
{
|
||||
new LanguageComponentEvidence(LanguageEvidenceKind.File, "pom.properties", "META-INF/maven/org.example/example-lib/pom.properties", null, "abc123"),
|
||||
}),
|
||||
LanguageComponentRecord.FromExplicitKey(
|
||||
analyzerId: "fake-java",
|
||||
componentKey: "bin::sha256:deadbeef",
|
||||
purl: null,
|
||||
name: "example-cli",
|
||||
version: null,
|
||||
type: "bin",
|
||||
metadata: new Dictionary<string, string?>
|
||||
{
|
||||
["sha256"] = "deadbeef",
|
||||
},
|
||||
evidence: new []
|
||||
{
|
||||
new LanguageComponentEvidence(LanguageEvidenceKind.File, "binary", "usr/local/bin/example", null, "deadbeef"),
|
||||
})),
|
||||
new FakeLanguageAnalyzer(
|
||||
"fake-node",
|
||||
LanguageComponentRecord.FromPurl(
|
||||
analyzerId: "fake-node",
|
||||
purl: "pkg:npm/example-package@4.5.6",
|
||||
name: "example-package",
|
||||
version: "4.5.6",
|
||||
type: "npm",
|
||||
metadata: new Dictionary<string, string?>
|
||||
{
|
||||
["workspace"] = "packages/example",
|
||||
},
|
||||
evidence: new []
|
||||
{
|
||||
new LanguageComponentEvidence(LanguageEvidenceKind.File, "package.json", "packages/example/package.json", null, null),
|
||||
},
|
||||
usedByEntrypoint: true)),
|
||||
};
|
||||
|
||||
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(fixturePath, goldenPath, analyzers, cancellationToken);
|
||||
|
||||
var first = await LanguageAnalyzerTestHarness.RunToJsonAsync(fixturePath, analyzers, cancellationToken);
|
||||
var second = await LanguageAnalyzerTestHarness.RunToJsonAsync(fixturePath, analyzers, cancellationToken);
|
||||
Assert.Equal(first, second);
|
||||
}
|
||||
|
||||
private sealed class FakeLanguageAnalyzer : ILanguageAnalyzer
|
||||
{
|
||||
private readonly IReadOnlyList<LanguageComponentRecord> _components;
|
||||
|
||||
public FakeLanguageAnalyzer(string id, params LanguageComponentRecord[] components)
|
||||
{
|
||||
Id = id;
|
||||
DisplayName = id;
|
||||
_components = components ?? Array.Empty<LanguageComponentRecord>();
|
||||
}
|
||||
|
||||
public string Id { get; }
|
||||
|
||||
public string DisplayName { get; }
|
||||
|
||||
public async ValueTask AnalyzeAsync(LanguageAnalyzerContext context, LanguageComponentWriter writer, CancellationToken cancellationToken)
|
||||
{
|
||||
await Task.Delay(5, cancellationToken).ConfigureAwait(false); // ensure asynchrony is handled
|
||||
|
||||
// Intentionally add in reverse order to prove determinism.
|
||||
foreach (var component in _components.Reverse())
|
||||
{
|
||||
writer.Add(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Nodes;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Rust;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Tests.Harness;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Tests.Rust;
|
||||
|
||||
public sealed class RustLanguageAnalyzerTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task SimpleFixtureProducesDeterministicOutputAsync()
|
||||
{
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
var fixturePath = TestPaths.ResolveFixture("lang", "rust", "simple");
|
||||
var goldenPath = Path.Combine(fixturePath, "expected.json");
|
||||
|
||||
var usageHints = new LanguageUsageHints(new[]
|
||||
{
|
||||
Path.Combine(fixturePath, "usr/local/bin/my_app")
|
||||
});
|
||||
|
||||
var analyzers = new ILanguageAnalyzer[]
|
||||
{
|
||||
new RustLanguageAnalyzer()
|
||||
};
|
||||
|
||||
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
|
||||
fixturePath,
|
||||
goldenPath,
|
||||
analyzers,
|
||||
cancellationToken,
|
||||
usageHints);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
using StellaOps.Scanner.Analyzers.Lang.Tests.Harness;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Tests.Rust;
|
||||
|
||||
public sealed class RustLanguageAnalyzerTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task SimpleFixtureProducesDeterministicOutputAsync()
|
||||
{
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
var fixturePath = TestPaths.ResolveFixture("lang", "rust", "simple");
|
||||
var goldenPath = Path.Combine(fixturePath, "expected.json");
|
||||
|
||||
var usageHints = new LanguageUsageHints(new[]
|
||||
{
|
||||
Path.Combine(fixturePath, "usr/local/bin/my_app")
|
||||
});
|
||||
|
||||
var analyzers = new ILanguageAnalyzer[]
|
||||
{
|
||||
new RustLanguageAnalyzer()
|
||||
};
|
||||
|
||||
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
|
||||
fixturePath,
|
||||
goldenPath,
|
||||
analyzers,
|
||||
cancellationToken,
|
||||
usageHints);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AnalyzerIsThreadSafeUnderConcurrencyAsync()
|
||||
{
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
|
||||
@@ -1,428 +1,428 @@
|
||||
using System.Buffers.Binary;
|
||||
using System.Text;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
|
||||
|
||||
public static class JavaClassFileFactory
|
||||
{
|
||||
public static byte[] CreateClassForNameInvoker(string internalClassName, string targetClassName)
|
||||
{
|
||||
using var buffer = new MemoryStream();
|
||||
using var writer = new BigEndianWriter(buffer);
|
||||
|
||||
WriteClassFileHeader(writer, constantPoolCount: 16);
|
||||
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8(internalClassName); // #1
|
||||
writer.WriteByte((byte)ConstantTag.Class); writer.WriteUInt16(1); // #2
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("java/lang/Object"); // #3
|
||||
writer.WriteByte((byte)ConstantTag.Class); writer.WriteUInt16(3); // #4
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("invoke"); // #5
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("()V"); // #6
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("Code"); // #7
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8(targetClassName); // #8
|
||||
writer.WriteByte((byte)ConstantTag.String); writer.WriteUInt16(8); // #9
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("java/lang/Class"); // #10
|
||||
writer.WriteByte((byte)ConstantTag.Class); writer.WriteUInt16(10); // #11
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("forName"); // #12
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("(Ljava/lang/String;)Ljava/lang/Class;"); // #13
|
||||
writer.WriteByte((byte)ConstantTag.NameAndType); writer.WriteUInt16(12); writer.WriteUInt16(13); // #14
|
||||
writer.WriteByte((byte)ConstantTag.Methodref); writer.WriteUInt16(11); writer.WriteUInt16(14); // #15
|
||||
|
||||
writer.WriteUInt16(0x0001); // public
|
||||
writer.WriteUInt16(2); // this class
|
||||
writer.WriteUInt16(4); // super class
|
||||
|
||||
writer.WriteUInt16(0); // interfaces
|
||||
writer.WriteUInt16(0); // fields
|
||||
writer.WriteUInt16(1); // methods
|
||||
|
||||
WriteInvokeMethod(writer, methodNameIndex: 5, descriptorIndex: 6, ldcIndex: 9, methodRefIndex: 15);
|
||||
|
||||
writer.WriteUInt16(0); // class attributes
|
||||
|
||||
return buffer.ToArray();
|
||||
}
|
||||
|
||||
public static byte[] CreateClassResourceLookup(string internalClassName, string resourcePath)
|
||||
{
|
||||
using var buffer = new MemoryStream();
|
||||
using var writer = new BigEndianWriter(buffer);
|
||||
|
||||
WriteClassFileHeader(writer, constantPoolCount: 20);
|
||||
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8(internalClassName); // #1
|
||||
writer.WriteByte((byte)ConstantTag.Class); writer.WriteUInt16(1); // #2
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("java/lang/Object"); // #3
|
||||
writer.WriteByte((byte)ConstantTag.Class); writer.WriteUInt16(3); // #4
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("load"); // #5
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("()V"); // #6
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("Code"); // #7
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8(resourcePath); // #8
|
||||
writer.WriteByte((byte)ConstantTag.String); writer.WriteUInt16(8); // #9
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("java/lang/ClassLoader"); // #10
|
||||
writer.WriteByte((byte)ConstantTag.Class); writer.WriteUInt16(10); // #11
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("getSystemClassLoader"); // #12
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("()Ljava/lang/ClassLoader;"); // #13
|
||||
writer.WriteByte((byte)ConstantTag.NameAndType); writer.WriteUInt16(12); writer.WriteUInt16(13); // #14
|
||||
writer.WriteByte((byte)ConstantTag.Methodref); writer.WriteUInt16(11); writer.WriteUInt16(14); // #15
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("getResource"); // #16
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("(Ljava/lang/String;)Ljava/net/URL;"); // #17
|
||||
writer.WriteByte((byte)ConstantTag.NameAndType); writer.WriteUInt16(16); writer.WriteUInt16(17); // #18
|
||||
writer.WriteByte((byte)ConstantTag.Methodref); writer.WriteUInt16(11); writer.WriteUInt16(18); // #19
|
||||
|
||||
writer.WriteUInt16(0x0001); // public
|
||||
writer.WriteUInt16(2); // this class
|
||||
writer.WriteUInt16(4); // super class
|
||||
|
||||
writer.WriteUInt16(0); // interfaces
|
||||
writer.WriteUInt16(0); // fields
|
||||
writer.WriteUInt16(1); // methods
|
||||
|
||||
WriteResourceLookupMethod(writer, methodNameIndex: 5, descriptorIndex: 6, systemLoaderMethodRefIndex: 15, stringIndex: 9, getResourceMethodRefIndex: 19);
|
||||
|
||||
writer.WriteUInt16(0); // class attributes
|
||||
|
||||
return buffer.ToArray();
|
||||
}
|
||||
|
||||
public static byte[] CreateTcclChecker(string internalClassName)
|
||||
{
|
||||
using var buffer = new MemoryStream();
|
||||
using var writer = new BigEndianWriter(buffer);
|
||||
|
||||
WriteClassFileHeader(writer, constantPoolCount: 18);
|
||||
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8(internalClassName); // #1
|
||||
writer.WriteByte((byte)ConstantTag.Class); writer.WriteUInt16(1); // #2
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("java/lang/Object"); // #3
|
||||
writer.WriteByte((byte)ConstantTag.Class); writer.WriteUInt16(3); // #4
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("check"); // #5
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("()V"); // #6
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("Code"); // #7
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("java/lang/Thread"); // #8
|
||||
writer.WriteByte((byte)ConstantTag.Class); writer.WriteUInt16(8); // #9
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("currentThread"); // #10
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("()Ljava/lang/Thread;"); // #11
|
||||
writer.WriteByte((byte)ConstantTag.NameAndType); writer.WriteUInt16(10); writer.WriteUInt16(11); // #12
|
||||
writer.WriteByte((byte)ConstantTag.Methodref); writer.WriteUInt16(9); writer.WriteUInt16(12); // #13
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("getContextClassLoader"); // #14
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("()Ljava/lang/ClassLoader;"); // #15
|
||||
writer.WriteByte((byte)ConstantTag.NameAndType); writer.WriteUInt16(14); writer.WriteUInt16(15); // #16
|
||||
writer.WriteByte((byte)ConstantTag.Methodref); writer.WriteUInt16(9); writer.WriteUInt16(16); // #17
|
||||
|
||||
writer.WriteUInt16(0x0001); // public
|
||||
writer.WriteUInt16(2); // this
|
||||
writer.WriteUInt16(4); // super
|
||||
|
||||
writer.WriteUInt16(0); // interfaces
|
||||
writer.WriteUInt16(0); // fields
|
||||
writer.WriteUInt16(1); // methods
|
||||
|
||||
WriteTcclMethod(writer, methodNameIndex: 5, descriptorIndex: 6, currentThreadMethodRefIndex: 13, getContextMethodRefIndex: 17);
|
||||
|
||||
writer.WriteUInt16(0); // class attributes
|
||||
|
||||
return buffer.ToArray();
|
||||
}
|
||||
|
||||
private static void WriteClassFileHeader(BigEndianWriter writer, ushort constantPoolCount)
|
||||
{
|
||||
writer.WriteUInt32(0xCAFEBABE);
|
||||
writer.WriteUInt16(0);
|
||||
writer.WriteUInt16(52);
|
||||
writer.WriteUInt16(constantPoolCount);
|
||||
}
|
||||
|
||||
private static void WriteInvokeMethod(BigEndianWriter writer, ushort methodNameIndex, ushort descriptorIndex, ushort ldcIndex, ushort methodRefIndex)
|
||||
{
|
||||
writer.WriteUInt16(0x0009); // public static
|
||||
writer.WriteUInt16(methodNameIndex);
|
||||
writer.WriteUInt16(descriptorIndex);
|
||||
writer.WriteUInt16(1); // attributes_count
|
||||
|
||||
writer.WriteUInt16(7); // "Code"
|
||||
using var codeBuffer = new MemoryStream();
|
||||
using (var codeWriter = new BigEndianWriter(codeBuffer))
|
||||
{
|
||||
codeWriter.WriteUInt16(1); // max_stack
|
||||
codeWriter.WriteUInt16(0); // max_locals
|
||||
codeWriter.WriteUInt32(6); // code_length
|
||||
codeWriter.WriteByte(0x12);
|
||||
codeWriter.WriteByte((byte)ldcIndex);
|
||||
codeWriter.WriteByte(0xB8);
|
||||
codeWriter.WriteUInt16(methodRefIndex);
|
||||
codeWriter.WriteByte(0xB1);
|
||||
codeWriter.WriteUInt16(0); // exception table length
|
||||
codeWriter.WriteUInt16(0); // code attributes
|
||||
}
|
||||
|
||||
var codeBytes = codeBuffer.ToArray();
|
||||
writer.WriteUInt32((uint)codeBytes.Length);
|
||||
writer.WriteBytes(codeBytes);
|
||||
}
|
||||
|
||||
private static void WriteTcclMethod(BigEndianWriter writer, ushort methodNameIndex, ushort descriptorIndex, ushort currentThreadMethodRefIndex, ushort getContextMethodRefIndex)
|
||||
{
|
||||
writer.WriteUInt16(0x0009);
|
||||
writer.WriteUInt16(methodNameIndex);
|
||||
writer.WriteUInt16(descriptorIndex);
|
||||
writer.WriteUInt16(1);
|
||||
|
||||
writer.WriteUInt16(7);
|
||||
using var codeBuffer = new MemoryStream();
|
||||
using (var codeWriter = new BigEndianWriter(codeBuffer))
|
||||
{
|
||||
codeWriter.WriteUInt16(2);
|
||||
codeWriter.WriteUInt16(0);
|
||||
codeWriter.WriteUInt32(8);
|
||||
codeWriter.WriteByte(0xB8);
|
||||
codeWriter.WriteUInt16(currentThreadMethodRefIndex);
|
||||
codeWriter.WriteByte(0xB6);
|
||||
codeWriter.WriteUInt16(getContextMethodRefIndex);
|
||||
codeWriter.WriteByte(0x57);
|
||||
codeWriter.WriteByte(0xB1);
|
||||
codeWriter.WriteUInt16(0);
|
||||
codeWriter.WriteUInt16(0);
|
||||
}
|
||||
|
||||
var codeBytes = codeBuffer.ToArray();
|
||||
writer.WriteUInt32((uint)codeBytes.Length);
|
||||
writer.WriteBytes(codeBytes);
|
||||
}
|
||||
|
||||
private static void WriteResourceLookupMethod(
|
||||
BigEndianWriter writer,
|
||||
ushort methodNameIndex,
|
||||
ushort descriptorIndex,
|
||||
ushort systemLoaderMethodRefIndex,
|
||||
ushort stringIndex,
|
||||
ushort getResourceMethodRefIndex)
|
||||
{
|
||||
writer.WriteUInt16(0x0009);
|
||||
writer.WriteUInt16(methodNameIndex);
|
||||
writer.WriteUInt16(descriptorIndex);
|
||||
writer.WriteUInt16(1);
|
||||
|
||||
writer.WriteUInt16(7);
|
||||
using var codeBuffer = new MemoryStream();
|
||||
using (var codeWriter = new BigEndianWriter(codeBuffer))
|
||||
{
|
||||
codeWriter.WriteUInt16(2);
|
||||
codeWriter.WriteUInt16(0);
|
||||
codeWriter.WriteUInt32(10);
|
||||
codeWriter.WriteByte(0xB8); // invokestatic
|
||||
codeWriter.WriteUInt16(systemLoaderMethodRefIndex);
|
||||
codeWriter.WriteByte(0x12); // ldc
|
||||
codeWriter.WriteByte((byte)stringIndex);
|
||||
codeWriter.WriteByte(0xB6); // invokevirtual
|
||||
codeWriter.WriteUInt16(getResourceMethodRefIndex);
|
||||
codeWriter.WriteByte(0x57);
|
||||
codeWriter.WriteByte(0xB1);
|
||||
codeWriter.WriteUInt16(0);
|
||||
codeWriter.WriteUInt16(0);
|
||||
}
|
||||
|
||||
var codeBytes = codeBuffer.ToArray();
|
||||
writer.WriteUInt32((uint)codeBytes.Length);
|
||||
writer.WriteBytes(codeBytes);
|
||||
}
|
||||
|
||||
private sealed class BigEndianWriter : IDisposable
|
||||
{
|
||||
private readonly BinaryWriter _writer;
|
||||
|
||||
public BigEndianWriter(Stream stream)
|
||||
{
|
||||
_writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true);
|
||||
}
|
||||
|
||||
public void WriteByte(byte value) => _writer.Write(value);
|
||||
|
||||
public void WriteBytes(byte[] data) => _writer.Write(data);
|
||||
|
||||
public void WriteUInt16(ushort value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[2];
|
||||
BinaryPrimitives.WriteUInt16BigEndian(buffer, value);
|
||||
_writer.Write(buffer);
|
||||
}
|
||||
|
||||
public void WriteUInt32(uint value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[4];
|
||||
BinaryPrimitives.WriteUInt32BigEndian(buffer, value);
|
||||
_writer.Write(buffer);
|
||||
}
|
||||
|
||||
public void WriteUtf8(string value)
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes(value);
|
||||
WriteUInt16((ushort)bytes.Length);
|
||||
_writer.Write(bytes);
|
||||
}
|
||||
|
||||
public void Dispose() => _writer.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a class file with a native method declaration.
|
||||
/// </summary>
|
||||
public static byte[] CreateNativeMethodClass(string internalClassName, string nativeMethodName)
|
||||
{
|
||||
using var buffer = new MemoryStream();
|
||||
using var writer = new BigEndianWriter(buffer);
|
||||
|
||||
WriteClassFileHeader(writer, constantPoolCount: 8);
|
||||
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8(internalClassName); // #1
|
||||
writer.WriteByte((byte)ConstantTag.Class); writer.WriteUInt16(1); // #2
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("java/lang/Object"); // #3
|
||||
writer.WriteByte((byte)ConstantTag.Class); writer.WriteUInt16(3); // #4
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8(nativeMethodName); // #5
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("()V"); // #6
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("Code"); // #7
|
||||
|
||||
writer.WriteUInt16(0x0001); // public
|
||||
writer.WriteUInt16(2); // this class
|
||||
writer.WriteUInt16(4); // super class
|
||||
|
||||
writer.WriteUInt16(0); // interfaces
|
||||
writer.WriteUInt16(0); // fields
|
||||
writer.WriteUInt16(1); // methods
|
||||
|
||||
// native method: access_flags = ACC_PUBLIC | ACC_NATIVE (0x0101)
|
||||
writer.WriteUInt16(0x0101);
|
||||
writer.WriteUInt16(5); // name
|
||||
writer.WriteUInt16(6); // descriptor
|
||||
writer.WriteUInt16(0); // no attributes (native methods have no Code)
|
||||
|
||||
writer.WriteUInt16(0); // class attributes
|
||||
|
||||
return buffer.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a class file with a System.loadLibrary call.
|
||||
/// </summary>
|
||||
public static byte[] CreateSystemLoadLibraryInvoker(string internalClassName, string libraryName)
|
||||
{
|
||||
using var buffer = new MemoryStream();
|
||||
using var writer = new BigEndianWriter(buffer);
|
||||
|
||||
WriteClassFileHeader(writer, constantPoolCount: 16);
|
||||
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8(internalClassName); // #1
|
||||
writer.WriteByte((byte)ConstantTag.Class); writer.WriteUInt16(1); // #2
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("java/lang/Object"); // #3
|
||||
writer.WriteByte((byte)ConstantTag.Class); writer.WriteUInt16(3); // #4
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("loadNative"); // #5
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("()V"); // #6
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("Code"); // #7
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8(libraryName); // #8
|
||||
writer.WriteByte((byte)ConstantTag.String); writer.WriteUInt16(8); // #9
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("java/lang/System"); // #10
|
||||
writer.WriteByte((byte)ConstantTag.Class); writer.WriteUInt16(10); // #11
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("loadLibrary"); // #12
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("(Ljava/lang/String;)V"); // #13
|
||||
writer.WriteByte((byte)ConstantTag.NameAndType); writer.WriteUInt16(12); writer.WriteUInt16(13); // #14
|
||||
writer.WriteByte((byte)ConstantTag.Methodref); writer.WriteUInt16(11); writer.WriteUInt16(14); // #15
|
||||
|
||||
writer.WriteUInt16(0x0001); // public
|
||||
writer.WriteUInt16(2); // this class
|
||||
writer.WriteUInt16(4); // super class
|
||||
|
||||
writer.WriteUInt16(0); // interfaces
|
||||
writer.WriteUInt16(0); // fields
|
||||
writer.WriteUInt16(1); // methods
|
||||
|
||||
WriteInvokeStaticMethod(writer, methodNameIndex: 5, descriptorIndex: 6, ldcIndex: 9, methodRefIndex: 15);
|
||||
|
||||
writer.WriteUInt16(0); // class attributes
|
||||
|
||||
return buffer.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a class file with a System.load call (loads by path).
|
||||
/// </summary>
|
||||
public static byte[] CreateSystemLoadInvoker(string internalClassName, string libraryPath)
|
||||
{
|
||||
using var buffer = new MemoryStream();
|
||||
using var writer = new BigEndianWriter(buffer);
|
||||
|
||||
WriteClassFileHeader(writer, constantPoolCount: 16);
|
||||
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8(internalClassName); // #1
|
||||
writer.WriteByte((byte)ConstantTag.Class); writer.WriteUInt16(1); // #2
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("java/lang/Object"); // #3
|
||||
writer.WriteByte((byte)ConstantTag.Class); writer.WriteUInt16(3); // #4
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("loadNative"); // #5
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("()V"); // #6
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("Code"); // #7
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8(libraryPath); // #8
|
||||
writer.WriteByte((byte)ConstantTag.String); writer.WriteUInt16(8); // #9
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("java/lang/System"); // #10
|
||||
writer.WriteByte((byte)ConstantTag.Class); writer.WriteUInt16(10); // #11
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("load"); // #12
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("(Ljava/lang/String;)V"); // #13
|
||||
writer.WriteByte((byte)ConstantTag.NameAndType); writer.WriteUInt16(12); writer.WriteUInt16(13); // #14
|
||||
writer.WriteByte((byte)ConstantTag.Methodref); writer.WriteUInt16(11); writer.WriteUInt16(14); // #15
|
||||
|
||||
writer.WriteUInt16(0x0001); // public
|
||||
writer.WriteUInt16(2); // this class
|
||||
writer.WriteUInt16(4); // super class
|
||||
|
||||
writer.WriteUInt16(0); // interfaces
|
||||
writer.WriteUInt16(0); // fields
|
||||
writer.WriteUInt16(1); // methods
|
||||
|
||||
WriteInvokeStaticMethod(writer, methodNameIndex: 5, descriptorIndex: 6, ldcIndex: 9, methodRefIndex: 15);
|
||||
|
||||
writer.WriteUInt16(0); // class attributes
|
||||
|
||||
return buffer.ToArray();
|
||||
}
|
||||
|
||||
private static void WriteInvokeStaticMethod(BigEndianWriter writer, ushort methodNameIndex, ushort descriptorIndex, ushort ldcIndex, ushort methodRefIndex)
|
||||
{
|
||||
writer.WriteUInt16(0x0009); // public static
|
||||
writer.WriteUInt16(methodNameIndex);
|
||||
writer.WriteUInt16(descriptorIndex);
|
||||
writer.WriteUInt16(1); // attributes_count
|
||||
|
||||
writer.WriteUInt16(7); // "Code"
|
||||
using var codeBuffer = new MemoryStream();
|
||||
using (var codeWriter = new BigEndianWriter(codeBuffer))
|
||||
{
|
||||
codeWriter.WriteUInt16(1); // max_stack
|
||||
codeWriter.WriteUInt16(0); // max_locals
|
||||
codeWriter.WriteUInt32(6); // code_length
|
||||
codeWriter.WriteByte(0x12); // ldc
|
||||
codeWriter.WriteByte((byte)ldcIndex);
|
||||
codeWriter.WriteByte(0xB8); // invokestatic
|
||||
codeWriter.WriteUInt16(methodRefIndex);
|
||||
codeWriter.WriteByte(0xB1); // return
|
||||
codeWriter.WriteUInt16(0); // exception table length
|
||||
codeWriter.WriteUInt16(0); // code attributes
|
||||
}
|
||||
|
||||
var codeBytes = codeBuffer.ToArray();
|
||||
writer.WriteUInt32((uint)codeBytes.Length);
|
||||
writer.WriteBytes(codeBytes);
|
||||
}
|
||||
|
||||
private enum ConstantTag : byte
|
||||
{
|
||||
Utf8 = 1,
|
||||
Integer = 3,
|
||||
Float = 4,
|
||||
Long = 5,
|
||||
Double = 6,
|
||||
Class = 7,
|
||||
String = 8,
|
||||
Fieldref = 9,
|
||||
Methodref = 10,
|
||||
InterfaceMethodref = 11,
|
||||
NameAndType = 12,
|
||||
}
|
||||
}
|
||||
using System.Buffers.Binary;
|
||||
using System.Text;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
|
||||
|
||||
public static class JavaClassFileFactory
|
||||
{
|
||||
public static byte[] CreateClassForNameInvoker(string internalClassName, string targetClassName)
|
||||
{
|
||||
using var buffer = new MemoryStream();
|
||||
using var writer = new BigEndianWriter(buffer);
|
||||
|
||||
WriteClassFileHeader(writer, constantPoolCount: 16);
|
||||
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8(internalClassName); // #1
|
||||
writer.WriteByte((byte)ConstantTag.Class); writer.WriteUInt16(1); // #2
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("java/lang/Object"); // #3
|
||||
writer.WriteByte((byte)ConstantTag.Class); writer.WriteUInt16(3); // #4
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("invoke"); // #5
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("()V"); // #6
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("Code"); // #7
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8(targetClassName); // #8
|
||||
writer.WriteByte((byte)ConstantTag.String); writer.WriteUInt16(8); // #9
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("java/lang/Class"); // #10
|
||||
writer.WriteByte((byte)ConstantTag.Class); writer.WriteUInt16(10); // #11
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("forName"); // #12
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("(Ljava/lang/String;)Ljava/lang/Class;"); // #13
|
||||
writer.WriteByte((byte)ConstantTag.NameAndType); writer.WriteUInt16(12); writer.WriteUInt16(13); // #14
|
||||
writer.WriteByte((byte)ConstantTag.Methodref); writer.WriteUInt16(11); writer.WriteUInt16(14); // #15
|
||||
|
||||
writer.WriteUInt16(0x0001); // public
|
||||
writer.WriteUInt16(2); // this class
|
||||
writer.WriteUInt16(4); // super class
|
||||
|
||||
writer.WriteUInt16(0); // interfaces
|
||||
writer.WriteUInt16(0); // fields
|
||||
writer.WriteUInt16(1); // methods
|
||||
|
||||
WriteInvokeMethod(writer, methodNameIndex: 5, descriptorIndex: 6, ldcIndex: 9, methodRefIndex: 15);
|
||||
|
||||
writer.WriteUInt16(0); // class attributes
|
||||
|
||||
return buffer.ToArray();
|
||||
}
|
||||
|
||||
public static byte[] CreateClassResourceLookup(string internalClassName, string resourcePath)
|
||||
{
|
||||
using var buffer = new MemoryStream();
|
||||
using var writer = new BigEndianWriter(buffer);
|
||||
|
||||
WriteClassFileHeader(writer, constantPoolCount: 20);
|
||||
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8(internalClassName); // #1
|
||||
writer.WriteByte((byte)ConstantTag.Class); writer.WriteUInt16(1); // #2
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("java/lang/Object"); // #3
|
||||
writer.WriteByte((byte)ConstantTag.Class); writer.WriteUInt16(3); // #4
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("load"); // #5
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("()V"); // #6
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("Code"); // #7
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8(resourcePath); // #8
|
||||
writer.WriteByte((byte)ConstantTag.String); writer.WriteUInt16(8); // #9
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("java/lang/ClassLoader"); // #10
|
||||
writer.WriteByte((byte)ConstantTag.Class); writer.WriteUInt16(10); // #11
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("getSystemClassLoader"); // #12
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("()Ljava/lang/ClassLoader;"); // #13
|
||||
writer.WriteByte((byte)ConstantTag.NameAndType); writer.WriteUInt16(12); writer.WriteUInt16(13); // #14
|
||||
writer.WriteByte((byte)ConstantTag.Methodref); writer.WriteUInt16(11); writer.WriteUInt16(14); // #15
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("getResource"); // #16
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("(Ljava/lang/String;)Ljava/net/URL;"); // #17
|
||||
writer.WriteByte((byte)ConstantTag.NameAndType); writer.WriteUInt16(16); writer.WriteUInt16(17); // #18
|
||||
writer.WriteByte((byte)ConstantTag.Methodref); writer.WriteUInt16(11); writer.WriteUInt16(18); // #19
|
||||
|
||||
writer.WriteUInt16(0x0001); // public
|
||||
writer.WriteUInt16(2); // this class
|
||||
writer.WriteUInt16(4); // super class
|
||||
|
||||
writer.WriteUInt16(0); // interfaces
|
||||
writer.WriteUInt16(0); // fields
|
||||
writer.WriteUInt16(1); // methods
|
||||
|
||||
WriteResourceLookupMethod(writer, methodNameIndex: 5, descriptorIndex: 6, systemLoaderMethodRefIndex: 15, stringIndex: 9, getResourceMethodRefIndex: 19);
|
||||
|
||||
writer.WriteUInt16(0); // class attributes
|
||||
|
||||
return buffer.ToArray();
|
||||
}
|
||||
|
||||
public static byte[] CreateTcclChecker(string internalClassName)
|
||||
{
|
||||
using var buffer = new MemoryStream();
|
||||
using var writer = new BigEndianWriter(buffer);
|
||||
|
||||
WriteClassFileHeader(writer, constantPoolCount: 18);
|
||||
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8(internalClassName); // #1
|
||||
writer.WriteByte((byte)ConstantTag.Class); writer.WriteUInt16(1); // #2
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("java/lang/Object"); // #3
|
||||
writer.WriteByte((byte)ConstantTag.Class); writer.WriteUInt16(3); // #4
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("check"); // #5
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("()V"); // #6
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("Code"); // #7
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("java/lang/Thread"); // #8
|
||||
writer.WriteByte((byte)ConstantTag.Class); writer.WriteUInt16(8); // #9
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("currentThread"); // #10
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("()Ljava/lang/Thread;"); // #11
|
||||
writer.WriteByte((byte)ConstantTag.NameAndType); writer.WriteUInt16(10); writer.WriteUInt16(11); // #12
|
||||
writer.WriteByte((byte)ConstantTag.Methodref); writer.WriteUInt16(9); writer.WriteUInt16(12); // #13
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("getContextClassLoader"); // #14
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("()Ljava/lang/ClassLoader;"); // #15
|
||||
writer.WriteByte((byte)ConstantTag.NameAndType); writer.WriteUInt16(14); writer.WriteUInt16(15); // #16
|
||||
writer.WriteByte((byte)ConstantTag.Methodref); writer.WriteUInt16(9); writer.WriteUInt16(16); // #17
|
||||
|
||||
writer.WriteUInt16(0x0001); // public
|
||||
writer.WriteUInt16(2); // this
|
||||
writer.WriteUInt16(4); // super
|
||||
|
||||
writer.WriteUInt16(0); // interfaces
|
||||
writer.WriteUInt16(0); // fields
|
||||
writer.WriteUInt16(1); // methods
|
||||
|
||||
WriteTcclMethod(writer, methodNameIndex: 5, descriptorIndex: 6, currentThreadMethodRefIndex: 13, getContextMethodRefIndex: 17);
|
||||
|
||||
writer.WriteUInt16(0); // class attributes
|
||||
|
||||
return buffer.ToArray();
|
||||
}
|
||||
|
||||
private static void WriteClassFileHeader(BigEndianWriter writer, ushort constantPoolCount)
|
||||
{
|
||||
writer.WriteUInt32(0xCAFEBABE);
|
||||
writer.WriteUInt16(0);
|
||||
writer.WriteUInt16(52);
|
||||
writer.WriteUInt16(constantPoolCount);
|
||||
}
|
||||
|
||||
private static void WriteInvokeMethod(BigEndianWriter writer, ushort methodNameIndex, ushort descriptorIndex, ushort ldcIndex, ushort methodRefIndex)
|
||||
{
|
||||
writer.WriteUInt16(0x0009); // public static
|
||||
writer.WriteUInt16(methodNameIndex);
|
||||
writer.WriteUInt16(descriptorIndex);
|
||||
writer.WriteUInt16(1); // attributes_count
|
||||
|
||||
writer.WriteUInt16(7); // "Code"
|
||||
using var codeBuffer = new MemoryStream();
|
||||
using (var codeWriter = new BigEndianWriter(codeBuffer))
|
||||
{
|
||||
codeWriter.WriteUInt16(1); // max_stack
|
||||
codeWriter.WriteUInt16(0); // max_locals
|
||||
codeWriter.WriteUInt32(6); // code_length
|
||||
codeWriter.WriteByte(0x12);
|
||||
codeWriter.WriteByte((byte)ldcIndex);
|
||||
codeWriter.WriteByte(0xB8);
|
||||
codeWriter.WriteUInt16(methodRefIndex);
|
||||
codeWriter.WriteByte(0xB1);
|
||||
codeWriter.WriteUInt16(0); // exception table length
|
||||
codeWriter.WriteUInt16(0); // code attributes
|
||||
}
|
||||
|
||||
var codeBytes = codeBuffer.ToArray();
|
||||
writer.WriteUInt32((uint)codeBytes.Length);
|
||||
writer.WriteBytes(codeBytes);
|
||||
}
|
||||
|
||||
private static void WriteTcclMethod(BigEndianWriter writer, ushort methodNameIndex, ushort descriptorIndex, ushort currentThreadMethodRefIndex, ushort getContextMethodRefIndex)
|
||||
{
|
||||
writer.WriteUInt16(0x0009);
|
||||
writer.WriteUInt16(methodNameIndex);
|
||||
writer.WriteUInt16(descriptorIndex);
|
||||
writer.WriteUInt16(1);
|
||||
|
||||
writer.WriteUInt16(7);
|
||||
using var codeBuffer = new MemoryStream();
|
||||
using (var codeWriter = new BigEndianWriter(codeBuffer))
|
||||
{
|
||||
codeWriter.WriteUInt16(2);
|
||||
codeWriter.WriteUInt16(0);
|
||||
codeWriter.WriteUInt32(8);
|
||||
codeWriter.WriteByte(0xB8);
|
||||
codeWriter.WriteUInt16(currentThreadMethodRefIndex);
|
||||
codeWriter.WriteByte(0xB6);
|
||||
codeWriter.WriteUInt16(getContextMethodRefIndex);
|
||||
codeWriter.WriteByte(0x57);
|
||||
codeWriter.WriteByte(0xB1);
|
||||
codeWriter.WriteUInt16(0);
|
||||
codeWriter.WriteUInt16(0);
|
||||
}
|
||||
|
||||
var codeBytes = codeBuffer.ToArray();
|
||||
writer.WriteUInt32((uint)codeBytes.Length);
|
||||
writer.WriteBytes(codeBytes);
|
||||
}
|
||||
|
||||
private static void WriteResourceLookupMethod(
|
||||
BigEndianWriter writer,
|
||||
ushort methodNameIndex,
|
||||
ushort descriptorIndex,
|
||||
ushort systemLoaderMethodRefIndex,
|
||||
ushort stringIndex,
|
||||
ushort getResourceMethodRefIndex)
|
||||
{
|
||||
writer.WriteUInt16(0x0009);
|
||||
writer.WriteUInt16(methodNameIndex);
|
||||
writer.WriteUInt16(descriptorIndex);
|
||||
writer.WriteUInt16(1);
|
||||
|
||||
writer.WriteUInt16(7);
|
||||
using var codeBuffer = new MemoryStream();
|
||||
using (var codeWriter = new BigEndianWriter(codeBuffer))
|
||||
{
|
||||
codeWriter.WriteUInt16(2);
|
||||
codeWriter.WriteUInt16(0);
|
||||
codeWriter.WriteUInt32(10);
|
||||
codeWriter.WriteByte(0xB8); // invokestatic
|
||||
codeWriter.WriteUInt16(systemLoaderMethodRefIndex);
|
||||
codeWriter.WriteByte(0x12); // ldc
|
||||
codeWriter.WriteByte((byte)stringIndex);
|
||||
codeWriter.WriteByte(0xB6); // invokevirtual
|
||||
codeWriter.WriteUInt16(getResourceMethodRefIndex);
|
||||
codeWriter.WriteByte(0x57);
|
||||
codeWriter.WriteByte(0xB1);
|
||||
codeWriter.WriteUInt16(0);
|
||||
codeWriter.WriteUInt16(0);
|
||||
}
|
||||
|
||||
var codeBytes = codeBuffer.ToArray();
|
||||
writer.WriteUInt32((uint)codeBytes.Length);
|
||||
writer.WriteBytes(codeBytes);
|
||||
}
|
||||
|
||||
private sealed class BigEndianWriter : IDisposable
|
||||
{
|
||||
private readonly BinaryWriter _writer;
|
||||
|
||||
public BigEndianWriter(Stream stream)
|
||||
{
|
||||
_writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true);
|
||||
}
|
||||
|
||||
public void WriteByte(byte value) => _writer.Write(value);
|
||||
|
||||
public void WriteBytes(byte[] data) => _writer.Write(data);
|
||||
|
||||
public void WriteUInt16(ushort value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[2];
|
||||
BinaryPrimitives.WriteUInt16BigEndian(buffer, value);
|
||||
_writer.Write(buffer);
|
||||
}
|
||||
|
||||
public void WriteUInt32(uint value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[4];
|
||||
BinaryPrimitives.WriteUInt32BigEndian(buffer, value);
|
||||
_writer.Write(buffer);
|
||||
}
|
||||
|
||||
public void WriteUtf8(string value)
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes(value);
|
||||
WriteUInt16((ushort)bytes.Length);
|
||||
_writer.Write(bytes);
|
||||
}
|
||||
|
||||
public void Dispose() => _writer.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a class file with a native method declaration.
|
||||
/// </summary>
|
||||
public static byte[] CreateNativeMethodClass(string internalClassName, string nativeMethodName)
|
||||
{
|
||||
using var buffer = new MemoryStream();
|
||||
using var writer = new BigEndianWriter(buffer);
|
||||
|
||||
WriteClassFileHeader(writer, constantPoolCount: 8);
|
||||
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8(internalClassName); // #1
|
||||
writer.WriteByte((byte)ConstantTag.Class); writer.WriteUInt16(1); // #2
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("java/lang/Object"); // #3
|
||||
writer.WriteByte((byte)ConstantTag.Class); writer.WriteUInt16(3); // #4
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8(nativeMethodName); // #5
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("()V"); // #6
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("Code"); // #7
|
||||
|
||||
writer.WriteUInt16(0x0001); // public
|
||||
writer.WriteUInt16(2); // this class
|
||||
writer.WriteUInt16(4); // super class
|
||||
|
||||
writer.WriteUInt16(0); // interfaces
|
||||
writer.WriteUInt16(0); // fields
|
||||
writer.WriteUInt16(1); // methods
|
||||
|
||||
// native method: access_flags = ACC_PUBLIC | ACC_NATIVE (0x0101)
|
||||
writer.WriteUInt16(0x0101);
|
||||
writer.WriteUInt16(5); // name
|
||||
writer.WriteUInt16(6); // descriptor
|
||||
writer.WriteUInt16(0); // no attributes (native methods have no Code)
|
||||
|
||||
writer.WriteUInt16(0); // class attributes
|
||||
|
||||
return buffer.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a class file with a System.loadLibrary call.
|
||||
/// </summary>
|
||||
public static byte[] CreateSystemLoadLibraryInvoker(string internalClassName, string libraryName)
|
||||
{
|
||||
using var buffer = new MemoryStream();
|
||||
using var writer = new BigEndianWriter(buffer);
|
||||
|
||||
WriteClassFileHeader(writer, constantPoolCount: 16);
|
||||
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8(internalClassName); // #1
|
||||
writer.WriteByte((byte)ConstantTag.Class); writer.WriteUInt16(1); // #2
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("java/lang/Object"); // #3
|
||||
writer.WriteByte((byte)ConstantTag.Class); writer.WriteUInt16(3); // #4
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("loadNative"); // #5
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("()V"); // #6
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("Code"); // #7
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8(libraryName); // #8
|
||||
writer.WriteByte((byte)ConstantTag.String); writer.WriteUInt16(8); // #9
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("java/lang/System"); // #10
|
||||
writer.WriteByte((byte)ConstantTag.Class); writer.WriteUInt16(10); // #11
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("loadLibrary"); // #12
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("(Ljava/lang/String;)V"); // #13
|
||||
writer.WriteByte((byte)ConstantTag.NameAndType); writer.WriteUInt16(12); writer.WriteUInt16(13); // #14
|
||||
writer.WriteByte((byte)ConstantTag.Methodref); writer.WriteUInt16(11); writer.WriteUInt16(14); // #15
|
||||
|
||||
writer.WriteUInt16(0x0001); // public
|
||||
writer.WriteUInt16(2); // this class
|
||||
writer.WriteUInt16(4); // super class
|
||||
|
||||
writer.WriteUInt16(0); // interfaces
|
||||
writer.WriteUInt16(0); // fields
|
||||
writer.WriteUInt16(1); // methods
|
||||
|
||||
WriteInvokeStaticMethod(writer, methodNameIndex: 5, descriptorIndex: 6, ldcIndex: 9, methodRefIndex: 15);
|
||||
|
||||
writer.WriteUInt16(0); // class attributes
|
||||
|
||||
return buffer.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a class file with a System.load call (loads by path).
|
||||
/// </summary>
|
||||
public static byte[] CreateSystemLoadInvoker(string internalClassName, string libraryPath)
|
||||
{
|
||||
using var buffer = new MemoryStream();
|
||||
using var writer = new BigEndianWriter(buffer);
|
||||
|
||||
WriteClassFileHeader(writer, constantPoolCount: 16);
|
||||
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8(internalClassName); // #1
|
||||
writer.WriteByte((byte)ConstantTag.Class); writer.WriteUInt16(1); // #2
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("java/lang/Object"); // #3
|
||||
writer.WriteByte((byte)ConstantTag.Class); writer.WriteUInt16(3); // #4
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("loadNative"); // #5
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("()V"); // #6
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("Code"); // #7
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8(libraryPath); // #8
|
||||
writer.WriteByte((byte)ConstantTag.String); writer.WriteUInt16(8); // #9
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("java/lang/System"); // #10
|
||||
writer.WriteByte((byte)ConstantTag.Class); writer.WriteUInt16(10); // #11
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("load"); // #12
|
||||
writer.WriteByte((byte)ConstantTag.Utf8); writer.WriteUtf8("(Ljava/lang/String;)V"); // #13
|
||||
writer.WriteByte((byte)ConstantTag.NameAndType); writer.WriteUInt16(12); writer.WriteUInt16(13); // #14
|
||||
writer.WriteByte((byte)ConstantTag.Methodref); writer.WriteUInt16(11); writer.WriteUInt16(14); // #15
|
||||
|
||||
writer.WriteUInt16(0x0001); // public
|
||||
writer.WriteUInt16(2); // this class
|
||||
writer.WriteUInt16(4); // super class
|
||||
|
||||
writer.WriteUInt16(0); // interfaces
|
||||
writer.WriteUInt16(0); // fields
|
||||
writer.WriteUInt16(1); // methods
|
||||
|
||||
WriteInvokeStaticMethod(writer, methodNameIndex: 5, descriptorIndex: 6, ldcIndex: 9, methodRefIndex: 15);
|
||||
|
||||
writer.WriteUInt16(0); // class attributes
|
||||
|
||||
return buffer.ToArray();
|
||||
}
|
||||
|
||||
private static void WriteInvokeStaticMethod(BigEndianWriter writer, ushort methodNameIndex, ushort descriptorIndex, ushort ldcIndex, ushort methodRefIndex)
|
||||
{
|
||||
writer.WriteUInt16(0x0009); // public static
|
||||
writer.WriteUInt16(methodNameIndex);
|
||||
writer.WriteUInt16(descriptorIndex);
|
||||
writer.WriteUInt16(1); // attributes_count
|
||||
|
||||
writer.WriteUInt16(7); // "Code"
|
||||
using var codeBuffer = new MemoryStream();
|
||||
using (var codeWriter = new BigEndianWriter(codeBuffer))
|
||||
{
|
||||
codeWriter.WriteUInt16(1); // max_stack
|
||||
codeWriter.WriteUInt16(0); // max_locals
|
||||
codeWriter.WriteUInt32(6); // code_length
|
||||
codeWriter.WriteByte(0x12); // ldc
|
||||
codeWriter.WriteByte((byte)ldcIndex);
|
||||
codeWriter.WriteByte(0xB8); // invokestatic
|
||||
codeWriter.WriteUInt16(methodRefIndex);
|
||||
codeWriter.WriteByte(0xB1); // return
|
||||
codeWriter.WriteUInt16(0); // exception table length
|
||||
codeWriter.WriteUInt16(0); // code attributes
|
||||
}
|
||||
|
||||
var codeBytes = codeBuffer.ToArray();
|
||||
writer.WriteUInt32((uint)codeBytes.Length);
|
||||
writer.WriteBytes(codeBytes);
|
||||
}
|
||||
|
||||
private enum ConstantTag : byte
|
||||
{
|
||||
Utf8 = 1,
|
||||
Integer = 3,
|
||||
Float = 4,
|
||||
Long = 5,
|
||||
Double = 6,
|
||||
Class = 7,
|
||||
String = 8,
|
||||
Fieldref = 9,
|
||||
Methodref = 10,
|
||||
InterfaceMethodref = 11,
|
||||
NameAndType = 12,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using System.IO.Compression;
|
||||
using System.Text;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
|
||||
|
||||
using System.IO.Compression;
|
||||
using System.Text;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
|
||||
|
||||
public static class JavaFixtureBuilder
|
||||
{
|
||||
private static readonly DateTimeOffset DefaultTimestamp = new(2024, 01, 01, 0, 0, 0, TimeSpan.Zero);
|
||||
|
||||
@@ -1,40 +1,40 @@
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
|
||||
|
||||
public static class TestPaths
|
||||
{
|
||||
public static string ResolveFixture(params string[] segments)
|
||||
{
|
||||
var baseDirectory = AppContext.BaseDirectory;
|
||||
var parts = new List<string> { baseDirectory };
|
||||
parts.AddRange(new[] { "Fixtures" });
|
||||
parts.AddRange(segments);
|
||||
return Path.GetFullPath(Path.Combine(parts.ToArray()));
|
||||
}
|
||||
|
||||
public static string CreateTemporaryDirectory()
|
||||
{
|
||||
var root = Path.Combine(AppContext.BaseDirectory, "tmp", Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(root);
|
||||
return root;
|
||||
}
|
||||
|
||||
public static void SafeDelete(string directory)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(directory) || !Directory.Exists(directory))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Directory.Delete(directory, recursive: true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Swallow cleanup exceptions to avoid masking test failures.
|
||||
}
|
||||
}
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
|
||||
|
||||
public static class TestPaths
|
||||
{
|
||||
public static string ResolveFixture(params string[] segments)
|
||||
{
|
||||
var baseDirectory = AppContext.BaseDirectory;
|
||||
var parts = new List<string> { baseDirectory };
|
||||
parts.AddRange(new[] { "Fixtures" });
|
||||
parts.AddRange(segments);
|
||||
return Path.GetFullPath(Path.Combine(parts.ToArray()));
|
||||
}
|
||||
|
||||
public static string CreateTemporaryDirectory()
|
||||
{
|
||||
var root = Path.Combine(AppContext.BaseDirectory, "tmp", Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(root);
|
||||
return root;
|
||||
}
|
||||
|
||||
public static void SafeDelete(string directory)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(directory) || !Directory.Exists(directory))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Directory.Delete(directory, recursive: true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Swallow cleanup exceptions to avoid masking test failures.
|
||||
}
|
||||
}
|
||||
|
||||
public static string ResolveProjectRoot()
|
||||
{
|
||||
var directory = AppContext.BaseDirectory;
|
||||
@@ -47,8 +47,8 @@ public static class TestPaths
|
||||
}
|
||||
|
||||
directory = Path.GetDirectoryName(directory) ?? string.Empty;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Unable to locate project root.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Unable to locate project root.");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user