Skip to content

Commit 883af6d

Browse files
l46kokcopybara-github
authored andcommitted
Give each uninterpreted overload its own solver symbol
PiperOrigin-RevId: 1003517315
1 parent f635f61 commit 883af6d

2 files changed

Lines changed: 68 additions & 1 deletion

File tree

‎verifier/src/main/java/dev/cel/verifier/CelAstToZ3Translator.java‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import com.google.common.collect.ImmutableList;
1818
import com.google.common.collect.ImmutableSet;
19+
import com.google.common.collect.ImmutableSortedSet;
1920
import com.google.common.collect.Iterables;
2021
import com.google.common.collect.Streams;
2122
import com.microsoft.z3.ArithExpr;
@@ -83,6 +84,7 @@ final class CelAstToZ3Translator {
8384
private static final String EMPTY_MSG_REF_PREFIX = "!empty_msg_ref_";
8485
private static final String EMPTY_LIST_PREFIX = "!empty_list";
8586
private static final String EMPTY_MAP_PREFIX = "!empty_map";
87+
private static final String FUNCTION_PREFIX = "!fn_";
8688
private static final String NULL_VALUE_FIELD = "null_value";
8789
private static final String SECONDS_FIELD = "seconds";
8890
private static final String NANOS_FIELD = "nanos";
@@ -927,7 +929,10 @@ private TranslatedValue translateCall(CelExpr expr, CelAbstractSyntaxTree ast) {
927929
Sort[] argSorts = new Sort[args.size()];
928930
Arrays.fill(argSorts, 0, args.size(), typeSystem.celValueSort());
929931
FuncDecl<?> funcDecl =
930-
typeSystem.internFuncDecl(functionName, argSorts, typeSystem.celValueSort());
932+
typeSystem.internFuncDecl(
933+
uninterpretedFunctionSymbol(exprId, ast),
934+
argSorts,
935+
typeSystem.celValueSort());
931936

932937
Expr<?>[] exprArgs = args.stream().map(TranslatedValue::z3Expr).toArray(Expr[]::new);
933938
Expr<?> callRes = ctx.mkApp(funcDecl, exprArgs);
@@ -947,6 +952,17 @@ private TranslatedValue translateCall(CelExpr expr, CelAbstractSyntaxTree ast) {
947952
});
948953
}
949954

955+
/**
956+
* Returns the Z3 symbol for an uninterpreted call. Each set of candidate overloads gets its own
957+
* symbol, since distinct overloads may have different implementations even on equal values (e.g.
958+
* empty lists of different element types). The prefix keeps it apart from variables and internal
959+
* symbols.
960+
*/
961+
private static String uninterpretedFunctionSymbol(long exprId, CelAbstractSyntaxTree ast) {
962+
ImmutableList<String> overloadIds = ast.getReferenceOrThrow(exprId).overloadIds();
963+
return FUNCTION_PREFIX + String.join("|", ImmutableSortedSet.copyOf(overloadIds));
964+
}
965+
950966
/**
951967
* Whether a call may construct a container that embeds its args (e.g. list concatenation, map
952968
* insertion or optional.of). Indexing and conditionals return existing values instead, so

‎verifier/src/test/java/dev/cel/verifier/CelVerifierZ3ImplTest.java‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2802,6 +2802,57 @@ public void isAlwaysTrue_boolFunction_verified() throws Exception {
28022802
assertThat(result.status()).isEqualTo(VerificationStatus.VERIFIED);
28032803
}
28042804

2805+
// Each pair of calls looks alike but may have different implementations, so equating them would
2806+
// be a false VERIFIED. Any counterexample depends on the values the functions return.
2807+
@Test
2808+
public void isAlwaysTrue_distinctFunctionSymbols_inconclusive(
2809+
@TestParameter({
2810+
// Member and global overloads of the same name.
2811+
"fx(role, country) == role.fx(country)",
2812+
// Overloads whose argument values coincide (empty lists of different element types).
2813+
"size(int_list) == 0 && size(string_list) == 0 ? f(int_list) == f(string_list) : true"
2814+
})
2815+
String expression)
2816+
throws Exception {
2817+
CelCompiler celCompiler =
2818+
CEL.toCompilerBuilder()
2819+
.addVar("string_list", ListType.create(SimpleType.STRING))
2820+
.addFunctionDeclarations(
2821+
CelFunctionDecl.newFunctionDeclaration(
2822+
"fx",
2823+
CelOverloadDecl.newGlobalOverload(
2824+
"fx_global", SimpleType.BOOL, SimpleType.STRING, SimpleType.STRING),
2825+
CelOverloadDecl.newMemberOverload(
2826+
"fx_member", SimpleType.BOOL, SimpleType.STRING, SimpleType.STRING)),
2827+
CelFunctionDecl.newFunctionDeclaration(
2828+
"f",
2829+
CelOverloadDecl.newGlobalOverload(
2830+
"f_list_int", SimpleType.INT, ListType.create(SimpleType.INT)),
2831+
CelOverloadDecl.newGlobalOverload(
2832+
"f_list_string", SimpleType.INT, ListType.create(SimpleType.STRING))))
2833+
.build();
2834+
CelAbstractSyntaxTree ast = celCompiler.compile(expression).getAst();
2835+
CelVerificationResult result = VERIFIER.isAlwaysTrue(ast);
2836+
2837+
assertThat(result.status()).isEqualTo(VerificationStatus.INCONCLUSIVE);
2838+
}
2839+
2840+
@Test
2841+
public void isAlwaysTrue_zeroArityFunctionAndSameNamedVariable_inconclusive() throws Exception {
2842+
CelCompiler celCompiler =
2843+
CEL.toCompilerBuilder()
2844+
.addVar("g", SimpleType.INT)
2845+
.addFunctionDeclarations(
2846+
CelFunctionDecl.newFunctionDeclaration(
2847+
"g", CelOverloadDecl.newGlobalOverload("g_zero", SimpleType.INT)))
2848+
.build();
2849+
CelAbstractSyntaxTree ast = celCompiler.compile("g() == g").getAst();
2850+
2851+
CelVerificationResult result = VERIFIER.isAlwaysTrue(ast);
2852+
2853+
assertThat(result.status()).isEqualTo(VerificationStatus.INCONCLUSIVE);
2854+
}
2855+
28052856
@Test
28062857
public void verifyEquivalence_identicalFunctionCalls_equivalent() throws Exception {
28072858
// Identical calls to an uninterpreted function always produce identical results.

0 commit comments

Comments
 (0)