From 07b399d7b6958a0ebd2e11ea0d3feaf4afe6725d Mon Sep 17 00:00:00 2001 From: Aleksandr Misonizhnik Date: Sun, 19 Jul 2026 23:55:25 +0300 Subject: [PATCH 1/6] test: pin negative-clause anchoring behavior for argument-position events Negative pattern clauses (pattern-not and pattern-not-inside) whose excluded event uses the tracked metavariable only in call-argument position never exclude anything, while the identical event matches when required positively and receiver-position counterparts exclude correctly. Static vs instance calls, callee body contents, pattern-not forms, and context containment are all ruled out as causes. DocNegativeShapesTest pins the current behavior: samples named PositiveNotYetExcluded assert the buggy outcome, so the suite is green today and fails exactly when the defect is fixed. Full analysis and reproduction in issues/negative-clause-argument-position.md. --- .../java/example/AllowlistNotInsideDoc.java | 35 ++++++++++++ .../main/java/example/ArgEventSanityDoc.java | 37 +++++++++++++ .../java/example/ArgFullPatternNotDoc.java | 36 ++++++++++++ .../java/example/ArgNotInsideAnchoredDoc.java | 39 +++++++++++++ .../main/java/example/ArgNotInsideDoc.java | 37 +++++++++++++ .../example/ArgObserverPatternNotDoc.java | 34 ++++++++++++ .../java/example/InstanceArgNotInsideDoc.java | 38 +++++++++++++ .../java/example/NegOnlyNotInsideDoc.java | 36 ++++++++++++ .../example/ReceiverNotInsideSpanDoc.java | 36 ++++++++++++ .../ReceiverSanitizePatternNotDoc.java | 36 ++++++++++++ .../java/example/SanitizeEventSanityDoc.java | 35 ++++++++++++ .../java/example/SanitizeNotInsideDoc.java | 36 ++++++++++++ .../java/example/SanitizePatternNotDoc.java | 35 ++++++++++++ .../example/AllowlistNotInsideDoc.yaml | 14 +++++ .../resources/example/ArgEventSanityDoc.yaml | 13 +++++ .../example/ArgFullPatternNotDoc.yaml | 17 ++++++ .../example/ArgNotInsideAnchoredDoc.yaml | 14 +++++ .../resources/example/ArgNotInsideDoc.yaml | 14 +++++ .../example/ArgObserverPatternNotDoc.yaml | 16 ++++++ .../example/InstanceArgNotInsideDoc.yaml | 14 +++++ .../example/NegOnlyNotInsideDoc.yaml | 11 ++++ .../example/ReceiverNotInsideSpanDoc.yaml | 14 +++++ .../ReceiverSanitizePatternNotDoc.yaml | 17 ++++++ .../example/SanitizeEventSanityDoc.yaml | 13 +++++ .../example/SanitizeNotInsideDoc.yaml | 14 +++++ .../example/SanitizePatternNotDoc.yaml | 17 ++++++ .../semgrep/DocNegativeShapesTest.kt | 55 +++++++++++++++++++ 27 files changed, 713 insertions(+) create mode 100644 core/opentaint-java-querylang/samples/src/main/java/example/AllowlistNotInsideDoc.java create mode 100644 core/opentaint-java-querylang/samples/src/main/java/example/ArgEventSanityDoc.java create mode 100644 core/opentaint-java-querylang/samples/src/main/java/example/ArgFullPatternNotDoc.java create mode 100644 core/opentaint-java-querylang/samples/src/main/java/example/ArgNotInsideAnchoredDoc.java create mode 100644 core/opentaint-java-querylang/samples/src/main/java/example/ArgNotInsideDoc.java create mode 100644 core/opentaint-java-querylang/samples/src/main/java/example/ArgObserverPatternNotDoc.java create mode 100644 core/opentaint-java-querylang/samples/src/main/java/example/InstanceArgNotInsideDoc.java create mode 100644 core/opentaint-java-querylang/samples/src/main/java/example/NegOnlyNotInsideDoc.java create mode 100644 core/opentaint-java-querylang/samples/src/main/java/example/ReceiverNotInsideSpanDoc.java create mode 100644 core/opentaint-java-querylang/samples/src/main/java/example/ReceiverSanitizePatternNotDoc.java create mode 100644 core/opentaint-java-querylang/samples/src/main/java/example/SanitizeEventSanityDoc.java create mode 100644 core/opentaint-java-querylang/samples/src/main/java/example/SanitizeNotInsideDoc.java create mode 100644 core/opentaint-java-querylang/samples/src/main/java/example/SanitizePatternNotDoc.java create mode 100644 core/opentaint-java-querylang/samples/src/main/resources/example/AllowlistNotInsideDoc.yaml create mode 100644 core/opentaint-java-querylang/samples/src/main/resources/example/ArgEventSanityDoc.yaml create mode 100644 core/opentaint-java-querylang/samples/src/main/resources/example/ArgFullPatternNotDoc.yaml create mode 100644 core/opentaint-java-querylang/samples/src/main/resources/example/ArgNotInsideAnchoredDoc.yaml create mode 100644 core/opentaint-java-querylang/samples/src/main/resources/example/ArgNotInsideDoc.yaml create mode 100644 core/opentaint-java-querylang/samples/src/main/resources/example/ArgObserverPatternNotDoc.yaml create mode 100644 core/opentaint-java-querylang/samples/src/main/resources/example/InstanceArgNotInsideDoc.yaml create mode 100644 core/opentaint-java-querylang/samples/src/main/resources/example/NegOnlyNotInsideDoc.yaml create mode 100644 core/opentaint-java-querylang/samples/src/main/resources/example/ReceiverNotInsideSpanDoc.yaml create mode 100644 core/opentaint-java-querylang/samples/src/main/resources/example/ReceiverSanitizePatternNotDoc.yaml create mode 100644 core/opentaint-java-querylang/samples/src/main/resources/example/SanitizeEventSanityDoc.yaml create mode 100644 core/opentaint-java-querylang/samples/src/main/resources/example/SanitizeNotInsideDoc.yaml create mode 100644 core/opentaint-java-querylang/samples/src/main/resources/example/SanitizePatternNotDoc.yaml create mode 100644 core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/DocNegativeShapesTest.kt diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/AllowlistNotInsideDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/AllowlistNotInsideDoc.java new file mode 100644 index 000000000..46a50dc41 --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/java/example/AllowlistNotInsideDoc.java @@ -0,0 +1,35 @@ +package example; + +import base.RuleSample; +import base.RuleSet; + +/** + * Doc validation: pattern-not-inside excludes when the positively produced + * client is the receiver of the safe-configuration call. + */ +@RuleSet("example/AllowlistNotInsideDoc.yaml") +public abstract class AllowlistNotInsideDoc implements RuleSample { + + static class Client { + static Client builder() { return new Client(); } + void allowHost(String host) {} + void connect(String url) {} + } + + static class Positive extends AllowlistNotInsideDoc { + @Override + public void entrypoint() { + Client c = Client.builder(); + c.connect("http://example.com"); + } + } + + static class Negative extends AllowlistNotInsideDoc { + @Override + public void entrypoint() { + Client c = Client.builder(); + c.allowHost("trusted.example"); + c.connect("http://example.com"); + } + } +} diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/ArgEventSanityDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/ArgEventSanityDoc.java new file mode 100644 index 000000000..e3bbe8f8d --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/java/example/ArgEventSanityDoc.java @@ -0,0 +1,37 @@ +package example; + +import base.RuleSample; +import base.RuleSet; + +/** + * Doc validation sanity probe: the same argument-position check event used in + * the negative probes, but required POSITIVELY. If this matches, the event + * exists as an automaton step and the negative failures are specific to + * negative clauses. + */ +@RuleSet("example/ArgEventSanityDoc.yaml") +public abstract class ArgEventSanityDoc implements RuleSample { + + static Object decode(Object o) { return o; } + static int checksum; + + static void check(Object o) { checksum += o.hashCode(); } + static void consume(Object o) {} + + static class Positive extends ArgEventSanityDoc { + @Override + public void entrypoint() { + Object r = decode("x"); + check(r); + consume(r); + } + } + + static class Negative extends ArgEventSanityDoc { + @Override + public void entrypoint() { + Object r = decode("x"); + consume(r); + } + } +} diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/ArgFullPatternNotDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/ArgFullPatternNotDoc.java new file mode 100644 index 000000000..7fc6b7080 --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/java/example/ArgFullPatternNotDoc.java @@ -0,0 +1,36 @@ +package example; + +import base.RuleSample; +import base.RuleSet; + +/** + * Doc validation: full-form pattern-not whose added event uses the produced + * value in argument position does not anchor the exclusion — while the + * identical event matches positively (see ArgEventSanityDoc). + */ +@RuleSet("example/ArgFullPatternNotDoc.yaml") +public abstract class ArgFullPatternNotDoc implements RuleSample { + + static Object decode(Object o) { return o; } + static int checksum; + + static void check(Object o) { checksum += o.hashCode(); } + static void consume(Object o) {} + + static class Positive extends ArgFullPatternNotDoc { + @Override + public void entrypoint() { + Object r = decode("x"); + consume(r); + } + } + + static class PositiveNotYetExcluded extends ArgFullPatternNotDoc { + @Override + public void entrypoint() { + Object r = decode("x"); + check(r); + consume(r); + } + } +} diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/ArgNotInsideAnchoredDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/ArgNotInsideAnchoredDoc.java new file mode 100644 index 000000000..3a2a262f7 --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/java/example/ArgNotInsideAnchoredDoc.java @@ -0,0 +1,39 @@ +package example; + +import base.RuleSample; +import base.RuleSet; + +/** + * Doc validation: argument-position pattern-not-inside does not anchor even + * with a satisfiable containment — the producer lives in pattern-inside and + * the main pattern is the single consume event, so the excluded context can + * enclose the match, yet the exclusion still has no effect. Contrast with + * ReceiverNotInsideSpanDoc, where a receiver-position exclusion works even + * when the context cannot contain the producing event. + */ +@RuleSet("example/ArgNotInsideAnchoredDoc.yaml") +public abstract class ArgNotInsideAnchoredDoc implements RuleSample { + + static Object decode(Object o) { return o; } + static int checksum; + + static void check(Object o) { checksum += o.hashCode(); } + static void consume(Object o) {} + + static class Positive extends ArgNotInsideAnchoredDoc { + @Override + public void entrypoint() { + Object r = decode("x"); + consume(r); + } + } + + static class PositiveNotYetExcluded extends ArgNotInsideAnchoredDoc { + @Override + public void entrypoint() { + Object r = decode("x"); + check(r); + consume(r); + } + } +} diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/ArgNotInsideDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/ArgNotInsideDoc.java new file mode 100644 index 000000000..b7edd9dab --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/java/example/ArgNotInsideDoc.java @@ -0,0 +1,37 @@ +package example; + +import base.RuleSample; +import base.RuleSet; + +/** + * Doc validation: pattern-not-inside whose excluded event uses the + * positively produced value in argument position does not anchor the + * exclusion — while the identical event matches positively (see + * ArgEventSanityDoc). + */ +@RuleSet("example/ArgNotInsideDoc.yaml") +public abstract class ArgNotInsideDoc implements RuleSample { + + static Object decode(Object o) { return o; } + static int checksum; + + static void check(Object o) { checksum += o.hashCode(); } + static void consume(Object o) {} + + static class Positive extends ArgNotInsideDoc { + @Override + public void entrypoint() { + Object r = decode("x"); + consume(r); + } + } + + static class PositiveNotYetExcluded extends ArgNotInsideDoc { + @Override + public void entrypoint() { + Object r = decode("x"); + check(r); + consume(r); + } + } +} diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/ArgObserverPatternNotDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/ArgObserverPatternNotDoc.java new file mode 100644 index 000000000..45f64fc0c --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/java/example/ArgObserverPatternNotDoc.java @@ -0,0 +1,34 @@ +package example; + +import base.RuleSample; +import base.RuleSet; + +/** + * Doc validation: documents the current argument-position restriction: an + * excluded event that uses the produced value only as a call argument does + * not yet anchor the exclusion. + */ +@RuleSet("example/ArgObserverPatternNotDoc.yaml") +public abstract class ArgObserverPatternNotDoc implements RuleSample { + + static Object decode(Object o) { return o; } + static void check(Object o) {} + static void consume(Object o) {} + + static class Positive extends ArgObserverPatternNotDoc { + @Override + public void entrypoint() { + Object r = decode("x"); + consume(r); + } + } + + static class PositiveNotYetExcluded extends ArgObserverPatternNotDoc { + @Override + public void entrypoint() { + Object r = decode("x"); + check(r); + consume(r); + } + } +} diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/InstanceArgNotInsideDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/InstanceArgNotInsideDoc.java new file mode 100644 index 000000000..3c72af8ef --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/java/example/InstanceArgNotInsideDoc.java @@ -0,0 +1,38 @@ +package example; + +import base.RuleSample; +import base.RuleSet; + +/** + * Doc validation: pattern-not-inside whose excluded event is an instance + * call taking the produced value as an argument (anonymous receiver) does + * not anchor the exclusion; the receiver-position counterparts do. + */ +@RuleSet("example/InstanceArgNotInsideDoc.yaml") +public abstract class InstanceArgNotInsideDoc implements RuleSample { + + static class Aux { + int state; + void verify(Object o) { state += o.hashCode(); } + } + + static Object decode(Object o) { return o; } + static void consume(Object o) {} + + static class Positive extends InstanceArgNotInsideDoc { + @Override + public void entrypoint() { + Object r = decode("x"); + consume(r); + } + } + + static class PositiveNotYetExcluded extends InstanceArgNotInsideDoc { + @Override + public void entrypoint() { + Object r = decode("x"); + new Aux().verify(r); + consume(r); + } + } +} diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/NegOnlyNotInsideDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/NegOnlyNotInsideDoc.java new file mode 100644 index 000000000..417cf99a8 --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/java/example/NegOnlyNotInsideDoc.java @@ -0,0 +1,36 @@ +package example; + +import base.RuleSample; +import base.RuleSet; + +/** + * Doc validation: documents that a pattern-not-inside whose metavariables + * exist only in the negative clause is silently ineffective. + */ +@RuleSet("example/NegOnlyNotInsideDoc.yaml") +public abstract class NegOnlyNotInsideDoc implements RuleSample { + + static class Client { + static Client builder() { return new Client(); } + void configure(String mode) {} + void connect(String url) {} + } + + static class Positive extends NegOnlyNotInsideDoc { + @Override + public void entrypoint() { + Client c = Client.builder(); + c.connect("http://example.com"); + } + } + + static class PositiveNotYetExcluded extends NegOnlyNotInsideDoc { + @Override + public void entrypoint() { + Client f = Client.builder(); + f.configure("mode"); + Client c = Client.builder(); + c.connect("http://example.com"); + } + } +} diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/ReceiverNotInsideSpanDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/ReceiverNotInsideSpanDoc.java new file mode 100644 index 000000000..c0b4f3848 --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/java/example/ReceiverNotInsideSpanDoc.java @@ -0,0 +1,36 @@ +package example; + +import base.RuleSample; +import base.RuleSet; + +/** + * Doc validation: receiver-position pattern-not-inside against a multi-event + * main pattern — does the excluded context need to contain the whole span or + * only the accepting event? + */ +@RuleSet("example/ReceiverNotInsideSpanDoc.yaml") +public abstract class ReceiverNotInsideSpanDoc implements RuleSample { + + static class Client { + static Client builder() { return new Client(); } + void allowHost(String host) {} + void connect(String url) {} + } + + static class Positive extends ReceiverNotInsideSpanDoc { + @Override + public void entrypoint() { + Client c = Client.builder(); + c.connect("http://example.com"); + } + } + + static class Negative extends ReceiverNotInsideSpanDoc { + @Override + public void entrypoint() { + Client c = Client.builder(); + c.allowHost("trusted.example"); + c.connect("http://example.com"); + } + } +} diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/ReceiverSanitizePatternNotDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/ReceiverSanitizePatternNotDoc.java new file mode 100644 index 000000000..1595e459e --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/java/example/ReceiverSanitizePatternNotDoc.java @@ -0,0 +1,36 @@ +package example; + +import base.RuleSample; +import base.RuleSet; + +/** + * Doc validation: multi-event pattern-not where the excluded on-path event is + * a receiver call on the produced value. + */ +@RuleSet("example/ReceiverSanitizePatternNotDoc.yaml") +public abstract class ReceiverSanitizePatternNotDoc implements RuleSample { + + static class Value { + Value sanitized() { return this; } + } + + static Value decode(Object o) { return new Value(); } + static void consume(Value v) {} + + static class Positive extends ReceiverSanitizePatternNotDoc { + @Override + public void entrypoint() { + Value r = decode("x"); + consume(r); + } + } + + static class Negative extends ReceiverSanitizePatternNotDoc { + @Override + public void entrypoint() { + Value r = decode("x"); + r = r.sanitized(); + consume(r); + } + } +} diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/SanitizeEventSanityDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/SanitizeEventSanityDoc.java new file mode 100644 index 000000000..066732757 --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/java/example/SanitizeEventSanityDoc.java @@ -0,0 +1,35 @@ +package example; + +import base.RuleSample; +import base.RuleSet; + +/** + * Doc validation sanity probe: the self-sanitizing reassignment event + * required POSITIVELY. If this matches, the event exists as an automaton + * step and the negative failures for the same event are specific to + * negative clauses. + */ +@RuleSet("example/SanitizeEventSanityDoc.yaml") +public abstract class SanitizeEventSanityDoc implements RuleSample { + + static Object decode(Object o) { return o; } + static Object sanitize(Object o) { return o; } + static void consume(Object o) {} + + static class Positive extends SanitizeEventSanityDoc { + @Override + public void entrypoint() { + Object r = decode("x"); + r = sanitize(r); + consume(r); + } + } + + static class Negative extends SanitizeEventSanityDoc { + @Override + public void entrypoint() { + Object r = decode("x"); + consume(r); + } + } +} diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/SanitizeNotInsideDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/SanitizeNotInsideDoc.java new file mode 100644 index 000000000..0db21a17b --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/java/example/SanitizeNotInsideDoc.java @@ -0,0 +1,36 @@ +package example; + +import base.RuleSample; +import base.RuleSet; + +/** + * Doc validation: pattern-not-inside whose excluded event is the + * self-sanitizing reassignment does not anchor the exclusion — the tracked + * value sits in argument position of the static call. The identical event + * matches positively (SanitizeEventSanityDoc), and the receiver-position + * counterpart excludes (ReceiverSanitizePatternNotDoc). + */ +@RuleSet("example/SanitizeNotInsideDoc.yaml") +public abstract class SanitizeNotInsideDoc implements RuleSample { + + static Object decode(Object o) { return o; } + static Object sanitize(Object o) { return o; } + static void consume(Object o) {} + + static class Positive extends SanitizeNotInsideDoc { + @Override + public void entrypoint() { + Object r = decode("x"); + consume(r); + } + } + + static class PositiveNotYetExcluded extends SanitizeNotInsideDoc { + @Override + public void entrypoint() { + Object r = decode("x"); + r = sanitize(r); + consume(r); + } + } +} diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/SanitizePatternNotDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/SanitizePatternNotDoc.java new file mode 100644 index 000000000..2d053efac --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/java/example/SanitizePatternNotDoc.java @@ -0,0 +1,35 @@ +package example; + +import base.RuleSample; +import base.RuleSet; + +/** + * Doc validation: documents the current argument-position restriction: a + * pattern-not event that uses the tracked value only as a call argument + * (static-call sanitize) does not yet anchor the exclusion. See + * ReceiverSanitizePatternNotDoc for the working receiver-shaped form. + */ +@RuleSet("example/SanitizePatternNotDoc.yaml") +public abstract class SanitizePatternNotDoc implements RuleSample { + + static Object decode(Object o) { return o; } + static Object sanitize(Object o) { return o; } + static void consume(Object o) {} + + static class Positive extends SanitizePatternNotDoc { + @Override + public void entrypoint() { + Object r = decode("x"); + consume(r); + } + } + + static class PositiveNotYetExcluded extends SanitizePatternNotDoc { + @Override + public void entrypoint() { + Object r = decode("x"); + r = sanitize(r); + consume(r); + } + } +} diff --git a/core/opentaint-java-querylang/samples/src/main/resources/example/AllowlistNotInsideDoc.yaml b/core/opentaint-java-querylang/samples/src/main/resources/example/AllowlistNotInsideDoc.yaml new file mode 100644 index 000000000..c170d3ec2 --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/resources/example/AllowlistNotInsideDoc.yaml @@ -0,0 +1,14 @@ +rules: + - id: example-AllowlistNotInsideDoc + languages: + - java + severity: ERROR + message: match example/AllowlistNotInsideDoc + patterns: + - pattern-inside: | + $CLIENT = Client.builder(); + ... + - pattern: $CLIENT.connect($URL) + - pattern-not-inside: | + $CLIENT.allowHost("trusted.example"); + ... diff --git a/core/opentaint-java-querylang/samples/src/main/resources/example/ArgEventSanityDoc.yaml b/core/opentaint-java-querylang/samples/src/main/resources/example/ArgEventSanityDoc.yaml new file mode 100644 index 000000000..8675a7a7f --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/resources/example/ArgEventSanityDoc.yaml @@ -0,0 +1,13 @@ +rules: + - id: example-ArgEventSanityDoc + languages: + - java + severity: ERROR + message: match example/ArgEventSanityDoc + patterns: + - pattern: | + $RESULT = decode($INPUT); + ... + check($RESULT); + ... + consume($RESULT); diff --git a/core/opentaint-java-querylang/samples/src/main/resources/example/ArgFullPatternNotDoc.yaml b/core/opentaint-java-querylang/samples/src/main/resources/example/ArgFullPatternNotDoc.yaml new file mode 100644 index 000000000..c159ada67 --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/resources/example/ArgFullPatternNotDoc.yaml @@ -0,0 +1,17 @@ +rules: + - id: example-ArgFullPatternNotDoc + languages: + - java + severity: ERROR + message: match example/ArgFullPatternNotDoc + patterns: + - pattern: | + $RESULT = decode($INPUT); + ... + consume($RESULT); + - pattern-not: | + $RESULT = decode($INPUT); + ... + check($RESULT); + ... + consume($RESULT); diff --git a/core/opentaint-java-querylang/samples/src/main/resources/example/ArgNotInsideAnchoredDoc.yaml b/core/opentaint-java-querylang/samples/src/main/resources/example/ArgNotInsideAnchoredDoc.yaml new file mode 100644 index 000000000..4bd099f2f --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/resources/example/ArgNotInsideAnchoredDoc.yaml @@ -0,0 +1,14 @@ +rules: + - id: example-ArgNotInsideAnchoredDoc + languages: + - java + severity: ERROR + message: match example/ArgNotInsideAnchoredDoc + patterns: + - pattern-inside: | + $RESULT = decode($INPUT); + ... + - pattern: consume($RESULT) + - pattern-not-inside: | + check($RESULT); + ... diff --git a/core/opentaint-java-querylang/samples/src/main/resources/example/ArgNotInsideDoc.yaml b/core/opentaint-java-querylang/samples/src/main/resources/example/ArgNotInsideDoc.yaml new file mode 100644 index 000000000..8207316a6 --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/resources/example/ArgNotInsideDoc.yaml @@ -0,0 +1,14 @@ +rules: + - id: example-ArgNotInsideDoc + languages: + - java + severity: ERROR + message: match example/ArgNotInsideDoc + patterns: + - pattern: | + $RESULT = decode($INPUT); + ... + consume($RESULT); + - pattern-not-inside: | + check($RESULT); + ... diff --git a/core/opentaint-java-querylang/samples/src/main/resources/example/ArgObserverPatternNotDoc.yaml b/core/opentaint-java-querylang/samples/src/main/resources/example/ArgObserverPatternNotDoc.yaml new file mode 100644 index 000000000..3142c391a --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/resources/example/ArgObserverPatternNotDoc.yaml @@ -0,0 +1,16 @@ +rules: + - id: example-ArgObserverPatternNotDoc + languages: + - java + severity: ERROR + message: match example/ArgObserverPatternNotDoc + patterns: + - pattern: | + $RESULT = decode($INPUT); + ... + consume($RESULT); + - pattern-not: | + ... + check($RESULT); + ... + consume($RESULT); diff --git a/core/opentaint-java-querylang/samples/src/main/resources/example/InstanceArgNotInsideDoc.yaml b/core/opentaint-java-querylang/samples/src/main/resources/example/InstanceArgNotInsideDoc.yaml new file mode 100644 index 000000000..904d11582 --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/resources/example/InstanceArgNotInsideDoc.yaml @@ -0,0 +1,14 @@ +rules: + - id: example-InstanceArgNotInsideDoc + languages: + - java + severity: ERROR + message: match example/InstanceArgNotInsideDoc + patterns: + - pattern: | + $RESULT = decode($INPUT); + ... + consume($RESULT); + - pattern-not-inside: | + $_.verify($RESULT); + ... diff --git a/core/opentaint-java-querylang/samples/src/main/resources/example/NegOnlyNotInsideDoc.yaml b/core/opentaint-java-querylang/samples/src/main/resources/example/NegOnlyNotInsideDoc.yaml new file mode 100644 index 000000000..ae3b00ad5 --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/resources/example/NegOnlyNotInsideDoc.yaml @@ -0,0 +1,11 @@ +rules: + - id: example-NegOnlyNotInsideDoc + languages: + - java + severity: ERROR + message: match example/NegOnlyNotInsideDoc + patterns: + - pattern: $CLIENT.connect($URL) + - pattern-not-inside: | + $FACTORY.configure($MODE); + ... diff --git a/core/opentaint-java-querylang/samples/src/main/resources/example/ReceiverNotInsideSpanDoc.yaml b/core/opentaint-java-querylang/samples/src/main/resources/example/ReceiverNotInsideSpanDoc.yaml new file mode 100644 index 000000000..d82450598 --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/resources/example/ReceiverNotInsideSpanDoc.yaml @@ -0,0 +1,14 @@ +rules: + - id: example-ReceiverNotInsideSpanDoc + languages: + - java + severity: ERROR + message: match example/ReceiverNotInsideSpanDoc + patterns: + - pattern: | + $CLIENT = Client.builder(); + ... + $CLIENT.connect($URL); + - pattern-not-inside: | + $CLIENT.allowHost("trusted.example"); + ... diff --git a/core/opentaint-java-querylang/samples/src/main/resources/example/ReceiverSanitizePatternNotDoc.yaml b/core/opentaint-java-querylang/samples/src/main/resources/example/ReceiverSanitizePatternNotDoc.yaml new file mode 100644 index 000000000..9c3547595 --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/resources/example/ReceiverSanitizePatternNotDoc.yaml @@ -0,0 +1,17 @@ +rules: + - id: example-ReceiverSanitizePatternNotDoc + languages: + - java + severity: ERROR + message: match example/ReceiverSanitizePatternNotDoc + patterns: + - pattern: | + $RESULT = decode($INPUT); + ... + consume($RESULT); + - pattern-not: | + $RESULT = decode($INPUT); + ... + $RESULT = $RESULT.sanitized(); + ... + consume($RESULT); diff --git a/core/opentaint-java-querylang/samples/src/main/resources/example/SanitizeEventSanityDoc.yaml b/core/opentaint-java-querylang/samples/src/main/resources/example/SanitizeEventSanityDoc.yaml new file mode 100644 index 000000000..b48272f83 --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/resources/example/SanitizeEventSanityDoc.yaml @@ -0,0 +1,13 @@ +rules: + - id: example-SanitizeEventSanityDoc + languages: + - java + severity: ERROR + message: match example/SanitizeEventSanityDoc + patterns: + - pattern: | + $RESULT = decode($INPUT); + ... + $RESULT = sanitize($RESULT); + ... + consume($RESULT); diff --git a/core/opentaint-java-querylang/samples/src/main/resources/example/SanitizeNotInsideDoc.yaml b/core/opentaint-java-querylang/samples/src/main/resources/example/SanitizeNotInsideDoc.yaml new file mode 100644 index 000000000..6a53783bd --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/resources/example/SanitizeNotInsideDoc.yaml @@ -0,0 +1,14 @@ +rules: + - id: example-SanitizeNotInsideDoc + languages: + - java + severity: ERROR + message: match example/SanitizeNotInsideDoc + patterns: + - pattern: | + $RESULT = decode($INPUT); + ... + consume($RESULT); + - pattern-not-inside: | + $RESULT = sanitize($RESULT); + ... diff --git a/core/opentaint-java-querylang/samples/src/main/resources/example/SanitizePatternNotDoc.yaml b/core/opentaint-java-querylang/samples/src/main/resources/example/SanitizePatternNotDoc.yaml new file mode 100644 index 000000000..b21982541 --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/resources/example/SanitizePatternNotDoc.yaml @@ -0,0 +1,17 @@ +rules: + - id: example-SanitizePatternNotDoc + languages: + - java + severity: ERROR + message: match example/SanitizePatternNotDoc + patterns: + - pattern: | + $RESULT = decode($INPUT); + ... + consume($RESULT); + - pattern-not: | + $RESULT = decode($INPUT); + ... + $RESULT = sanitize($RESULT); + ... + consume($RESULT); diff --git a/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/DocNegativeShapesTest.kt b/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/DocNegativeShapesTest.kt new file mode 100644 index 000000000..d4711fb89 --- /dev/null +++ b/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/DocNegativeShapesTest.kt @@ -0,0 +1,55 @@ +package org.opentaint.semgrep + +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS +import org.opentaint.semgrep.util.SampleBasedTest +import kotlin.test.Test + +@TestInstance(PER_CLASS) +class DocNegativeShapesTest : SampleBasedTest() { + + @Test + fun `test sanitize pattern-not`() = runTest() + + @Test + fun `test argument observer pattern-not`() = runTest() + + @Test + fun `test allowlist pattern-not-inside`() = runTest() + + @Test + fun `test negative-only metavariables`() = runTest() + + @Test + fun `test receiver sanitize pattern-not`() = runTest() + + @Test + fun `test argument full-form pattern-not`() = runTest() + + @Test + fun `test argument pattern-not-inside`() = runTest() + + @Test + fun `test argument event matches positively`() = runTest() + + @Test + fun `test instance argument pattern-not-inside`() = runTest() + + @Test + fun `test sanitize reassignment event matches positively`() = runTest() + + @Test + fun `test sanitize reassignment pattern-not-inside`() = runTest() + + @Test + fun `test argument not-inside with satisfiable containment`() = runTest() + + @Test + fun `test receiver not-inside against multi-event main pattern`() = runTest() + + @AfterAll + fun close() { + closeRunner() + } +} From 702afe6305c82e2d260f50f3cfacc35ae250705d Mon Sep 17 00:00:00 2001 From: Aleksandr Misonizhnik Date: Mon, 20 Jul 2026 00:04:51 +0300 Subject: [PATCH 2/6] test: use anchored pattern-not-inside shapes in argument-position probes Drop the probe whose pattern-not-inside lacked a pattern-inside producer (dismissible on shape grounds) and reshape the instance-argument and sanitize-reassignment probes to the anchored form: producer declared in pattern-inside, single-event main pattern, containment satisfiable. The argument-position exclusions remain ineffective in the anchored shapes, so the finding stands on clean rules; issue tables and repro updated to match. --- .../main/java/example/ArgNotInsideDoc.java | 37 ------------------- .../java/example/InstanceArgNotInsideDoc.java | 5 ++- .../java/example/SanitizeNotInsideDoc.java | 9 +++-- .../resources/example/ArgNotInsideDoc.yaml | 14 ------- .../example/InstanceArgNotInsideDoc.yaml | 4 +- .../example/SanitizeNotInsideDoc.yaml | 4 +- .../semgrep/DocNegativeShapesTest.kt | 3 -- 7 files changed, 12 insertions(+), 64 deletions(-) delete mode 100644 core/opentaint-java-querylang/samples/src/main/java/example/ArgNotInsideDoc.java delete mode 100644 core/opentaint-java-querylang/samples/src/main/resources/example/ArgNotInsideDoc.yaml diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/ArgNotInsideDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/ArgNotInsideDoc.java deleted file mode 100644 index b7edd9dab..000000000 --- a/core/opentaint-java-querylang/samples/src/main/java/example/ArgNotInsideDoc.java +++ /dev/null @@ -1,37 +0,0 @@ -package example; - -import base.RuleSample; -import base.RuleSet; - -/** - * Doc validation: pattern-not-inside whose excluded event uses the - * positively produced value in argument position does not anchor the - * exclusion — while the identical event matches positively (see - * ArgEventSanityDoc). - */ -@RuleSet("example/ArgNotInsideDoc.yaml") -public abstract class ArgNotInsideDoc implements RuleSample { - - static Object decode(Object o) { return o; } - static int checksum; - - static void check(Object o) { checksum += o.hashCode(); } - static void consume(Object o) {} - - static class Positive extends ArgNotInsideDoc { - @Override - public void entrypoint() { - Object r = decode("x"); - consume(r); - } - } - - static class PositiveNotYetExcluded extends ArgNotInsideDoc { - @Override - public void entrypoint() { - Object r = decode("x"); - check(r); - consume(r); - } - } -} diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/InstanceArgNotInsideDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/InstanceArgNotInsideDoc.java index 3c72af8ef..90032c3c3 100644 --- a/core/opentaint-java-querylang/samples/src/main/java/example/InstanceArgNotInsideDoc.java +++ b/core/opentaint-java-querylang/samples/src/main/java/example/InstanceArgNotInsideDoc.java @@ -5,8 +5,9 @@ /** * Doc validation: pattern-not-inside whose excluded event is an instance - * call taking the produced value as an argument (anonymous receiver) does - * not anchor the exclusion; the receiver-position counterparts do. + * call taking the produced value as an argument (anonymous receiver), in + * the anchored shape: producer in pattern-inside, single-event main + * pattern. */ @RuleSet("example/InstanceArgNotInsideDoc.yaml") public abstract class InstanceArgNotInsideDoc implements RuleSample { diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/SanitizeNotInsideDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/SanitizeNotInsideDoc.java index 0db21a17b..53fafa3af 100644 --- a/core/opentaint-java-querylang/samples/src/main/java/example/SanitizeNotInsideDoc.java +++ b/core/opentaint-java-querylang/samples/src/main/java/example/SanitizeNotInsideDoc.java @@ -5,10 +5,11 @@ /** * Doc validation: pattern-not-inside whose excluded event is the - * self-sanitizing reassignment does not anchor the exclusion — the tracked - * value sits in argument position of the static call. The identical event - * matches positively (SanitizeEventSanityDoc), and the receiver-position - * counterpart excludes (ReceiverSanitizePatternNotDoc). + * self-sanitizing reassignment, in the anchored shape: producer in + * pattern-inside, single-event main pattern, so the excluded context can + * enclose the match. The identical event matches positively + * (SanitizeEventSanityDoc); the receiver-position counterpart excludes + * (ReceiverSanitizePatternNotDoc). */ @RuleSet("example/SanitizeNotInsideDoc.yaml") public abstract class SanitizeNotInsideDoc implements RuleSample { diff --git a/core/opentaint-java-querylang/samples/src/main/resources/example/ArgNotInsideDoc.yaml b/core/opentaint-java-querylang/samples/src/main/resources/example/ArgNotInsideDoc.yaml deleted file mode 100644 index 8207316a6..000000000 --- a/core/opentaint-java-querylang/samples/src/main/resources/example/ArgNotInsideDoc.yaml +++ /dev/null @@ -1,14 +0,0 @@ -rules: - - id: example-ArgNotInsideDoc - languages: - - java - severity: ERROR - message: match example/ArgNotInsideDoc - patterns: - - pattern: | - $RESULT = decode($INPUT); - ... - consume($RESULT); - - pattern-not-inside: | - check($RESULT); - ... diff --git a/core/opentaint-java-querylang/samples/src/main/resources/example/InstanceArgNotInsideDoc.yaml b/core/opentaint-java-querylang/samples/src/main/resources/example/InstanceArgNotInsideDoc.yaml index 904d11582..f736224a2 100644 --- a/core/opentaint-java-querylang/samples/src/main/resources/example/InstanceArgNotInsideDoc.yaml +++ b/core/opentaint-java-querylang/samples/src/main/resources/example/InstanceArgNotInsideDoc.yaml @@ -5,10 +5,10 @@ rules: severity: ERROR message: match example/InstanceArgNotInsideDoc patterns: - - pattern: | + - pattern-inside: | $RESULT = decode($INPUT); ... - consume($RESULT); + - pattern: consume($RESULT) - pattern-not-inside: | $_.verify($RESULT); ... diff --git a/core/opentaint-java-querylang/samples/src/main/resources/example/SanitizeNotInsideDoc.yaml b/core/opentaint-java-querylang/samples/src/main/resources/example/SanitizeNotInsideDoc.yaml index 6a53783bd..0a5e46b09 100644 --- a/core/opentaint-java-querylang/samples/src/main/resources/example/SanitizeNotInsideDoc.yaml +++ b/core/opentaint-java-querylang/samples/src/main/resources/example/SanitizeNotInsideDoc.yaml @@ -5,10 +5,10 @@ rules: severity: ERROR message: match example/SanitizeNotInsideDoc patterns: - - pattern: | + - pattern-inside: | $RESULT = decode($INPUT); ... - consume($RESULT); + - pattern: consume($RESULT) - pattern-not-inside: | $RESULT = sanitize($RESULT); ... diff --git a/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/DocNegativeShapesTest.kt b/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/DocNegativeShapesTest.kt index d4711fb89..a8c7f96d6 100644 --- a/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/DocNegativeShapesTest.kt +++ b/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/DocNegativeShapesTest.kt @@ -27,9 +27,6 @@ class DocNegativeShapesTest : SampleBasedTest() { @Test fun `test argument full-form pattern-not`() = runTest() - @Test - fun `test argument pattern-not-inside`() = runTest() - @Test fun `test argument event matches positively`() = runTest() From 7c763cf02ddbb024e4191fd6e7177ee8fc5675bc Mon Sep 17 00:00:00 2001 From: Aleksandr Misonizhnik Date: Mon, 20 Jul 2026 00:11:47 +0300 Subject: [PATCH 3/6] test: mark argument-position negative probes as known false positives Replace the PositiveNotYetExcluded naming hack with the repository's established idiom: the affected samples are Negative (declaring the desired semantics) annotated @TaintRuleFalsePositive, so the harness skips the known over-reporting with a printed reason instead of asserting the buggy outcome. The suite stays green and the skip log shows exactly which exclusions are currently ineffective; when the defect is fixed, the annotations become removable. --- .../samples/src/main/java/example/ArgFullPatternNotDoc.java | 4 +++- .../src/main/java/example/ArgNotInsideAnchoredDoc.java | 4 +++- .../src/main/java/example/ArgObserverPatternNotDoc.java | 4 +++- .../src/main/java/example/InstanceArgNotInsideDoc.java | 4 +++- .../samples/src/main/java/example/NegOnlyNotInsideDoc.java | 4 +++- .../samples/src/main/java/example/SanitizeNotInsideDoc.java | 4 +++- .../samples/src/main/java/example/SanitizePatternNotDoc.java | 4 +++- 7 files changed, 21 insertions(+), 7 deletions(-) diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/ArgFullPatternNotDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/ArgFullPatternNotDoc.java index 7fc6b7080..7f21b4208 100644 --- a/core/opentaint-java-querylang/samples/src/main/java/example/ArgFullPatternNotDoc.java +++ b/core/opentaint-java-querylang/samples/src/main/java/example/ArgFullPatternNotDoc.java @@ -2,6 +2,7 @@ import base.RuleSample; import base.RuleSet; +import base.TaintRuleFalsePositive; /** * Doc validation: full-form pattern-not whose added event uses the produced @@ -25,7 +26,8 @@ public void entrypoint() { } } - static class PositiveNotYetExcluded extends ArgFullPatternNotDoc { + @TaintRuleFalsePositive("negative clauses do not anchor on argument-position events") + static class Negative extends ArgFullPatternNotDoc { @Override public void entrypoint() { Object r = decode("x"); diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/ArgNotInsideAnchoredDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/ArgNotInsideAnchoredDoc.java index 3a2a262f7..57c506b1b 100644 --- a/core/opentaint-java-querylang/samples/src/main/java/example/ArgNotInsideAnchoredDoc.java +++ b/core/opentaint-java-querylang/samples/src/main/java/example/ArgNotInsideAnchoredDoc.java @@ -2,6 +2,7 @@ import base.RuleSample; import base.RuleSet; +import base.TaintRuleFalsePositive; /** * Doc validation: argument-position pattern-not-inside does not anchor even @@ -28,7 +29,8 @@ public void entrypoint() { } } - static class PositiveNotYetExcluded extends ArgNotInsideAnchoredDoc { + @TaintRuleFalsePositive("negative clauses do not anchor on argument-position events") + static class Negative extends ArgNotInsideAnchoredDoc { @Override public void entrypoint() { Object r = decode("x"); diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/ArgObserverPatternNotDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/ArgObserverPatternNotDoc.java index 45f64fc0c..8bf11a4e4 100644 --- a/core/opentaint-java-querylang/samples/src/main/java/example/ArgObserverPatternNotDoc.java +++ b/core/opentaint-java-querylang/samples/src/main/java/example/ArgObserverPatternNotDoc.java @@ -2,6 +2,7 @@ import base.RuleSample; import base.RuleSet; +import base.TaintRuleFalsePositive; /** * Doc validation: documents the current argument-position restriction: an @@ -23,7 +24,8 @@ public void entrypoint() { } } - static class PositiveNotYetExcluded extends ArgObserverPatternNotDoc { + @TaintRuleFalsePositive("negative clauses do not anchor on argument-position events") + static class Negative extends ArgObserverPatternNotDoc { @Override public void entrypoint() { Object r = decode("x"); diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/InstanceArgNotInsideDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/InstanceArgNotInsideDoc.java index 90032c3c3..c6a278d51 100644 --- a/core/opentaint-java-querylang/samples/src/main/java/example/InstanceArgNotInsideDoc.java +++ b/core/opentaint-java-querylang/samples/src/main/java/example/InstanceArgNotInsideDoc.java @@ -2,6 +2,7 @@ import base.RuleSample; import base.RuleSet; +import base.TaintRuleFalsePositive; /** * Doc validation: pattern-not-inside whose excluded event is an instance @@ -28,7 +29,8 @@ public void entrypoint() { } } - static class PositiveNotYetExcluded extends InstanceArgNotInsideDoc { + @TaintRuleFalsePositive("negative clauses do not anchor on argument-position events") + static class Negative extends InstanceArgNotInsideDoc { @Override public void entrypoint() { Object r = decode("x"); diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/NegOnlyNotInsideDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/NegOnlyNotInsideDoc.java index 417cf99a8..e0e98c9cd 100644 --- a/core/opentaint-java-querylang/samples/src/main/java/example/NegOnlyNotInsideDoc.java +++ b/core/opentaint-java-querylang/samples/src/main/java/example/NegOnlyNotInsideDoc.java @@ -2,6 +2,7 @@ import base.RuleSample; import base.RuleSet; +import base.TaintRuleFalsePositive; /** * Doc validation: documents that a pattern-not-inside whose metavariables @@ -24,7 +25,8 @@ public void entrypoint() { } } - static class PositiveNotYetExcluded extends NegOnlyNotInsideDoc { + @TaintRuleFalsePositive("negative-only metavariables have no positive domain to subtract from") + static class Negative extends NegOnlyNotInsideDoc { @Override public void entrypoint() { Client f = Client.builder(); diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/SanitizeNotInsideDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/SanitizeNotInsideDoc.java index 53fafa3af..cefb9f74b 100644 --- a/core/opentaint-java-querylang/samples/src/main/java/example/SanitizeNotInsideDoc.java +++ b/core/opentaint-java-querylang/samples/src/main/java/example/SanitizeNotInsideDoc.java @@ -2,6 +2,7 @@ import base.RuleSample; import base.RuleSet; +import base.TaintRuleFalsePositive; /** * Doc validation: pattern-not-inside whose excluded event is the @@ -26,7 +27,8 @@ public void entrypoint() { } } - static class PositiveNotYetExcluded extends SanitizeNotInsideDoc { + @TaintRuleFalsePositive("negative clauses do not anchor on argument-position events") + static class Negative extends SanitizeNotInsideDoc { @Override public void entrypoint() { Object r = decode("x"); diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/SanitizePatternNotDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/SanitizePatternNotDoc.java index 2d053efac..ef9af3be9 100644 --- a/core/opentaint-java-querylang/samples/src/main/java/example/SanitizePatternNotDoc.java +++ b/core/opentaint-java-querylang/samples/src/main/java/example/SanitizePatternNotDoc.java @@ -2,6 +2,7 @@ import base.RuleSample; import base.RuleSet; +import base.TaintRuleFalsePositive; /** * Doc validation: documents the current argument-position restriction: a @@ -24,7 +25,8 @@ public void entrypoint() { } } - static class PositiveNotYetExcluded extends SanitizePatternNotDoc { + @TaintRuleFalsePositive("negative clauses do not anchor on argument-position events") + static class Negative extends SanitizePatternNotDoc { @Override public void entrypoint() { Object r = decode("x"); From 2f4d650953d3d5fa84c35f4cd4d496c24e372228 Mon Sep 17 00:00:00 2001 From: Aleksandr Misonizhnik Date: Mon, 20 Jul 2026 00:19:14 +0300 Subject: [PATCH 4/6] test: assert desired negative-clause semantics, failing on the open defect Drop the known-false-positive annotations: the argument-position and negative-only-metavariable probes now assert that their Negative samples must not report, and fail until negative clauses anchor on argument-position events. Positive controls and receiver-position counterparts keep passing. --- .../src/main/java/example/ArgFullPatternNotDoc.java | 2 -- .../src/main/java/example/ArgNotInsideAnchoredDoc.java | 2 -- .../src/main/java/example/ArgObserverPatternNotDoc.java | 2 -- .../src/main/java/example/InstanceArgNotInsideDoc.java | 2 -- .../src/main/java/example/NegOnlyNotInsideDoc.java | 2 -- .../src/main/java/example/SanitizeNotInsideDoc.java | 2 -- .../src/main/java/example/SanitizePatternNotDoc.java | 2 -- .../kotlin/org/opentaint/semgrep/DocNegativeShapesTest.kt | 8 ++++++++ 8 files changed, 8 insertions(+), 14 deletions(-) diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/ArgFullPatternNotDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/ArgFullPatternNotDoc.java index 7f21b4208..f5c6f38d2 100644 --- a/core/opentaint-java-querylang/samples/src/main/java/example/ArgFullPatternNotDoc.java +++ b/core/opentaint-java-querylang/samples/src/main/java/example/ArgFullPatternNotDoc.java @@ -2,7 +2,6 @@ import base.RuleSample; import base.RuleSet; -import base.TaintRuleFalsePositive; /** * Doc validation: full-form pattern-not whose added event uses the produced @@ -26,7 +25,6 @@ public void entrypoint() { } } - @TaintRuleFalsePositive("negative clauses do not anchor on argument-position events") static class Negative extends ArgFullPatternNotDoc { @Override public void entrypoint() { diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/ArgNotInsideAnchoredDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/ArgNotInsideAnchoredDoc.java index 57c506b1b..77a8077e4 100644 --- a/core/opentaint-java-querylang/samples/src/main/java/example/ArgNotInsideAnchoredDoc.java +++ b/core/opentaint-java-querylang/samples/src/main/java/example/ArgNotInsideAnchoredDoc.java @@ -2,7 +2,6 @@ import base.RuleSample; import base.RuleSet; -import base.TaintRuleFalsePositive; /** * Doc validation: argument-position pattern-not-inside does not anchor even @@ -29,7 +28,6 @@ public void entrypoint() { } } - @TaintRuleFalsePositive("negative clauses do not anchor on argument-position events") static class Negative extends ArgNotInsideAnchoredDoc { @Override public void entrypoint() { diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/ArgObserverPatternNotDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/ArgObserverPatternNotDoc.java index 8bf11a4e4..dc5da7c1c 100644 --- a/core/opentaint-java-querylang/samples/src/main/java/example/ArgObserverPatternNotDoc.java +++ b/core/opentaint-java-querylang/samples/src/main/java/example/ArgObserverPatternNotDoc.java @@ -2,7 +2,6 @@ import base.RuleSample; import base.RuleSet; -import base.TaintRuleFalsePositive; /** * Doc validation: documents the current argument-position restriction: an @@ -24,7 +23,6 @@ public void entrypoint() { } } - @TaintRuleFalsePositive("negative clauses do not anchor on argument-position events") static class Negative extends ArgObserverPatternNotDoc { @Override public void entrypoint() { diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/InstanceArgNotInsideDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/InstanceArgNotInsideDoc.java index c6a278d51..379c47e2a 100644 --- a/core/opentaint-java-querylang/samples/src/main/java/example/InstanceArgNotInsideDoc.java +++ b/core/opentaint-java-querylang/samples/src/main/java/example/InstanceArgNotInsideDoc.java @@ -2,7 +2,6 @@ import base.RuleSample; import base.RuleSet; -import base.TaintRuleFalsePositive; /** * Doc validation: pattern-not-inside whose excluded event is an instance @@ -29,7 +28,6 @@ public void entrypoint() { } } - @TaintRuleFalsePositive("negative clauses do not anchor on argument-position events") static class Negative extends InstanceArgNotInsideDoc { @Override public void entrypoint() { diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/NegOnlyNotInsideDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/NegOnlyNotInsideDoc.java index e0e98c9cd..32a7023ce 100644 --- a/core/opentaint-java-querylang/samples/src/main/java/example/NegOnlyNotInsideDoc.java +++ b/core/opentaint-java-querylang/samples/src/main/java/example/NegOnlyNotInsideDoc.java @@ -2,7 +2,6 @@ import base.RuleSample; import base.RuleSet; -import base.TaintRuleFalsePositive; /** * Doc validation: documents that a pattern-not-inside whose metavariables @@ -25,7 +24,6 @@ public void entrypoint() { } } - @TaintRuleFalsePositive("negative-only metavariables have no positive domain to subtract from") static class Negative extends NegOnlyNotInsideDoc { @Override public void entrypoint() { diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/SanitizeNotInsideDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/SanitizeNotInsideDoc.java index cefb9f74b..79342cca2 100644 --- a/core/opentaint-java-querylang/samples/src/main/java/example/SanitizeNotInsideDoc.java +++ b/core/opentaint-java-querylang/samples/src/main/java/example/SanitizeNotInsideDoc.java @@ -2,7 +2,6 @@ import base.RuleSample; import base.RuleSet; -import base.TaintRuleFalsePositive; /** * Doc validation: pattern-not-inside whose excluded event is the @@ -27,7 +26,6 @@ public void entrypoint() { } } - @TaintRuleFalsePositive("negative clauses do not anchor on argument-position events") static class Negative extends SanitizeNotInsideDoc { @Override public void entrypoint() { diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/SanitizePatternNotDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/SanitizePatternNotDoc.java index ef9af3be9..5bbbc8ef2 100644 --- a/core/opentaint-java-querylang/samples/src/main/java/example/SanitizePatternNotDoc.java +++ b/core/opentaint-java-querylang/samples/src/main/java/example/SanitizePatternNotDoc.java @@ -2,7 +2,6 @@ import base.RuleSample; import base.RuleSet; -import base.TaintRuleFalsePositive; /** * Doc validation: documents the current argument-position restriction: a @@ -25,7 +24,6 @@ public void entrypoint() { } } - @TaintRuleFalsePositive("negative clauses do not anchor on argument-position events") static class Negative extends SanitizePatternNotDoc { @Override public void entrypoint() { diff --git a/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/DocNegativeShapesTest.kt b/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/DocNegativeShapesTest.kt index a8c7f96d6..10715c3b9 100644 --- a/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/DocNegativeShapesTest.kt +++ b/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/DocNegativeShapesTest.kt @@ -6,6 +6,14 @@ import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS import org.opentaint.semgrep.util.SampleBasedTest import kotlin.test.Test +/** + * Pins negative-clause anchoring behavior. The failing tests are intentional: + * their Negative samples assert the desired exclusion semantics, and they stay + * red while negative clauses do not anchor on argument-position events (or on + * metavariables without a positive domain). The passing tests are the positive + * controls proving the same events match positively, and the receiver-position + * counterparts proving the exclusions work in receiver shape. + */ @TestInstance(PER_CLASS) class DocNegativeShapesTest : SampleBasedTest() { From ffbc8a3d23baa92c5b11f87557aec939f6d2a1db Mon Sep 17 00:00:00 2001 From: Aleksandr Misonizhnik Date: Mon, 20 Jul 2026 00:31:22 +0300 Subject: [PATCH 5/6] test: drop weak negative-clause probes Remove InstanceArgNotInsideDoc, NegOnlyNotInsideDoc, and ReceiverNotInsideSpanDoc; the suite keeps the argument-position probes with shape-clean rules plus their positive controls and receiver counterparts. --- .../java/example/InstanceArgNotInsideDoc.java | 39 ------------------- .../java/example/NegOnlyNotInsideDoc.java | 36 ----------------- .../example/ReceiverNotInsideSpanDoc.java | 36 ----------------- .../example/InstanceArgNotInsideDoc.yaml | 14 ------- .../example/NegOnlyNotInsideDoc.yaml | 11 ------ .../example/ReceiverNotInsideSpanDoc.yaml | 14 ------- .../semgrep/DocNegativeShapesTest.kt | 13 +------ 7 files changed, 2 insertions(+), 161 deletions(-) delete mode 100644 core/opentaint-java-querylang/samples/src/main/java/example/InstanceArgNotInsideDoc.java delete mode 100644 core/opentaint-java-querylang/samples/src/main/java/example/NegOnlyNotInsideDoc.java delete mode 100644 core/opentaint-java-querylang/samples/src/main/java/example/ReceiverNotInsideSpanDoc.java delete mode 100644 core/opentaint-java-querylang/samples/src/main/resources/example/InstanceArgNotInsideDoc.yaml delete mode 100644 core/opentaint-java-querylang/samples/src/main/resources/example/NegOnlyNotInsideDoc.yaml delete mode 100644 core/opentaint-java-querylang/samples/src/main/resources/example/ReceiverNotInsideSpanDoc.yaml diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/InstanceArgNotInsideDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/InstanceArgNotInsideDoc.java deleted file mode 100644 index 379c47e2a..000000000 --- a/core/opentaint-java-querylang/samples/src/main/java/example/InstanceArgNotInsideDoc.java +++ /dev/null @@ -1,39 +0,0 @@ -package example; - -import base.RuleSample; -import base.RuleSet; - -/** - * Doc validation: pattern-not-inside whose excluded event is an instance - * call taking the produced value as an argument (anonymous receiver), in - * the anchored shape: producer in pattern-inside, single-event main - * pattern. - */ -@RuleSet("example/InstanceArgNotInsideDoc.yaml") -public abstract class InstanceArgNotInsideDoc implements RuleSample { - - static class Aux { - int state; - void verify(Object o) { state += o.hashCode(); } - } - - static Object decode(Object o) { return o; } - static void consume(Object o) {} - - static class Positive extends InstanceArgNotInsideDoc { - @Override - public void entrypoint() { - Object r = decode("x"); - consume(r); - } - } - - static class Negative extends InstanceArgNotInsideDoc { - @Override - public void entrypoint() { - Object r = decode("x"); - new Aux().verify(r); - consume(r); - } - } -} diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/NegOnlyNotInsideDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/NegOnlyNotInsideDoc.java deleted file mode 100644 index 32a7023ce..000000000 --- a/core/opentaint-java-querylang/samples/src/main/java/example/NegOnlyNotInsideDoc.java +++ /dev/null @@ -1,36 +0,0 @@ -package example; - -import base.RuleSample; -import base.RuleSet; - -/** - * Doc validation: documents that a pattern-not-inside whose metavariables - * exist only in the negative clause is silently ineffective. - */ -@RuleSet("example/NegOnlyNotInsideDoc.yaml") -public abstract class NegOnlyNotInsideDoc implements RuleSample { - - static class Client { - static Client builder() { return new Client(); } - void configure(String mode) {} - void connect(String url) {} - } - - static class Positive extends NegOnlyNotInsideDoc { - @Override - public void entrypoint() { - Client c = Client.builder(); - c.connect("http://example.com"); - } - } - - static class Negative extends NegOnlyNotInsideDoc { - @Override - public void entrypoint() { - Client f = Client.builder(); - f.configure("mode"); - Client c = Client.builder(); - c.connect("http://example.com"); - } - } -} diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/ReceiverNotInsideSpanDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/ReceiverNotInsideSpanDoc.java deleted file mode 100644 index c0b4f3848..000000000 --- a/core/opentaint-java-querylang/samples/src/main/java/example/ReceiverNotInsideSpanDoc.java +++ /dev/null @@ -1,36 +0,0 @@ -package example; - -import base.RuleSample; -import base.RuleSet; - -/** - * Doc validation: receiver-position pattern-not-inside against a multi-event - * main pattern — does the excluded context need to contain the whole span or - * only the accepting event? - */ -@RuleSet("example/ReceiverNotInsideSpanDoc.yaml") -public abstract class ReceiverNotInsideSpanDoc implements RuleSample { - - static class Client { - static Client builder() { return new Client(); } - void allowHost(String host) {} - void connect(String url) {} - } - - static class Positive extends ReceiverNotInsideSpanDoc { - @Override - public void entrypoint() { - Client c = Client.builder(); - c.connect("http://example.com"); - } - } - - static class Negative extends ReceiverNotInsideSpanDoc { - @Override - public void entrypoint() { - Client c = Client.builder(); - c.allowHost("trusted.example"); - c.connect("http://example.com"); - } - } -} diff --git a/core/opentaint-java-querylang/samples/src/main/resources/example/InstanceArgNotInsideDoc.yaml b/core/opentaint-java-querylang/samples/src/main/resources/example/InstanceArgNotInsideDoc.yaml deleted file mode 100644 index f736224a2..000000000 --- a/core/opentaint-java-querylang/samples/src/main/resources/example/InstanceArgNotInsideDoc.yaml +++ /dev/null @@ -1,14 +0,0 @@ -rules: - - id: example-InstanceArgNotInsideDoc - languages: - - java - severity: ERROR - message: match example/InstanceArgNotInsideDoc - patterns: - - pattern-inside: | - $RESULT = decode($INPUT); - ... - - pattern: consume($RESULT) - - pattern-not-inside: | - $_.verify($RESULT); - ... diff --git a/core/opentaint-java-querylang/samples/src/main/resources/example/NegOnlyNotInsideDoc.yaml b/core/opentaint-java-querylang/samples/src/main/resources/example/NegOnlyNotInsideDoc.yaml deleted file mode 100644 index ae3b00ad5..000000000 --- a/core/opentaint-java-querylang/samples/src/main/resources/example/NegOnlyNotInsideDoc.yaml +++ /dev/null @@ -1,11 +0,0 @@ -rules: - - id: example-NegOnlyNotInsideDoc - languages: - - java - severity: ERROR - message: match example/NegOnlyNotInsideDoc - patterns: - - pattern: $CLIENT.connect($URL) - - pattern-not-inside: | - $FACTORY.configure($MODE); - ... diff --git a/core/opentaint-java-querylang/samples/src/main/resources/example/ReceiverNotInsideSpanDoc.yaml b/core/opentaint-java-querylang/samples/src/main/resources/example/ReceiverNotInsideSpanDoc.yaml deleted file mode 100644 index d82450598..000000000 --- a/core/opentaint-java-querylang/samples/src/main/resources/example/ReceiverNotInsideSpanDoc.yaml +++ /dev/null @@ -1,14 +0,0 @@ -rules: - - id: example-ReceiverNotInsideSpanDoc - languages: - - java - severity: ERROR - message: match example/ReceiverNotInsideSpanDoc - patterns: - - pattern: | - $CLIENT = Client.builder(); - ... - $CLIENT.connect($URL); - - pattern-not-inside: | - $CLIENT.allowHost("trusted.example"); - ... diff --git a/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/DocNegativeShapesTest.kt b/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/DocNegativeShapesTest.kt index 10715c3b9..c20b4b84d 100644 --- a/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/DocNegativeShapesTest.kt +++ b/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/DocNegativeShapesTest.kt @@ -9,8 +9,8 @@ import kotlin.test.Test /** * Pins negative-clause anchoring behavior. The failing tests are intentional: * their Negative samples assert the desired exclusion semantics, and they stay - * red while negative clauses do not anchor on argument-position events (or on - * metavariables without a positive domain). The passing tests are the positive + * red while negative clauses do not anchor on argument-position events. The + * passing tests are the positive * controls proving the same events match positively, and the receiver-position * counterparts proving the exclusions work in receiver shape. */ @@ -26,9 +26,6 @@ class DocNegativeShapesTest : SampleBasedTest() { @Test fun `test allowlist pattern-not-inside`() = runTest() - @Test - fun `test negative-only metavariables`() = runTest() - @Test fun `test receiver sanitize pattern-not`() = runTest() @@ -38,9 +35,6 @@ class DocNegativeShapesTest : SampleBasedTest() { @Test fun `test argument event matches positively`() = runTest() - @Test - fun `test instance argument pattern-not-inside`() = runTest() - @Test fun `test sanitize reassignment event matches positively`() = runTest() @@ -50,9 +44,6 @@ class DocNegativeShapesTest : SampleBasedTest() { @Test fun `test argument not-inside with satisfiable containment`() = runTest() - @Test - fun `test receiver not-inside against multi-event main pattern`() = runTest() - @AfterAll fun close() { closeRunner() From d338ea8eb3291fcd546cf714cca06b0edeb0ef62 Mon Sep 17 00:00:00 2001 From: Aleksandr Misonizhnik Date: Mon, 20 Jul 2026 10:06:42 +0300 Subject: [PATCH 6/6] =?UTF-8?q?test:=20correct=20the=20finding=20=E2=80=94?= =?UTF-8?q?=20negatives=20fail=20on=20Object-typed=20values?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The earlier probes all declared their tracked value as Object while the working counterexamples used declared types, so the apparent receiver-vs-argument split was a fixture artifact. example/Rule.yaml already excludes an argument-position event, which prompted the recheck. Bisecting from that rule isolates the real discriminator: a structural negative does not exclude when the tracked value's declared type is java.lang.Object. The excluded call's own parameter type is irrelevant, and an Object-typed value is excluded correctly when the negative rebinds it through a receiver call. Retyping the original probes to String makes every one of them pass. ObjectTypedValueDoc and ObjectTypedValueCastDoc assert the desired semantics and fail on the open defect; the rest are controls and clause-shape coverage. --- .../java/example/ArgFullPatternNotDoc.java | 10 +-- .../java/example/ArgNotInsideAnchoredDoc.java | 10 +-- .../example/ArgObserverPatternNotDoc.java | 10 +-- .../example/ObjectParameterControlDoc.java | 30 ++++++++ .../java/example/ObjectTypedValueCastDoc.java | 30 ++++++++ .../java/example/ObjectTypedValueDoc.java | 28 ++++++++ .../example/ObjectTypedValueReceiverDoc.java | 27 ++++++++ .../java/example/SanitizeNotInsideDoc.java | 10 +-- .../java/example/SanitizePatternNotDoc.java | 10 +-- .../java/example/TypedValueControlDoc.java | 30 ++++++++ .../example/ObjectParameterControlDoc.yaml | 17 +++++ .../example/ObjectTypedValueCastDoc.yaml | 17 +++++ .../example/ObjectTypedValueDoc.yaml | 17 +++++ .../example/ObjectTypedValueReceiverDoc.yaml | 17 +++++ .../example/TypedValueControlDoc.yaml | 17 +++++ .../semgrep/DocNegativeShapesTest.kt | 69 +++++++++++++++---- 16 files changed, 309 insertions(+), 40 deletions(-) create mode 100644 core/opentaint-java-querylang/samples/src/main/java/example/ObjectParameterControlDoc.java create mode 100644 core/opentaint-java-querylang/samples/src/main/java/example/ObjectTypedValueCastDoc.java create mode 100644 core/opentaint-java-querylang/samples/src/main/java/example/ObjectTypedValueDoc.java create mode 100644 core/opentaint-java-querylang/samples/src/main/java/example/ObjectTypedValueReceiverDoc.java create mode 100644 core/opentaint-java-querylang/samples/src/main/java/example/TypedValueControlDoc.java create mode 100644 core/opentaint-java-querylang/samples/src/main/resources/example/ObjectParameterControlDoc.yaml create mode 100644 core/opentaint-java-querylang/samples/src/main/resources/example/ObjectTypedValueCastDoc.yaml create mode 100644 core/opentaint-java-querylang/samples/src/main/resources/example/ObjectTypedValueDoc.yaml create mode 100644 core/opentaint-java-querylang/samples/src/main/resources/example/ObjectTypedValueReceiverDoc.yaml create mode 100644 core/opentaint-java-querylang/samples/src/main/resources/example/TypedValueControlDoc.yaml diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/ArgFullPatternNotDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/ArgFullPatternNotDoc.java index f5c6f38d2..abc02d2af 100644 --- a/core/opentaint-java-querylang/samples/src/main/java/example/ArgFullPatternNotDoc.java +++ b/core/opentaint-java-querylang/samples/src/main/java/example/ArgFullPatternNotDoc.java @@ -11,16 +11,16 @@ @RuleSet("example/ArgFullPatternNotDoc.yaml") public abstract class ArgFullPatternNotDoc implements RuleSample { - static Object decode(Object o) { return o; } + static String decode(Object o) { return String.valueOf(o); } static int checksum; - static void check(Object o) { checksum += o.hashCode(); } - static void consume(Object o) {} + static void check(String o) { checksum += o.hashCode(); } + static void consume(String o) {} static class Positive extends ArgFullPatternNotDoc { @Override public void entrypoint() { - Object r = decode("x"); + String r = decode("x"); consume(r); } } @@ -28,7 +28,7 @@ public void entrypoint() { static class Negative extends ArgFullPatternNotDoc { @Override public void entrypoint() { - Object r = decode("x"); + String r = decode("x"); check(r); consume(r); } diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/ArgNotInsideAnchoredDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/ArgNotInsideAnchoredDoc.java index 77a8077e4..2ce2fbe4d 100644 --- a/core/opentaint-java-querylang/samples/src/main/java/example/ArgNotInsideAnchoredDoc.java +++ b/core/opentaint-java-querylang/samples/src/main/java/example/ArgNotInsideAnchoredDoc.java @@ -14,16 +14,16 @@ @RuleSet("example/ArgNotInsideAnchoredDoc.yaml") public abstract class ArgNotInsideAnchoredDoc implements RuleSample { - static Object decode(Object o) { return o; } + static String decode(Object o) { return String.valueOf(o); } static int checksum; - static void check(Object o) { checksum += o.hashCode(); } - static void consume(Object o) {} + static void check(String o) { checksum += o.hashCode(); } + static void consume(String o) {} static class Positive extends ArgNotInsideAnchoredDoc { @Override public void entrypoint() { - Object r = decode("x"); + String r = decode("x"); consume(r); } } @@ -31,7 +31,7 @@ public void entrypoint() { static class Negative extends ArgNotInsideAnchoredDoc { @Override public void entrypoint() { - Object r = decode("x"); + String r = decode("x"); check(r); consume(r); } diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/ArgObserverPatternNotDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/ArgObserverPatternNotDoc.java index dc5da7c1c..393805242 100644 --- a/core/opentaint-java-querylang/samples/src/main/java/example/ArgObserverPatternNotDoc.java +++ b/core/opentaint-java-querylang/samples/src/main/java/example/ArgObserverPatternNotDoc.java @@ -11,14 +11,14 @@ @RuleSet("example/ArgObserverPatternNotDoc.yaml") public abstract class ArgObserverPatternNotDoc implements RuleSample { - static Object decode(Object o) { return o; } - static void check(Object o) {} - static void consume(Object o) {} + static String decode(Object o) { return String.valueOf(o); } + static void check(String o) {} + static void consume(String o) {} static class Positive extends ArgObserverPatternNotDoc { @Override public void entrypoint() { - Object r = decode("x"); + String r = decode("x"); consume(r); } } @@ -26,7 +26,7 @@ public void entrypoint() { static class Negative extends ArgObserverPatternNotDoc { @Override public void entrypoint() { - Object r = decode("x"); + String r = decode("x"); check(r); consume(r); } diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/ObjectParameterControlDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/ObjectParameterControlDoc.java new file mode 100644 index 000000000..03d055c0a --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/java/example/ObjectParameterControlDoc.java @@ -0,0 +1,30 @@ +package example; + +import base.RuleSample; +import base.RuleSet; + +@RuleSet("example/ObjectParameterControlDoc.yaml") +public abstract class ObjectParameterControlDoc implements RuleSample { + static class Payload {} + + Payload src() { return new Payload(); } + void clean(Object data) {} // parameter widened to Object + void sink(Payload data) {} + + final static class PositiveSimple extends ObjectParameterControlDoc { + @Override + public void entrypoint() { + Payload data = src(); + sink(data); + } + } + + final static class NegativeSimple extends ObjectParameterControlDoc { + @Override + public void entrypoint() { + Payload data = src(); + clean(data); + sink(data); + } + } +} diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/ObjectTypedValueCastDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/ObjectTypedValueCastDoc.java new file mode 100644 index 000000000..48b91bd9a --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/java/example/ObjectTypedValueCastDoc.java @@ -0,0 +1,30 @@ +package example; + +import base.RuleSample; +import base.RuleSet; + +@RuleSet("example/ObjectTypedValueCastDoc.yaml") +public abstract class ObjectTypedValueCastDoc implements RuleSample { + static class Payload {} + + Object src() { return new Payload(); } // value's static type is Object + void clean(Payload data) {} + void sink(Object data) {} + + final static class PositiveSimple extends ObjectTypedValueCastDoc { + @Override + public void entrypoint() { + Object data = src(); + sink(data); + } + } + + final static class NegativeSimple extends ObjectTypedValueCastDoc { + @Override + public void entrypoint() { + Object data = src(); + clean((Payload) data); + sink(data); + } + } +} diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/ObjectTypedValueDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/ObjectTypedValueDoc.java new file mode 100644 index 000000000..1cb98a2b9 --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/java/example/ObjectTypedValueDoc.java @@ -0,0 +1,28 @@ +package example; + +import base.RuleSample; +import base.RuleSet; + +@RuleSet("example/ObjectTypedValueDoc.yaml") +public abstract class ObjectTypedValueDoc implements RuleSample { + Object src() { return null; } + void clean(Object data) {} + void sink(Object data) {} + + final static class PositiveSimple extends ObjectTypedValueDoc { + @Override + public void entrypoint() { + Object data = src(); + sink(data); + } + } + + final static class NegativeSimple extends ObjectTypedValueDoc { + @Override + public void entrypoint() { + Object data = src(); + clean(data); + sink(data); + } + } +} diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/ObjectTypedValueReceiverDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/ObjectTypedValueReceiverDoc.java new file mode 100644 index 000000000..eb263975c --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/java/example/ObjectTypedValueReceiverDoc.java @@ -0,0 +1,27 @@ +package example; + +import base.RuleSample; +import base.RuleSet; + +@RuleSet("example/ObjectTypedValueReceiverDoc.yaml") +public abstract class ObjectTypedValueReceiverDoc implements RuleSample { + Object src() { return null; } + void sink(Object data) {} + + final static class PositiveSimple extends ObjectTypedValueReceiverDoc { + @Override + public void entrypoint() { + Object data = src(); + sink(data); + } + } + + final static class NegativeSimple extends ObjectTypedValueReceiverDoc { + @Override + public void entrypoint() { + Object data = src(); + data = data.toString(); + sink(data); + } + } +} diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/SanitizeNotInsideDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/SanitizeNotInsideDoc.java index 79342cca2..f5c0ef446 100644 --- a/core/opentaint-java-querylang/samples/src/main/java/example/SanitizeNotInsideDoc.java +++ b/core/opentaint-java-querylang/samples/src/main/java/example/SanitizeNotInsideDoc.java @@ -14,14 +14,14 @@ @RuleSet("example/SanitizeNotInsideDoc.yaml") public abstract class SanitizeNotInsideDoc implements RuleSample { - static Object decode(Object o) { return o; } - static Object sanitize(Object o) { return o; } - static void consume(Object o) {} + static String decode(Object o) { return String.valueOf(o); } + static String sanitize(String o) { return o; } + static void consume(String o) {} static class Positive extends SanitizeNotInsideDoc { @Override public void entrypoint() { - Object r = decode("x"); + String r = decode("x"); consume(r); } } @@ -29,7 +29,7 @@ public void entrypoint() { static class Negative extends SanitizeNotInsideDoc { @Override public void entrypoint() { - Object r = decode("x"); + String r = decode("x"); r = sanitize(r); consume(r); } diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/SanitizePatternNotDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/SanitizePatternNotDoc.java index 5bbbc8ef2..509584511 100644 --- a/core/opentaint-java-querylang/samples/src/main/java/example/SanitizePatternNotDoc.java +++ b/core/opentaint-java-querylang/samples/src/main/java/example/SanitizePatternNotDoc.java @@ -12,14 +12,14 @@ @RuleSet("example/SanitizePatternNotDoc.yaml") public abstract class SanitizePatternNotDoc implements RuleSample { - static Object decode(Object o) { return o; } - static Object sanitize(Object o) { return o; } - static void consume(Object o) {} + static String decode(Object o) { return String.valueOf(o); } + static String sanitize(String o) { return o; } + static void consume(String o) {} static class Positive extends SanitizePatternNotDoc { @Override public void entrypoint() { - Object r = decode("x"); + String r = decode("x"); consume(r); } } @@ -27,7 +27,7 @@ public void entrypoint() { static class Negative extends SanitizePatternNotDoc { @Override public void entrypoint() { - Object r = decode("x"); + String r = decode("x"); r = sanitize(r); consume(r); } diff --git a/core/opentaint-java-querylang/samples/src/main/java/example/TypedValueControlDoc.java b/core/opentaint-java-querylang/samples/src/main/java/example/TypedValueControlDoc.java new file mode 100644 index 000000000..547809ffe --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/java/example/TypedValueControlDoc.java @@ -0,0 +1,30 @@ +package example; + +import base.RuleSample; +import base.RuleSet; + +@RuleSet("example/TypedValueControlDoc.yaml") +public abstract class TypedValueControlDoc implements RuleSample { + static class Payload {} + + Payload src() { return new Payload(); } + void clean(Payload data) {} + void sink(Payload data) {} + + final static class PositiveSimple extends TypedValueControlDoc { + @Override + public void entrypoint() { + Payload data = src(); + sink(data); + } + } + + final static class NegativeSimple extends TypedValueControlDoc { + @Override + public void entrypoint() { + Payload data = src(); + clean(data); + sink(data); + } + } +} diff --git a/core/opentaint-java-querylang/samples/src/main/resources/example/ObjectParameterControlDoc.yaml b/core/opentaint-java-querylang/samples/src/main/resources/example/ObjectParameterControlDoc.yaml new file mode 100644 index 000000000..60b09bee3 --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/resources/example/ObjectParameterControlDoc.yaml @@ -0,0 +1,17 @@ +rules: + - id: example-ObjectParameterControlDoc + languages: + - java + severity: ERROR + message: match example/ObjectParameterControlDoc + patterns: + - pattern: |- + $A = src(); + ... + sink($A); + - pattern-not: |- + $A = src(); + ... + clean($A); + ... + sink($A); diff --git a/core/opentaint-java-querylang/samples/src/main/resources/example/ObjectTypedValueCastDoc.yaml b/core/opentaint-java-querylang/samples/src/main/resources/example/ObjectTypedValueCastDoc.yaml new file mode 100644 index 000000000..17520c269 --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/resources/example/ObjectTypedValueCastDoc.yaml @@ -0,0 +1,17 @@ +rules: + - id: example-ObjectTypedValueCastDoc + languages: + - java + severity: ERROR + message: match example/ObjectTypedValueCastDoc + patterns: + - pattern: |- + $A = src(); + ... + sink($A); + - pattern-not: |- + $A = src(); + ... + clean($A); + ... + sink($A); diff --git a/core/opentaint-java-querylang/samples/src/main/resources/example/ObjectTypedValueDoc.yaml b/core/opentaint-java-querylang/samples/src/main/resources/example/ObjectTypedValueDoc.yaml new file mode 100644 index 000000000..3b93c7573 --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/resources/example/ObjectTypedValueDoc.yaml @@ -0,0 +1,17 @@ +rules: + - id: example-ObjectTypedValueDoc + languages: + - java + severity: ERROR + message: match example/ObjectTypedValueDoc + patterns: + - pattern: |- + $A = src(); + ... + sink($A); + - pattern-not: |- + $A = src(); + ... + clean($A); + ... + sink($A); diff --git a/core/opentaint-java-querylang/samples/src/main/resources/example/ObjectTypedValueReceiverDoc.yaml b/core/opentaint-java-querylang/samples/src/main/resources/example/ObjectTypedValueReceiverDoc.yaml new file mode 100644 index 000000000..818bcf1eb --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/resources/example/ObjectTypedValueReceiverDoc.yaml @@ -0,0 +1,17 @@ +rules: + - id: example-ObjectTypedValueReceiverDoc + languages: + - java + severity: ERROR + message: match example/ObjectTypedValueReceiverDoc + patterns: + - pattern: |- + $A = src(); + ... + sink($A); + - pattern-not: |- + $A = src(); + ... + $A = $A.toString(); + ... + sink($A); diff --git a/core/opentaint-java-querylang/samples/src/main/resources/example/TypedValueControlDoc.yaml b/core/opentaint-java-querylang/samples/src/main/resources/example/TypedValueControlDoc.yaml new file mode 100644 index 000000000..c9ec1f705 --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/resources/example/TypedValueControlDoc.yaml @@ -0,0 +1,17 @@ +rules: + - id: example-TypedValueControlDoc + languages: + - java + severity: ERROR + message: match example/TypedValueControlDoc + patterns: + - pattern: |- + $A = src(); + ... + sink($A); + - pattern-not: |- + $A = src(); + ... + clean($A); + ... + sink($A); diff --git a/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/DocNegativeShapesTest.kt b/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/DocNegativeShapesTest.kt index c20b4b84d..7c486cc14 100644 --- a/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/DocNegativeShapesTest.kt +++ b/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/DocNegativeShapesTest.kt @@ -7,42 +7,81 @@ import org.opentaint.semgrep.util.SampleBasedTest import kotlin.test.Test /** - * Pins negative-clause anchoring behavior. The failing tests are intentional: - * their Negative samples assert the desired exclusion semantics, and they stay - * red while negative clauses do not anchor on argument-position events. The - * passing tests are the positive - * controls proving the same events match positively, and the receiver-position - * counterparts proving the exclusions work in receiver shape. + * Pins negative-clause exclusion behavior. + * + * The open defect: when the tracked value's declared type is + * `java.lang.Object`, a structural negative that observes the value does not + * exclude the match. `ObjectTypedValueDoc` and `ObjectTypedValueCastDoc` + * assert the desired semantics and stay red until it is fixed. + * + * Everything else passes and isolates the defect: the same rule shapes with a + * declared type (String, a custom class) exclude correctly, the excluded + * call's own parameter type is irrelevant, and an Object-typed value is + * excluded correctly when the negative rebinds it through a receiver call. */ @TestInstance(PER_CLASS) class DocNegativeShapesTest : SampleBasedTest() { + // --- the defect --- + + @Test + fun `test Object-typed value is not excluded`() = runTest() + + @Test + fun `test Object-typed value is not excluded through a cast`() = + runTest() + + // --- controls isolating it --- + + @Test + fun `test declared-type value is excluded`() = runTest() + + @Test + fun `test excluded call parameter type is irrelevant`() = + runTest() + @Test - fun `test sanitize pattern-not`() = runTest() + fun `test Object-typed value is excluded when the negative rebinds it`() = + runTest() + + // --- clause shapes, all with declared-type values --- @Test - fun `test argument observer pattern-not`() = runTest() + fun `test full-form pattern-not with an observing event`() = + runTest() @Test - fun `test allowlist pattern-not-inside`() = runTest() + fun `test leading-ellipsis pattern-not with an observing event`() = + runTest() @Test - fun `test receiver sanitize pattern-not`() = runTest() + fun `test pattern-not with a self-sanitizing reassignment`() = + runTest() @Test - fun `test argument full-form pattern-not`() = runTest() + fun `test pattern-not with a receiver-call reassignment`() = + runTest() @Test - fun `test argument event matches positively`() = runTest() + fun `test pattern-not-inside with an observing event`() = + runTest() @Test - fun `test sanitize reassignment event matches positively`() = runTest() + fun `test pattern-not-inside with a self-sanitizing reassignment`() = + runTest() + + @Test + fun `test pattern-not-inside excluding a configured receiver`() = + runTest() + + // --- positive controls: the excluded events match when required --- @Test - fun `test sanitize reassignment pattern-not-inside`() = runTest() + fun `test observing event matches positively`() = runTest() @Test - fun `test argument not-inside with satisfiable containment`() = runTest() + fun `test reassignment event matches positively`() = + runTest() @AfterAll fun close() {