Java: Adapt to changes in FlowSummaryImpl

Missing manual models were added using the following code added to `FlowSummaryImpl.qll`:

```ql
    private predicate testsummaryElement(
      Input::SummarizedCallableBase c, string namespace, string type, boolean subtypes, string name,
      string signature, string ext, string originalInput, string originalOutput, string kind,
      string provenance, string model, boolean isExact
    ) {
      exists(string input, string output, Callable baseCallable |
        summaryModel(namespace, type, subtypes, name, signature, ext, originalInput, originalOutput,
          kind, provenance, model) and
        baseCallable = interpretElement(namespace, type, subtypes, name, signature, ext, isExact) and
        (
          c.asCallable() = baseCallable and input = originalInput and output = originalOutput
          or
          correspondingKotlinParameterDefaultsArgSpec(baseCallable, c.asCallable(), originalInput,
            input) and
          correspondingKotlinParameterDefaultsArgSpec(baseCallable, c.asCallable(), originalOutput,
            output)
        )
      )
    }

    private predicate testsummaryElement2(
      string namespace, string type, boolean subtypes, string name, string signature, string ext,
      string originalInput, string originalOutput, string kind, string provenance, string model,
      string namespace2, string type2
    ) {
      exists(Input::SummarizedCallableBase c |
        testsummaryElement(c, namespace2, type2, _, _, _, ext, originalInput, originalOutput, kind,
          provenance, model, false) and
        testsummaryElement(c, namespace, type, subtypes, name, _, _, _, _, _, provenance, _, true) and
        signature = paramsString(c.asCallable()) and
        not testsummaryElement(c, _, _, _, _, _, _, originalInput, originalOutput, kind, provenance,
          _, true)
      )
    }

    private string getAMissingManualModel(string namespace2, string type2) {
      exists(
        string namespace, string type, boolean subtypes, string name, string signature, string ext,
        string originalInput, string originalOutput, string kind, string provenance, string model
      |
        testsummaryElement2(namespace, type, subtypes, name, signature, ext, originalInput,
          originalOutput, kind, provenance, model, namespace2, type2) and
        result =
          "- [\"" + namespace + "\", \"" + type + "\", True, \"" + name + "\", \"" + signature +
            "\", \"\", \"" + originalInput + "\", \"" + originalOutput + "\", \"" + kind + "\", \"" +
            provenance + "\"]"
      )
    }
```
This commit is contained in:
Tom Hvitved
2025-12-16 14:09:09 +01:00
parent c975ae5231
commit 7024b07dd2
27 changed files with 14499 additions and 14881 deletions

View File

@@ -398,6 +398,7 @@ extensions:
- ["java.util", "StringJoiner", False, "toString", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
- ["java.util", "StringTokenizer", False, "StringTokenizer", "", "", "Argument[0]", "Argument[this]", "taint", "manual"]
- ["java.util", "StringTokenizer", False, "nextElement", "()", "", "Argument[this]", "ReturnValue", "taint", "manual"]
- ["java.util", "StringTokenizer", False, "nextElement", "()", "", "Argument[this].Element", "ReturnValue", "value", "manual"]
- ["java.util", "StringTokenizer", False, "nextToken", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
- ["java.util", "TreeMap", False, "TreeMap", "(Map)", "", "Argument[0].MapKey", "Argument[this].MapKey", "value", "manual"]
- ["java.util", "TreeMap", False, "TreeMap", "(Map)", "", "Argument[0].MapValue", "Argument[this].MapValue", "value", "manual"]

View File

@@ -70,10 +70,10 @@ extensions:
- ["org.apache.commons.collections4.map", "ListOrderedMap", True, "keyList", "", "", "Argument[this].MapKey", "ReturnValue.Element", "value", "manual"]
- ["org.apache.commons.collections4.map", "ListOrderedMap", True, "listOrderedMap", "", "", "Argument[0].MapKey", "ReturnValue.MapKey", "value", "manual"]
- ["org.apache.commons.collections4.map", "ListOrderedMap", True, "listOrderedMap", "", "", "Argument[0].MapValue", "ReturnValue.MapValue", "value", "manual"]
- ["org.apache.commons.collections4.map", "ListOrderedMap", True, "put", "", "", "Argument[1]", "Argument[this].MapKey", "value", "manual"]
- ["org.apache.commons.collections4.map", "ListOrderedMap", True, "put", "", "", "Argument[2]", "Argument[this].MapValue", "value", "manual"]
- ["org.apache.commons.collections4.map", "ListOrderedMap", True, "putAll", "", "", "Argument[1].MapKey", "Argument[this].MapKey", "value", "manual"]
- ["org.apache.commons.collections4.map", "ListOrderedMap", True, "putAll", "", "", "Argument[1].MapValue", "Argument[this].MapValue", "value", "manual"]
- ["org.apache.commons.collections4.map", "ListOrderedMap", True, "put", "(int,Object,Object)", "", "Argument[1]", "Argument[this].MapKey", "value", "manual"]
- ["org.apache.commons.collections4.map", "ListOrderedMap", True, "put", "(int,Object,Object)", "", "Argument[2]", "Argument[this].MapValue", "value", "manual"]
- ["org.apache.commons.collections4.map", "ListOrderedMap", True, "putAll", "(int,Map)", "", "Argument[1].MapKey", "Argument[this].MapKey", "value", "manual"]
- ["org.apache.commons.collections4.map", "ListOrderedMap", True, "putAll", "(int,Map)", "", "Argument[1].MapValue", "Argument[this].MapValue", "value", "manual"]
- ["org.apache.commons.collections4.map", "ListOrderedMap", True, "remove", "(int)", "", "Argument[this].MapValue", "ReturnValue", "value", "manual"]
- ["org.apache.commons.collections4.map", "ListOrderedMap", True, "setValue", "", "", "Argument[1]", "Argument[this].MapValue", "value", "manual"]
- ["org.apache.commons.collections4.map", "ListOrderedMap", True, "valueList", "", "", "Argument[this].MapValue", "ReturnValue.Element", "value", "manual"]
@@ -87,6 +87,8 @@ extensions:
- ["org.apache.commons.collections4.map", "MultiKeyMap", True, "put", "(Object,Object,Object,Object,Object)", "", "Argument[4]", "Argument[this].MapValue", "value", "manual"]
- ["org.apache.commons.collections4.map", "MultiKeyMap", True, "put", "(Object,Object,Object,Object,Object,Object)", "", "Argument[0..4]", "Argument[this].MapKey.Element", "value", "manual"]
- ["org.apache.commons.collections4.map", "MultiKeyMap", True, "put", "(Object,Object,Object,Object,Object,Object)", "", "Argument[5]", "Argument[this].MapValue", "value", "manual"]
- ["org.apache.commons.collections4.map", "MultiKeyMap", True, "put", "(MultiKey,Object)", "", "Argument[0]", "Argument[this].MapKey", "value", "manual"]
- ["org.apache.commons.collections4.map", "MultiKeyMap", True, "put", "(MultiKey,Object)", "", "Argument[1]", "Argument[this].MapValue", "value", "manual"]
- ["org.apache.commons.collections4.map", "MultiKeyMap", True, "removeMultiKey", "", "", "Argument[this].MapValue", "ReturnValue", "value", "manual"]
- ["org.apache.commons.collections4.map", "MultiValueMap", True, "getCollection", "", "", "Argument[this].MapValue.Element", "ReturnValue.Element", "value", "manual"]
- ["org.apache.commons.collections4.map", "MultiValueMap", True, "iterator", "()", "", "Argument[this].MapKey", "ReturnValue.Element.MapKey", "value", "manual"]
@@ -98,6 +100,8 @@ extensions:
- ["org.apache.commons.collections4.map", "MultiValueMap", True, "putAll", "(Map)", "", "Argument[0].MapValue.Element", "Argument[this].MapValue.Element", "value", "manual"]
- ["org.apache.commons.collections4.map", "MultiValueMap", True, "putAll", "(Object,Collection)", "", "Argument[0]", "Argument[this].MapKey", "value", "manual"]
- ["org.apache.commons.collections4.map", "MultiValueMap", True, "putAll", "(Object,Collection)", "", "Argument[1].Element", "Argument[this].MapValue.Element", "value", "manual"]
- ["org.apache.commons.collections4.map", "MultiValueMap", True, "putAll", "(Map)", "", "Argument[0].MapKey", "Argument[this].MapKey", "value", "manual"]
- ["org.apache.commons.collections4.map", "MultiValueMap", True, "putAll", "(Map)", "", "Argument[0].MapValue", "Argument[this].MapValue", "value", "manual"]
- ["org.apache.commons.collections4.map", "MultiValueMap", True, "values", "", "", "Argument[this].MapValue.Element", "ReturnValue.Element", "value", "manual"]
- ["org.apache.commons.collections4.map", "PassiveExpiringMap", True, "PassiveExpiringMap", "(PassiveExpiringMap$ExpirationPolicy,Map)", "", "Argument[1].MapKey", "Argument[this].MapKey", "value", "manual"]
- ["org.apache.commons.collections4.map", "PassiveExpiringMap", True, "PassiveExpiringMap", "(PassiveExpiringMap$ExpirationPolicy,Map)", "", "Argument[1].MapValue", "Argument[this].MapValue", "value", "manual"]

View File

@@ -287,7 +287,7 @@ extensions:
- ["org.apache.commons.collections4", "MapUtils", True, "unmodifiableMap", "", "", "Argument[0].MapValue", "ReturnValue.MapValue", "value", "manual"]
- ["org.apache.commons.collections4", "MapUtils", True, "unmodifiableSortedMap", "", "", "Argument[0].MapKey", "ReturnValue.MapKey", "value", "manual"]
- ["org.apache.commons.collections4", "MapUtils", True, "unmodifiableSortedMap", "", "", "Argument[0].MapValue", "ReturnValue.MapValue", "value", "manual"]
- ["org.apache.commons.collections4", "MultiMap", True, "get", "", "", "Argument[this].MapValue.Element", "ReturnValue.Element", "value", "manual"]
- ["org.apache.commons.collections4", "MultiMap", True, "get", "", "", "Argument[this].MapValue", "ReturnValue", "value", "manual"]
- ["org.apache.commons.collections4", "MultiMap", True, "put", "", "", "Argument[0]", "Argument[this].MapKey", "value", "manual"]
- ["org.apache.commons.collections4", "MultiMap", True, "put", "", "", "Argument[1]", "Argument[this].MapValue.Element", "value", "manual"]
- ["org.apache.commons.collections4", "MultiMap", True, "values", "", "", "Argument[this].MapValue.Element", "ReturnValue.Element", "value", "manual"]
@@ -331,9 +331,9 @@ extensions:
- ["org.apache.commons.collections4", "OrderedMap", True, "lastKey", "", "", "Argument[this].MapKey", "ReturnValue", "value", "manual"]
- ["org.apache.commons.collections4", "OrderedMap", True, "nextKey", "", "", "Argument[this].MapKey", "ReturnValue", "value", "manual"]
- ["org.apache.commons.collections4", "OrderedMap", True, "previousKey", "", "", "Argument[this].MapKey", "ReturnValue", "value", "manual"]
- ["org.apache.commons.collections4", "Put", True, "put", "", "", "Argument[this].MapValue", "ReturnValue", "value", "manual"]
- ["org.apache.commons.collections4", "Put", True, "put", "", "", "Argument[0]", "Argument[this].MapKey", "value", "manual"]
- ["org.apache.commons.collections4", "Put", True, "put", "", "", "Argument[1]", "Argument[this].MapValue", "value", "manual"]
- ["org.apache.commons.collections4", "Put", True, "put", "(Object,Object)", "", "Argument[this].MapValue", "ReturnValue", "value", "manual"]
- ["org.apache.commons.collections4", "Put", True, "put", "(Object,Object)", "", "Argument[0]", "Argument[this].MapKey", "value", "manual"]
- ["org.apache.commons.collections4", "Put", True, "put", "(Object,Object)", "", "Argument[1]", "Argument[this].MapValue", "value", "manual"]
- ["org.apache.commons.collections4", "Put", True, "putAll", "(Map)", "", "Argument[0].MapKey", "Argument[this].MapKey", "value", "manual"]
- ["org.apache.commons.collections4", "Put", True, "putAll", "(Map)", "", "Argument[0].MapValue", "Argument[this].MapValue", "value", "manual"]
- ["org.apache.commons.collections4", "QueueUtils", True, "predicatedQueue", "", "", "Argument[0].Element", "ReturnValue.Element", "value", "manual"]

View File

@@ -20,7 +20,9 @@ extensions:
- ["org.apache.commons.collections4.set", "CompositeSet$SetMutator", True, "addAll", "", "", "Argument[2].Element", "Argument[0].Element", "value", "manual"]
- ["org.apache.commons.collections4.set", "CompositeSet$SetMutator", True, "addAll", "", "", "Argument[2].Element", "Argument[1].Element.Element", "value", "manual"]
- ["org.apache.commons.collections4.set", "ListOrderedSet", True, "add", "", "", "Argument[1]", "Argument[this].Element", "value", "manual"]
- ["org.apache.commons.collections4.set", "ListOrderedSet", True, "add", "(Object)", "", "Argument[0]", "Argument[this].Element", "value", "manual"]
- ["org.apache.commons.collections4.set", "ListOrderedSet", True, "addAll", "", "", "Argument[1].Element", "Argument[this].Element", "value", "manual"]
- ["org.apache.commons.collections4.set", "ListOrderedSet", True, "addAll", "(Collection)", "", "Argument[0].Element", "Argument[this].Element", "value", "manual"]
- ["org.apache.commons.collections4.set", "ListOrderedSet", True, "asList", "", "", "Argument[this].Element", "ReturnValue.Element", "value", "manual"]
- ["org.apache.commons.collections4.set", "ListOrderedSet", True, "get", "", "", "Argument[this].Element", "ReturnValue", "value", "manual"]
- ["org.apache.commons.collections4.set", "ListOrderedSet", True, "listOrderedSet", "(List)", "", "Argument[0].Element", "ReturnValue.Element", "value", "manual"]

View File

@@ -30,7 +30,7 @@ extensions:
- ["org.springframework.web.util", "DefaultUriBuilderFactory", False, "builder", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
- ["org.springframework.web.util", "DefaultUriBuilderFactory", False, "getDefaultUriVariables", "", "", "Argument[this]", "ReturnValue.MapValue", "taint", "manual"]
- ["org.springframework.web.util", "DefaultUriBuilderFactory", False, "setDefaultUriVariables", "", "", "Argument[0].MapValue", "Argument[this]", "taint", "manual"]
- ["org.springframework.web.util", "DefaultUriBuilderFactory", False, "uriString", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
- ["org.springframework.web.util", "DefaultUriBuilderFactory", False, "uriString", "", "", "Argument[this,0]", "ReturnValue", "taint", "manual"]
- ["org.springframework.web.util", "HtmlUtils", False, "htmlEscape", "", "", "Argument[0]", "ReturnValue", "taint", "manual"]
- ["org.springframework.web.util", "HtmlUtils", False, "htmlEscapeDecimal", "", "", "Argument[0]", "ReturnValue", "taint", "manual"]
- ["org.springframework.web.util", "HtmlUtils", False, "htmlEscapeHex", "", "", "Argument[0]", "ReturnValue", "taint", "manual"]
@@ -83,8 +83,7 @@ extensions:
- ["org.springframework.web.util", "UriBuilder", True, "userInfo", "", "", "Argument[this]", "ReturnValue", "value", "manual"]
- ["org.springframework.web.util", "UriBuilder", True, "userInfo", "", "", "Argument[0]", "Argument[this]", "taint", "manual"]
- ["org.springframework.web.util", "UriBuilderFactory", True, "builder", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
- ["org.springframework.web.util", "UriBuilderFactory", True, "uriString", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
- ["org.springframework.web.util", "UriBuilderFactory", True, "uriString", "", "", "Argument[0]", "ReturnValue", "taint", "manual"]
- ["org.springframework.web.util", "UriBuilderFactory", True, "uriString", "", "", "Argument[this,0]", "ReturnValue", "taint", "manual"]
- ["org.springframework.web.util", "UriComponents", False, "UriComponents", "", "", "Argument[0..1]", "Argument[this]", "taint", "manual"]
- ["org.springframework.web.util", "UriComponents", False, "copyToUriComponentsBuilder", "", "", "Argument[this]", "Argument[0]", "taint", "manual"]
- ["org.springframework.web.util", "UriComponents", False, "encode", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
@@ -107,6 +106,9 @@ extensions:
- ["org.springframework.web.util", "UriComponents", False, "toUriString", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
- ["org.springframework.web.util", "UriComponents$UriTemplateVariables", True, "getValue", "", "", "Argument[this].MapValue", "ReturnValue", "value", "manual"]
- ["org.springframework.web.util", "UriComponentsBuilder", False, "build", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
- ["org.springframework.web.util", "UriComponentsBuilder", False, "build", "(Map)", "", "Argument[0].MapValue", "Argument[this]", "taint", "manual"]
- ["org.springframework.web.util", "UriComponentsBuilder", False, "build", "(Map)", "", "Argument[0].MapValue", "ReturnValue", "taint", "manual"]
- ["org.springframework.web.util", "UriComponentsBuilder", False, "build", "(Object[])", "", "Argument[0].ArrayElement", "ReturnValue", "taint", "manual"]
- ["org.springframework.web.util", "UriComponentsBuilder", False, "buildAndExpand", "(Map)", "", "Argument[0].MapValue", "ReturnValue", "taint", "manual"]
- ["org.springframework.web.util", "UriComponentsBuilder", False, "buildAndExpand", "(Object[])", "", "Argument[0].ArrayElement", "ReturnValue", "taint", "manual"]
- ["org.springframework.web.util", "UriComponentsBuilder", False, "cloneBuilder", "", "", "Argument[this]", "ReturnValue", "value", "manual"]

View File

@@ -23,7 +23,7 @@ module Modification {
/** Holds if the call `c` modifies a shared resource. */
predicate isModifyingCall(Call c) {
exists(SummarizedCallable sc, string output | sc.getACall() = c |
sc.propagatesFlow(_, output, _, _) and
sc.propagatesFlow(_, output, _, _, _, _) and
output.matches("Argument[this]%")
)
}

View File

@@ -620,48 +620,25 @@ predicate barrierNode(Node node, string kind) { barrierNode(node, kind, _) }
// adapter class for converting Mad summaries to `SummarizedCallable`s
private class SummarizedCallableAdapter extends SummarizedCallable {
SummarizedCallableAdapter() { summaryElement(this, _, _, _, _, _, _) }
string input_;
string output_;
string kind;
Provenance p_;
boolean isExact_;
string model_;
private predicate relevantSummaryElementManual(
string input, string output, string kind, string model
) {
exists(Provenance provenance |
summaryElement(this, input, output, kind, provenance, model, _) and
provenance.isManual()
)
}
private predicate relevantSummaryElementGenerated(
string input, string output, string kind, string model
) {
exists(Provenance provenance |
summaryElement(this, input, output, kind, provenance, model, _) and
provenance.isGenerated()
) and
not exists(Provenance provenance |
neutralElement(this, "summary", provenance, _) and
provenance.isManual()
)
}
SummarizedCallableAdapter() { summaryElement(this, input_, output_, kind, p_, model_, isExact_) }
override predicate propagatesFlow(
string input, string output, boolean preservesValue, string model
string input, string output, boolean preservesValue, Provenance p, boolean isExact, string model
) {
exists(string kind |
this.relevantSummaryElementManual(input, output, kind, model)
or
not this.relevantSummaryElementManual(_, _, _, _) and
this.relevantSummaryElementGenerated(input, output, kind, model)
|
if kind = "value" then preservesValue = true else preservesValue = false
)
input = input_ and
output = output_ and
(if kind = "value" then preservesValue = true else preservesValue = false) and
p = p_ and
isExact = isExact_ and
model = model_
}
override predicate hasProvenance(Provenance provenance) {
summaryElement(this, _, _, _, provenance, _, _)
}
override predicate hasExactModel() { summaryElement(this, _, _, _, _, _, true) }
}
final class SinkCallable = SinkModelCallable;

View File

@@ -121,24 +121,31 @@ class SummarizedCallableBase extends TSummarizedCallableBase {
class Provenance = Impl::Public::Provenance;
class SummarizedCallable = Impl::Public::SummarizedCallable;
/** Provides the `Range` class used to define the extent of `SummarizedCallable`. */
module SummarizedCallable {
class Range = Impl::Public::SummarizedCallable;
}
class SummarizedCallable = Impl::Public::RelevantSummarizedCallable;
/**
* An adapter class to add the flow summaries specified on `SyntheticCallable`
* to `SummarizedCallable`.
*/
private class SummarizedSyntheticCallableAdapter extends SummarizedCallable, TSyntheticCallable {
private class SummarizedSyntheticCallableAdapter extends SummarizedCallable::Range,
TSyntheticCallable
{
override predicate propagatesFlow(
string input, string output, boolean preservesValue, string model
string input, string output, boolean preservesValue, Provenance p, boolean isExact, string model
) {
exists(SyntheticCallable sc |
sc = this.asSyntheticCallable() and
sc.propagatesFlow(input, output, preservesValue) and
p = "manual" and
isExact = true and
model = sc
)
}
override predicate hasExactModel() { any() }
}
deprecated class RequiredSummaryComponentStack = Impl::Private::RequiredSummaryComponentStack;

View File

@@ -12,7 +12,11 @@ private import semmle.code.java.dispatch.internal.Unification
private module DispatchImpl {
private predicate hasHighConfidenceTarget(Call c) {
exists(Impl::Public::SummarizedCallable sc | sc.getACall() = c and not sc.applyGeneratedModel())
exists(Impl::Public::SummarizedCallable sc, Impl::Public::Provenance p |
sc.getACall() = c and
sc.propagatesFlow(_, _, _, p, _, _) and
not p.isGenerated()
)
or
exists(Impl::Public::NeutralSummaryCallable nc | nc.getACall() = c and nc.hasManualModel())
or
@@ -25,8 +29,10 @@ private module DispatchImpl {
private predicate hasExactManualModel(Call c, Callable tgt) {
tgt = c.getCallee().getSourceDeclaration() and
(
exists(Impl::Public::SummarizedCallable sc |
sc.getACall() = c and sc.hasExactModel() and sc.hasManualModel()
exists(Impl::Public::SummarizedCallable sc, Impl::Public::Provenance p |
sc.getACall() = c and
sc.propagatesFlow(_, _, _, p, true, _) and
p.isManual()
)
or
exists(Impl::Public::NeutralSummaryCallable nc |
@@ -57,16 +63,6 @@ private module DispatchImpl {
exists(Call call | call = c.asCall() |
result.asCallable() = sourceDispatch(call)
or
not (
// Only use summarized callables with generated summaries in case
// the static call target is not in the source code.
// Note that if `applyGeneratedModel` holds it implies that there doesn't
// exist a manual model.
exists(Callable staticTarget | staticTarget = call.getCallee().getSourceDeclaration() |
staticTarget.fromSource() and not staticTarget.isStub()
) and
result.asSummarizedCallable().applyGeneratedModel()
) and
result.asSummarizedCallable().getACall() = call
)
}

View File

@@ -33,6 +33,10 @@ module Input implements InputSig<Location, DataFlowImplSpecific::JavaDataFlow> {
class SummarizedCallableBase = FlowSummary::SummarizedCallableBase;
predicate callableFromSource(SummarizedCallableBase sc) {
sc.asCallable() = any(Callable c | c.fromSource() and not c.isStub())
}
class SourceBase = Void;
class SinkBase = Void;

View File

@@ -68,19 +68,19 @@ private predicate mayInvokeCallback(SrcMethod m, int n) {
(not m.fromSource() or m.isNative() or m.getFile().getAbsolutePath().matches("%/test/stubs/%"))
}
private class SummarizedCallableWithCallback extends SummarizedCallable {
private class SummarizedCallableWithCallback extends SummarizedCallable::Range {
private int pos;
SummarizedCallableWithCallback() { mayInvokeCallback(this.asCallable(), pos) }
override predicate propagatesFlow(
string input, string output, boolean preservesValue, string model
string input, string output, boolean preservesValue, Provenance p, boolean isExact, string model
) {
input = "Argument[" + pos + "]" and
output = "Argument[" + pos + "].Parameter[-1]" and
preservesValue = true and
p = "hq-generated" and
isExact = true and
model = "heuristic-callback"
}
override predicate hasProvenance(Provenance provenance) { provenance = "hq-generated" }
}