Skip to content

Commit f61eb5f

Browse files
CEL Dev Teamcopybara-github
authored andcommitted
Add support for stdlib subsetting via CelEnvironment
PiperOrigin-RevId: 776693221
1 parent acd091b commit f61eb5f

13 files changed

Lines changed: 484 additions & 3 deletions

File tree

bundle/src/main/java/dev/cel/bundle/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ java_library(
6161
":required_fields_checker",
6262
"//:auto_value",
6363
"//bundle:cel",
64+
"//checker:standard_decl",
6465
"//common:compiler_common",
6566
"//common:options",
6667
"//common:source",
@@ -69,6 +70,7 @@ java_library(
6970
"//compiler:compiler_builder",
7071
"//extensions",
7172
"//extensions:optional_library",
73+
"//parser:macro",
7274
"//runtime",
7375
"@maven//:com_google_errorprone_error_prone_annotations",
7476
"@maven//:com_google_guava_guava",

bundle/src/main/java/dev/cel/bundle/CelEnvironment.java

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import static com.google.common.base.Preconditions.checkNotNull;
1818
import static com.google.common.base.Preconditions.checkState;
1919
import static com.google.common.collect.ImmutableList.toImmutableList;
20+
import static com.google.common.collect.ImmutableSet.toImmutableSet;
2021

2122
import com.google.auto.value.AutoValue;
2223
import com.google.common.annotations.VisibleForTesting;
@@ -25,6 +26,8 @@
2526
import com.google.common.collect.ImmutableSet;
2627
import com.google.errorprone.annotations.CanIgnoreReturnValue;
2728
import com.google.errorprone.annotations.CheckReturnValue;
29+
import dev.cel.checker.CelStandardDeclarations;
30+
import dev.cel.checker.CelStandardDeclarations.StandardFunction;
2831
import dev.cel.common.CelFunctionDecl;
2932
import dev.cel.common.CelOptions;
3033
import dev.cel.common.CelOverloadDecl;
@@ -41,6 +44,7 @@
4144
import dev.cel.compiler.CelCompilerBuilder;
4245
import dev.cel.extensions.CelExtensions;
4346
import dev.cel.extensions.CelOptionalLibrary;
47+
import dev.cel.parser.CelStandardMacro;
4448
import dev.cel.runtime.CelRuntimeBuilder;
4549
import java.util.Arrays;
4650
import java.util.Optional;
@@ -97,6 +101,9 @@ public abstract class CelEnvironment {
97101
/** New function declarations to add in the compilation environment. */
98102
public abstract ImmutableSet<FunctionDecl> functions();
99103

104+
/** Standard library subset (which macros, functions to include/exclude) */
105+
public abstract Optional<LibrarySubset> standardLibrarySubset();
106+
100107
/** Builder for {@link CelEnvironment}. */
101108
@AutoValue.Builder
102109
public abstract static class Builder {
@@ -141,6 +148,8 @@ public Builder setFunctions(FunctionDecl... functions) {
141148

142149
public abstract Builder setFunctions(ImmutableSet<FunctionDecl> functions);
143150

151+
public abstract Builder setStandardLibrarySubset(Optional<LibrarySubset> stdLibrarySubset);
152+
144153
@CheckReturnValue
145154
public abstract CelEnvironment build();
146155
}
@@ -180,6 +189,8 @@ public CelCompiler extend(CelCompiler celCompiler, CelOptions celOptions)
180189

181190
addAllCompilerExtensions(compilerBuilder, celOptions);
182191

192+
applyStandardLibrarySubset(compilerBuilder);
193+
183194
return compilerBuilder.build();
184195
} catch (RuntimeException e) {
185196
throw new CelEnvironmentException(e.getMessage(), e);
@@ -218,6 +229,75 @@ private void addAllRuntimeExtensions(CelRuntimeBuilder celRuntimeBuilder, CelOpt
218229
}
219230
}
220231

232+
private void applyStandardLibrarySubset(CelCompilerBuilder compilerBuilder) {
233+
if (!standardLibrarySubset().isPresent()) {
234+
return;
235+
}
236+
237+
LibrarySubset librarySubset = standardLibrarySubset().get();
238+
if (librarySubset.disabled()) {
239+
compilerBuilder.setStandardEnvironmentEnabled(false);
240+
return;
241+
}
242+
243+
compilerBuilder.setStandardEnvironmentEnabled(true);
244+
245+
if (librarySubset.macrosDisabled()) {
246+
compilerBuilder.setStandardMacros(ImmutableList.of());
247+
} else if (!librarySubset.includedMacros().isEmpty()) {
248+
compilerBuilder.setStandardMacros(
249+
librarySubset.includedMacros().stream()
250+
.map(name -> getStandardMacroOrThrow(name))
251+
.toList());
252+
} else if (!librarySubset.excludedMacros().isEmpty()) {
253+
ImmutableSet<CelStandardMacro> set =
254+
librarySubset.excludedMacros().stream()
255+
.map(name -> getStandardMacroOrThrow(name))
256+
.collect(toImmutableSet());
257+
compilerBuilder.setStandardMacros(
258+
CelStandardMacro.STANDARD_MACROS.stream().filter(macro -> !set.contains(macro)).toList());
259+
}
260+
261+
if (!librarySubset.includedFunctions().isEmpty()) {
262+
compilerBuilder.setStandardEnvironmentEnabled(false);
263+
compilerBuilder.setStandardDeclarations(
264+
CelStandardDeclarations.newBuilder()
265+
.includeFunctions(
266+
librarySubset.includedFunctions().stream()
267+
.map(name -> getStandardFunctionOrThrow(name))
268+
.toList())
269+
.build());
270+
} else if (!librarySubset.excludedFunctions().isEmpty()) {
271+
compilerBuilder.setStandardEnvironmentEnabled(false);
272+
compilerBuilder.setStandardDeclarations(
273+
CelStandardDeclarations.newBuilder()
274+
.excludeFunctions(
275+
librarySubset.excludedFunctions().stream()
276+
.map(name -> getStandardFunctionOrThrow(name))
277+
.toList())
278+
.build());
279+
280+
}
281+
}
282+
283+
private static CelStandardMacro getStandardMacroOrThrow(String macroName) {
284+
for (CelStandardMacro macro : CelStandardMacro.STANDARD_MACROS) {
285+
if (macro.getFunction().equals(macroName)) {
286+
return macro;
287+
}
288+
}
289+
throw new IllegalArgumentException("unrecognized standard macro `" + macroName + "'");
290+
}
291+
292+
private static StandardFunction getStandardFunctionOrThrow(String functionName) {
293+
for (StandardFunction function : StandardFunction.values()) {
294+
if (function.functionName().equals(functionName)) {
295+
return function;
296+
}
297+
}
298+
throw new IllegalArgumentException("unrecognized standard function `" + functionName + "'");
299+
}
300+
221301
private static CanonicalCelExtension getExtensionOrThrow(String extensionName) {
222302
CanonicalCelExtension extension = CEL_EXTENSION_CONFIG_MAP.get(extensionName);
223303
if (extension == null) {
@@ -624,4 +704,76 @@ void addRuntimeExtension(CelRuntimeBuilder runtimeBuilder, CelOptions options) {
624704
this.runtimeExtensionApplier = runtimeExtensionApplier;
625705
}
626706
}
707+
708+
/**
709+
* LibrarySubset indicates a subset of the macros and function supported by a subsettable
710+
* library.
711+
*/
712+
@AutoValue
713+
public abstract static class LibrarySubset {
714+
715+
/**
716+
* Disabled indicates whether the library has been disabled, typically only used for
717+
* default-enabled libraries like stdlib.
718+
*/
719+
public abstract boolean disabled();
720+
721+
/** DisableMacros disables macros for the given library. */
722+
public abstract boolean macrosDisabled();
723+
724+
/** IncludeMacros specifies a set of macro function names to include in the subset. */
725+
public abstract ImmutableSet<String> includedMacros();
726+
727+
/**
728+
* ExcludeMacros specifies a set of macro function names to exclude from the subset.
729+
*
730+
* <p>Note: if IncludedMacros is non-empty, then ExcludedMacros is ignored.
731+
*/
732+
public abstract ImmutableSet<String> excludedMacros();
733+
734+
/**
735+
* IncludeFunctions specifies a set of functions to include in the subset.
736+
*
737+
* <p>Note: the overloads specified in the subset need only specify their ID.
738+
* <p>Note: if IncludedFunctions is non-empty, then ExcludedFunctions is ignored.
739+
*/
740+
public abstract ImmutableSet<String> includedFunctions();
741+
742+
/**
743+
* ExcludeFunctions specifies the set of functions to exclude from the subset.
744+
*
745+
* <p>Note: the overloads specified in the subset need only specify their ID.
746+
*/
747+
public abstract ImmutableSet<String> excludedFunctions();
748+
749+
public static Builder newBuilder() {
750+
return new AutoValue_CelEnvironment_LibrarySubset.Builder()
751+
.setDisabled(true)
752+
.setMacrosDisabled(false)
753+
.setIncludedMacros(ImmutableSet.of())
754+
.setExcludedMacros(ImmutableSet.of())
755+
.setIncludedFunctions(ImmutableSet.of())
756+
.setExcludedFunctions(ImmutableSet.of());
757+
}
758+
759+
/** Builder for {@link LibrarySubset}. */
760+
@AutoValue.Builder
761+
public abstract static class Builder {
762+
763+
public abstract Builder setDisabled(boolean disabled);
764+
765+
public abstract Builder setMacrosDisabled(boolean disabled);
766+
767+
public abstract Builder setIncludedMacros(ImmutableSet<String> includedMacros);
768+
769+
public abstract Builder setExcludedMacros(ImmutableSet<String> excludedMacros);
770+
771+
public abstract Builder setIncludedFunctions(ImmutableSet<String> includedFunctions);
772+
773+
public abstract Builder setExcludedFunctions(ImmutableSet<String> excludedFunctions);
774+
775+
@CheckReturnValue
776+
public abstract LibrarySubset build();
777+
}
778+
}
627779
}

bundle/src/main/java/dev/cel/bundle/CelEnvironmentYamlParser.java

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import com.google.errorprone.annotations.CanIgnoreReturnValue;
3030
import dev.cel.bundle.CelEnvironment.ExtensionConfig;
3131
import dev.cel.bundle.CelEnvironment.FunctionDecl;
32+
import dev.cel.bundle.CelEnvironment.LibrarySubset;
3233
import dev.cel.bundle.CelEnvironment.OverloadDecl;
3334
import dev.cel.bundle.CelEnvironment.TypeDecl;
3435
import dev.cel.bundle.CelEnvironment.VariableDecl;
@@ -38,6 +39,7 @@
3839
import dev.cel.common.formats.YamlHelper.YamlNodeType;
3940
import dev.cel.common.formats.YamlParserContextImpl;
4041
import dev.cel.common.internal.CelCodePointArray;
42+
import java.util.Optional;
4143
import org.jspecify.annotations.Nullable;
4244
import org.yaml.snakeyaml.DumperOptions.FlowStyle;
4345
import org.yaml.snakeyaml.nodes.MappingNode;
@@ -337,6 +339,79 @@ private static ExtensionConfig parseExtension(ParserContext<Node> ctx, Node node
337339
return builder.build();
338340
}
339341

342+
private static LibrarySubset parseLibrarySubset(
343+
ParserContext<Node> ctx, Node node, boolean disabled) {
344+
LibrarySubset.Builder builder = LibrarySubset.newBuilder().setDisabled(disabled);
345+
MappingNode subsetMap = (MappingNode) node;
346+
for (NodeTuple nodeTuple : subsetMap.getValue()) {
347+
Node keyNode = nodeTuple.getKeyNode();
348+
long keyId = ctx.collectMetadata(keyNode);
349+
Node valueNode = nodeTuple.getValueNode();
350+
String keyName = ((ScalarNode) keyNode).getValue();
351+
System.out.println("keyId = " + keyId + " keyName = " + keyName);
352+
switch (keyName) {
353+
case "disabled":
354+
builder.setDisabled(newBoolean(ctx, valueNode));
355+
break;
356+
case "disable_macros":
357+
builder.setMacrosDisabled(newBoolean(ctx, valueNode));
358+
break;
359+
case "include_macros":
360+
builder.setIncludedMacros(parseNameSet(ctx, keyName, valueNode));
361+
break;
362+
case "exclude_macros":
363+
builder.setExcludedMacros(parseNameSet(ctx, keyName, valueNode));
364+
break;
365+
case "include_functions":
366+
builder.setIncludedFunctions(parseNameSet(ctx, keyName, valueNode));
367+
break;
368+
case "exclude_functions":
369+
builder.setExcludedFunctions(parseNameSet(ctx, keyName, valueNode));
370+
break;
371+
default:
372+
ctx.reportError(keyId, String.format("Unsupported library subset tag: %s", keyName));
373+
break;
374+
}
375+
}
376+
return builder.build();
377+
}
378+
379+
private static ImmutableSet<String> parseNameSet(
380+
ParserContext<Node> ctx, String listKey, Node node) {
381+
long valueId = ctx.collectMetadata(node);
382+
if (!assertYamlType(ctx, valueId, node, YamlNodeType.LIST)) {
383+
return ImmutableSet.of(ERROR);
384+
}
385+
386+
ImmutableSet.Builder<String> builder = ImmutableSet.builder();
387+
SequenceNode nameListNode = (SequenceNode) node;
388+
for (Node elementNode : nameListNode.getValue()) {
389+
long elementId = ctx.collectMetadata(elementNode);
390+
System.out.println("NODE:" + elementNode);
391+
if (!assertYamlType(ctx, elementId, elementNode, YamlNodeType.MAP)) {
392+
return ImmutableSet.of(ERROR);
393+
}
394+
395+
for (NodeTuple nodeTuple : ((MappingNode) elementNode).getValue()) {
396+
Node keyNode = nodeTuple.getKeyNode();
397+
long keyId = ctx.collectMetadata(keyNode);
398+
Node valueNode = nodeTuple.getValueNode();
399+
String keyName = ((ScalarNode) keyNode).getValue();
400+
System.out.println("IN LIST keyId = " + keyId + " keyName = " + keyName);
401+
switch (keyName) {
402+
case "name":
403+
builder.add(newString(ctx, valueNode));
404+
break;
405+
default:
406+
ctx.reportError(
407+
keyId, String.format("Unsupported %s element tag: %s", listKey, keyName));
408+
break;
409+
}
410+
}
411+
}
412+
return builder.build();
413+
}
414+
340415
private static @Nullable Integer tryParse(String str) {
341416
try {
342417
return Integer.parseInt(str);
@@ -477,6 +552,10 @@ private CelEnvironment.Builder parseConfig(ParserContext<Node> ctx, Node node) {
477552
case "extensions":
478553
builder.addExtensions(parseExtensions(ctx, valueNode));
479554
break;
555+
case "stdlib":
556+
builder.setStandardLibrarySubset(
557+
Optional.of(parseLibrarySubset(ctx, valueNode, /* disabled= */ false)));
558+
break;
480559
default:
481560
ctx.reportError(id, "Unknown config tag: " + fieldName);
482561
// continue handling the rest of the nodes

bundle/src/main/java/dev/cel/bundle/CelEnvironmentYamlSerializer.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
package dev.cel.bundle;
1616

1717
import com.google.common.collect.ImmutableMap;
18+
import dev.cel.bundle.CelEnvironment.LibrarySubset;
19+
import java.util.List;
20+
import java.util.Set;
1821
import org.yaml.snakeyaml.DumperOptions;
1922
import org.yaml.snakeyaml.Yaml;
2023
import org.yaml.snakeyaml.nodes.Node;
@@ -45,6 +48,7 @@ private CelEnvironmentYamlSerializer() {
4548
this.multiRepresenters.put(CelEnvironment.TypeDecl.class, new RepresentTypeDecl());
4649
this.multiRepresenters.put(
4750
CelEnvironment.ExtensionConfig.class, new RepresentExtensionConfig());
51+
this.multiRepresenters.put(CelEnvironment.LibrarySubset.class, new RepresetLibrarySubset());
4852
}
4953

5054
public static String toYaml(CelEnvironment environment) {
@@ -74,6 +78,9 @@ public Node representData(Object data) {
7478
if (!environment.functions().isEmpty()) {
7579
configMap.put("functions", environment.functions().asList());
7680
}
81+
if (environment.standardLibrarySubset().isPresent()) {
82+
configMap.put("stdlib", environment.standardLibrarySubset().get());
83+
}
7784
return represent(configMap.buildOrThrow());
7885
}
7986
}
@@ -143,4 +150,35 @@ public Node representData(Object data) {
143150
return represent(configMap.buildOrThrow());
144151
}
145152
}
153+
154+
private final class RepresetLibrarySubset implements Represent {
155+
@Override
156+
public Node representData(Object data) {
157+
LibrarySubset librarySubset = (LibrarySubset) data;
158+
ImmutableMap.Builder<String, Object> configMap = new ImmutableMap.Builder<>();
159+
if (librarySubset.disabled()) {
160+
configMap.put("disabled", true);
161+
}
162+
if (librarySubset.macrosDisabled()) {
163+
configMap.put("disable_macros", true);
164+
}
165+
if (!librarySubset.includedMacros().isEmpty()) {
166+
configMap.put("include_macros", toListOfNames(librarySubset.includedMacros()));
167+
}
168+
if (!librarySubset.excludedMacros().isEmpty()) {
169+
configMap.put("exclude_macros", toListOfNames(librarySubset.excludedMacros()));
170+
}
171+
if (!librarySubset.includedFunctions().isEmpty()) {
172+
configMap.put("include_functions", toListOfNames(librarySubset.includedFunctions()));
173+
}
174+
if (!librarySubset.excludedFunctions().isEmpty()) {
175+
configMap.put("exclude_functions", toListOfNames(librarySubset.excludedFunctions()));
176+
}
177+
return represent(configMap.buildOrThrow());
178+
}
179+
180+
private List<ImmutableMap<String, String>> toListOfNames(Set<String> names) {
181+
return names.stream().sorted().map(name -> ImmutableMap.of("name", name)).toList();
182+
}
183+
}
146184
}

0 commit comments

Comments
 (0)