Skip to content

Commit cdcd194

Browse files
dmitriplotnikovcopybara-github
authored andcommitted
Add support for stdlib subsetting via CelEnvironment
PiperOrigin-RevId: 778229084
1 parent 52015ce commit cdcd194

13 files changed

Lines changed: 881 additions & 5 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: 268 additions & 1 deletion
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,10 @@
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.bundle.CelEnvironment.LibrarySubset.FunctionSelector;
30+
import dev.cel.checker.CelStandardDeclarations;
31+
import dev.cel.checker.CelStandardDeclarations.StandardFunction;
32+
import dev.cel.checker.CelStandardDeclarations.StandardOverload;
2833
import dev.cel.common.CelFunctionDecl;
2934
import dev.cel.common.CelOptions;
3035
import dev.cel.common.CelOverloadDecl;
@@ -41,6 +46,7 @@
4146
import dev.cel.compiler.CelCompilerBuilder;
4247
import dev.cel.extensions.CelExtensions;
4348
import dev.cel.extensions.CelOptionalLibrary;
49+
import dev.cel.parser.CelStandardMacro;
4450
import dev.cel.runtime.CelRuntimeBuilder;
4551
import java.util.Arrays;
4652
import java.util.Optional;
@@ -97,6 +103,9 @@ public abstract class CelEnvironment {
97103
/** New function declarations to add in the compilation environment. */
98104
public abstract ImmutableSet<FunctionDecl> functions();
99105

106+
/** Standard library subset (which macros, functions to include/exclude) */
107+
public abstract Optional<LibrarySubset> standardLibrarySubset();
108+
100109
/** Builder for {@link CelEnvironment}. */
101110
@AutoValue.Builder
102111
public abstract static class Builder {
@@ -141,8 +150,28 @@ public Builder setFunctions(FunctionDecl... functions) {
141150

142151
public abstract Builder setFunctions(ImmutableSet<FunctionDecl> functions);
143152

153+
public abstract Builder setStandardLibrarySubset(LibrarySubset stdLibrarySubset);
154+
155+
abstract CelEnvironment autoBuild();
156+
144157
@CheckReturnValue
145-
public abstract CelEnvironment build();
158+
public final CelEnvironment build() {
159+
CelEnvironment env = autoBuild();
160+
LibrarySubset librarySubset = env.standardLibrarySubset().orElse(null);
161+
if (librarySubset != null) {
162+
if (!librarySubset.includedMacros().isEmpty()
163+
&& !librarySubset.excludedMacros().isEmpty()) {
164+
throw new IllegalArgumentException(
165+
"Invalid subset: cannot both include and exclude macros");
166+
}
167+
if (!librarySubset.includedFunctions().isEmpty()
168+
&& !librarySubset.excludedFunctions().isEmpty()) {
169+
throw new IllegalArgumentException(
170+
"Invalid subset: cannot both include and exclude functions");
171+
}
172+
}
173+
return env;
174+
}
146175
}
147176

148177
/** Creates a new builder to construct a {@link CelEnvironment} instance. */
@@ -180,6 +209,8 @@ public CelCompiler extend(CelCompiler celCompiler, CelOptions celOptions)
180209

181210
addAllCompilerExtensions(compilerBuilder, celOptions);
182211

212+
applyStandardLibrarySubset(compilerBuilder);
213+
183214
return compilerBuilder.build();
184215
} catch (RuntimeException e) {
185216
throw new CelEnvironmentException(e.getMessage(), e);
@@ -218,6 +249,67 @@ private void addAllRuntimeExtensions(CelRuntimeBuilder celRuntimeBuilder, CelOpt
218249
}
219250
}
220251

252+
private void applyStandardLibrarySubset(CelCompilerBuilder compilerBuilder) {
253+
if (!standardLibrarySubset().isPresent()) {
254+
return;
255+
}
256+
257+
LibrarySubset librarySubset = standardLibrarySubset().get();
258+
if (librarySubset.disabled()) {
259+
compilerBuilder.setStandardEnvironmentEnabled(false);
260+
return;
261+
}
262+
263+
if (librarySubset.macrosDisabled()) {
264+
compilerBuilder.setStandardMacros(ImmutableList.of());
265+
} else if (!librarySubset.includedMacros().isEmpty()) {
266+
compilerBuilder.setStandardMacros(
267+
librarySubset.includedMacros().stream()
268+
.map(CelEnvironment::getStandardMacroOrThrow)
269+
.collect(toImmutableSet()));
270+
} else if (!librarySubset.excludedMacros().isEmpty()) {
271+
ImmutableSet<CelStandardMacro> set =
272+
librarySubset.excludedMacros().stream()
273+
.map(CelEnvironment::getStandardMacroOrThrow)
274+
.collect(toImmutableSet());
275+
compilerBuilder.setStandardMacros(
276+
CelStandardMacro.STANDARD_MACROS.stream()
277+
.filter(macro -> !set.contains(macro))
278+
.collect(toImmutableSet()));
279+
}
280+
281+
if (!librarySubset.includedFunctions().isEmpty()) {
282+
ImmutableSet<FunctionSelector> includedFunctions = librarySubset.includedFunctions();
283+
compilerBuilder
284+
.setStandardEnvironmentEnabled(false)
285+
.setStandardDeclarations(
286+
CelStandardDeclarations.newBuilder()
287+
.filterFunctions(
288+
(function, overload) ->
289+
FunctionSelector.matchesAny(function, overload, includedFunctions))
290+
.build());
291+
} else if (!librarySubset.excludedFunctions().isEmpty()) {
292+
ImmutableSet<FunctionSelector> excludedFunctions = librarySubset.excludedFunctions();
293+
compilerBuilder
294+
.setStandardEnvironmentEnabled(false)
295+
.setStandardDeclarations(
296+
CelStandardDeclarations.newBuilder()
297+
.filterFunctions(
298+
(function, overload) ->
299+
!FunctionSelector.matchesAny(function, overload, excludedFunctions))
300+
.build());
301+
}
302+
}
303+
304+
private static CelStandardMacro getStandardMacroOrThrow(String macroName) {
305+
for (CelStandardMacro macro : CelStandardMacro.STANDARD_MACROS) {
306+
if (macro.getFunction().equals(macroName)) {
307+
return macro;
308+
}
309+
}
310+
throw new IllegalArgumentException("unrecognized standard macro `" + macroName + "'");
311+
}
312+
221313
private static CanonicalCelExtension getExtensionOrThrow(String extensionName) {
222314
CanonicalCelExtension extension = CEL_EXTENSION_CONFIG_MAP.get(extensionName);
223315
if (extension == null) {
@@ -624,4 +716,179 @@ void addRuntimeExtension(CelRuntimeBuilder runtimeBuilder, CelOptions options) {
624716
this.runtimeExtensionApplier = runtimeExtensionApplier;
625717
}
626718
}
719+
720+
/**
721+
* LibrarySubset indicates a subset of the macros and function supported by a subsettable
722+
* library.
723+
*/
724+
@AutoValue
725+
public abstract static class LibrarySubset {
726+
727+
/**
728+
* Disabled indicates whether the library has been disabled, typically only used for
729+
* default-enabled libraries like stdlib.
730+
*/
731+
public abstract boolean disabled();
732+
733+
/** DisableMacros disables macros for the given library. */
734+
public abstract boolean macrosDisabled();
735+
736+
/** IncludeMacros specifies a set of macro function names to include in the subset. */
737+
public abstract ImmutableSet<String> includedMacros();
738+
739+
/**
740+
* ExcludeMacros specifies a set of macro function names to exclude from the subset.
741+
*
742+
* <p>Note: if IncludedMacros is non-empty, then ExcludedMacros is ignored.
743+
*/
744+
public abstract ImmutableSet<String> excludedMacros();
745+
746+
/**
747+
* IncludeFunctions specifies a set of functions to include in the subset.
748+
*
749+
* <p>Note: the overloads specified in the subset need only specify their ID.
750+
* <p>Note: if IncludedFunctions is non-empty, then ExcludedFunctions is ignored.
751+
*/
752+
public abstract ImmutableSet<FunctionSelector> includedFunctions();
753+
754+
/**
755+
* ExcludeFunctions specifies the set of functions to exclude from the subset.
756+
*
757+
* <p>Note: the overloads specified in the subset need only specify their ID.
758+
*/
759+
public abstract ImmutableSet<FunctionSelector> excludedFunctions();
760+
761+
public static Builder newBuilder() {
762+
return new AutoValue_CelEnvironment_LibrarySubset.Builder()
763+
.setMacrosDisabled(false)
764+
.setIncludedMacros(ImmutableSet.of())
765+
.setExcludedMacros(ImmutableSet.of())
766+
.setIncludedFunctions(ImmutableSet.of())
767+
.setExcludedFunctions(ImmutableSet.of());
768+
}
769+
770+
/** Builder for {@link LibrarySubset}. */
771+
@AutoValue.Builder
772+
public abstract static class Builder {
773+
774+
public abstract Builder setDisabled(boolean disabled);
775+
776+
public abstract Builder setMacrosDisabled(boolean disabled);
777+
778+
public abstract Builder setIncludedMacros(ImmutableSet<String> includedMacros);
779+
780+
public abstract Builder setExcludedMacros(ImmutableSet<String> excludedMacros);
781+
782+
public abstract Builder setIncludedFunctions(
783+
ImmutableSet<FunctionSelector> includedFunctions);
784+
785+
public abstract Builder setExcludedFunctions(
786+
ImmutableSet<FunctionSelector> excludedFunctions);
787+
788+
@CheckReturnValue
789+
public abstract LibrarySubset build();
790+
}
791+
792+
/**
793+
* Represents a function selector, which can be used to configure included/excluded library
794+
* functions.
795+
*/
796+
@AutoValue
797+
public abstract static class FunctionSelector {
798+
799+
public abstract String name();
800+
801+
public abstract ImmutableSet<OverloadSelector> overloads();
802+
803+
/** Builder for {@link FunctionSelector}. */
804+
@AutoValue.Builder
805+
public abstract static class Builder implements RequiredFieldsChecker {
806+
807+
public abstract Optional<String> name();
808+
809+
public abstract Builder setName(String name);
810+
811+
public abstract Builder setOverloads(ImmutableSet<OverloadSelector> overloads);
812+
813+
@Override
814+
public ImmutableList<RequiredField> requiredFields() {
815+
return ImmutableList.of(RequiredField.of("name", this::name));
816+
}
817+
818+
/** Builds a new instance of {@link FunctionSelector}. */
819+
public abstract FunctionSelector build();
820+
}
821+
822+
/** Creates a new builder to construct a {@link FunctionSelector} instance. */
823+
public static FunctionSelector.Builder newBuilder() {
824+
return new AutoValue_CelEnvironment_LibrarySubset_FunctionSelector.Builder()
825+
.setOverloads(ImmutableSet.of());
826+
}
827+
828+
public static FunctionSelector create(String name, ImmutableSet<String> overloads) {
829+
return newBuilder()
830+
.setName(name)
831+
.setOverloads(
832+
overloads.stream()
833+
.map(id -> OverloadSelector.newBuilder().setId(id).build())
834+
.collect(toImmutableSet()))
835+
.build();
836+
}
837+
838+
private static boolean matchesAny(
839+
StandardFunction function,
840+
StandardOverload overload,
841+
ImmutableSet<FunctionSelector> selectors) {
842+
String functionName = function.functionDecl().name();
843+
for (FunctionSelector functionSelector : selectors) {
844+
if (!functionSelector.name().equals(functionName)) {
845+
continue;
846+
}
847+
848+
if (functionSelector.overloads().isEmpty()) {
849+
return true;
850+
}
851+
852+
String overloadId = overload.celOverloadDecl().overloadId();
853+
for (OverloadSelector overloadSelector : functionSelector.overloads()) {
854+
if (overloadSelector.id().equals(overloadId)) {
855+
return true;
856+
}
857+
}
858+
}
859+
return false;
860+
}
861+
}
862+
863+
/** Represents an overload selector on a function selector. */
864+
@AutoValue
865+
public abstract static class OverloadSelector {
866+
867+
/** An overload ID. Required. Follows the same format as {@link OverloadDecl#id()} */
868+
public abstract String id();
869+
870+
/** Builder for {@link OverloadSelector}. */
871+
@AutoValue.Builder
872+
public abstract static class Builder implements RequiredFieldsChecker {
873+
874+
public abstract Optional<String> id();
875+
876+
public abstract Builder setId(String overloadId);
877+
878+
@Override
879+
public ImmutableList<RequiredField> requiredFields() {
880+
return ImmutableList.of(RequiredField.of("id", this::id));
881+
}
882+
883+
/** Builds a new instance of {@link OverloadSelector}. */
884+
@CheckReturnValue
885+
public abstract OverloadSelector build();
886+
}
887+
888+
/** Creates a new builder to construct a {@link OverloadSelector} instance. */
889+
public static OverloadSelector.Builder newBuilder() {
890+
return new AutoValue_CelEnvironment_LibrarySubset_OverloadSelector.Builder();
891+
}
892+
}
893+
}
627894
}

0 commit comments

Comments
 (0)