-
Notifications
You must be signed in to change notification settings - Fork 24.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Painless: Add Bindings #33042
Merged
Merged
Painless: Add Bindings #33042
Changes from 123 commits
Commits
Show all changes
127 commits
Select commit
Hold shift + click to select a range
1419751
Parially added constructor.
jdconrad 6469598
Add method type to method.
jdconrad 54b8992
Merge branch 'master' into clean18
jdconrad 8581deb
Merge branch 'clean16' into clean19
jdconrad 641c383
Add PainlessConstructor.
jdconrad 5b449d8
Clean up method.
jdconrad 3c5db32
Merge branch 'master' into clean19
jdconrad c19b95d
Merge branch 'clean19' into clean20
jdconrad 266a92d
Fixes.
jdconrad 2875c48
Clean up fields.
jdconrad 3f17455
Merge branch 'master' into clean19
jdconrad b9299d6
Merge branch 'clean19' into clean20
jdconrad aa833d9
Merge branch 'clean20' into clean21
jdconrad 0c9c174
Reponse to PR comments.
jdconrad 683361a
Merge branch 'clean19' into clean20
jdconrad 6757987
Merge branch 'clean20' into clean21
jdconrad ccabb90
Merge branch 'master' into clean19
jdconrad 55004f1
Merge branch 'clean19' into clean20
jdconrad 9ca9381
Merge branch 'master' into clean20
jdconrad f379f2d
Merge branch 'clean20' into clean21
jdconrad 98995f2
Start to clean up PainlessLookup.
jdconrad 8bd9a28
Complete method lookup.
jdconrad 6eaca81
Add lookup constructor.
jdconrad aab122a
Add field lookup.
jdconrad 509e607
Merge branch 'master' into clean20
jdconrad aff5c3e
Reponse to PR comments.
jdconrad dbc3e27
Merge branch 'clean20' into clean21
jdconrad 43d5e49
Merge branch 'clean21' into clean22
jdconrad dfa6de1
Merge branch 'master' into clean20
jdconrad 36f41af
Merge branch 'clean20' into clean21
jdconrad 2265836
Merge branch 'clean21' into clean22
jdconrad 57e2a46
Add local methods to def bootstrap for consistency in look ups.
jdconrad bd2d854
Merge branch 'master' into clean21
jdconrad 7187e4f
Merge branch 'master' into clean21
jdconrad 69da907
Merge branch 'clean21' into clean22
jdconrad eb3bb9f
Merge branch 'clean22' into clean24
jdconrad 4da7113
Add FunctionReference.
jdconrad 1d8b547
Progress.
jdconrad e51f3df
Progress.
jdconrad a082624
Progress.
jdconrad 89e936e
Fix.
jdconrad cf430a9
Progress.
jdconrad c4e8357
Large clean up of function reference.
jdconrad bde26b4
A bit more clean up.
jdconrad b3ed1b7
Merge branch 'master' into clean22
jdconrad 264f6a4
Merge branch 'clean22' into clean24
jdconrad 2cf04ec
Merge branch 'clean24' into clean25
jdconrad 02ab981
Move runtime method lookup to PainlessLookup.
jdconrad 551dc4e
Add getter/setter lookup methods to PainlessLookup.
jdconrad c1d9d25
Rename.
jdconrad 73bfbdf
Mechanical change to rename variables in PainlessCast.
jdconrad 581f359
Clean up whitelist names.
jdconrad 87820b9
Rename only_fqn to no_import.
jdconrad 4255c3c
Merge branch 'master' into clean22
jdconrad 9d5f02f
Remove unnecessary sorted classes list.
jdconrad a255273
Merge branch 'clean22' into clean24
jdconrad 4dbb100
Merge branch 'clean24' into clean25
jdconrad 9609b45
Merge branch 'clean25' into clean26
jdconrad f27f954
Merge branch 'clean26' into clean27
jdconrad 172df36
Merge branch 'clean27' into clean28
jdconrad 8d8cce4
Merge branch 'clean28' into clean29
jdconrad 12b66d8
Special case def.
jdconrad 64933d1
Merge branch 'master' into clean22
jdconrad 61737be
Merge branch 'clean22' into clean24
jdconrad 67f4140
Merge branch 'clean24' into clean25
jdconrad 8471f6b
Merge branch 'clean25' into clean26
jdconrad 61946df
Merge branch 'clean26' into clean27
jdconrad ef3356a
Merge branch 'clean27' into clean28
jdconrad eb20b67
Merge branch 'clean28' into clean29
jdconrad 03f68e6
Merge branch 'clean29' into clean30
jdconrad d7d0292
Remove extraneous line.
jdconrad aac6d88
Change Object to Map.
jdconrad 4051638
Fix import.
jdconrad b65f459
Merge branch 'master' into clean24
jdconrad f071c78
Merge branch 'clean24' into clean25
jdconrad 8e2a6af
Merge branch 'master' into clean25
jdconrad 9921693
Merge branch 'clean25' into clean26
jdconrad c42e64e
Merge branch 'clean26' into clean27
jdconrad 6e5e7ad
Merge branch 'clean27' into clean28
jdconrad 78f70b6
Merge branch 'clean28' into clean29
jdconrad 6b17a51
Merge branch 'clean29' into clean30
jdconrad af80f8e
Merge branch 'master' into clean25
jdconrad 0b8ebdf
Fix formatting.
jdconrad b7812d6
Add FunctionRef back.
jdconrad c7ebe54
Remove extraneous character.
jdconrad 28f6e60
Merge branch 'master' into clean25
jdconrad b98030a
Merge branch 'clean25' into clean26
jdconrad fc578ed
Merge branch 'master' into clean26
jdconrad ab9a805
Merge branch 'clean26' into clean27
jdconrad 058c988
Merge branch 'clean27' into clean28
jdconrad 4ebb939
Merge branch 'clean28' into clean29
jdconrad 128f668
Merge branch 'clean29' into clean30
jdconrad 1d3d2fc
Merge branch 'master' into clean26
jdconrad 804f529
Return null instead of throwing exceptions for all methods in
jdconrad 973c1b2
Merge branch 'master' into clean26
jdconrad f05f3d9
Merge branch 'clean26' into clean27
jdconrad a1a166b
Merge branch 'clean27' into clean28
jdconrad a5a0070
Merge branch 'clean28' into clean29
jdconrad ce8de87
Merge branch 'clean29' into clean30
jdconrad 4240f75
Merge branch 'master' into clean27
jdconrad 1939f16
Merge branch 'clean27' into clean28
jdconrad 3f2cda7
Merge branch 'clean28' into clean29
jdconrad 6077300
Merge branch 'clean29' into clean30
jdconrad e617201
Merge branch 'master' into clean27
jdconrad 0cbe7c5
Merge branch 'clean27' into clean28
jdconrad 8886264
Merge branch 'master' into clean28
jdconrad b6c6bd5
Merge branch 'clean28' into clean29
jdconrad 1e25bc5
Merge branch 'clean29' into clean30
jdconrad b535937
Merge branch 'master' into clean29
jdconrad 21d09f0
Merge branch 'master' into clean29
jdconrad 68637a1
Merge branch 'clean29' into clean30
jdconrad fde19b3
Merge branch 'master' into clean30
jdconrad 8836c20
Merge branch 'master' into clean30
jdconrad b5498e9
Some progress.
jdconrad 87bc424
Merge branch 'master' into clean30
jdconrad 9e57635
Merge branch 'clean30' into bindings
jdconrad 5536cdb
Merge branch 'master' into bindings
jdconrad 7fbea29
Completion of adding bindings to lookup.
jdconrad 059cf4c
Added bindings.
jdconrad d04deb0
Merge branch 'master' into bindings
jdconrad 352c897
Binding updated.
jdconrad ff26f71
Update bindings in whitelist to new format.
jdconrad 869a23a
Merge branch 'master' into bindings
jdconrad a1298e2
Merge branch 'master' into bindings
jdconrad 1fa435e
Response to PR comments.
jdconrad ee49cc8
Merge branch 'master' into bindings
jdconrad b4e2655
Merge branch 'master' into bindings
jdconrad File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
66 changes: 66 additions & 0 deletions
66
modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistBinding.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
/* | ||
* Licensed to Elasticsearch under one or more contributor | ||
* license agreements. See the NOTICE file distributed with | ||
* this work for additional information regarding copyright | ||
* ownership. Elasticsearch licenses this file to you under | ||
* the Apache License, Version 2.0 (the "License"); you may | ||
* not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, | ||
* software distributed under the License is distributed on an | ||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
* KIND, either express or implied. See the License for the | ||
* specific language governing permissions and limitations | ||
* under the License. | ||
*/ | ||
|
||
package org.elasticsearch.painless.spi; | ||
|
||
import java.util.List; | ||
import java.util.Objects; | ||
|
||
/** | ||
* A binding represents a static method call that stores state. Each binding must have exactly | ||
* one public constructor and one public method excluding those inherited directly from {@link Object}. | ||
* The canonical type name parameters provided must match those of the constructor and method combined. | ||
* The constructor for a binding will be called when the binding is called for the first time at which | ||
* point state may be stored for the arguments passed into the constructor. The method for a binding | ||
* will be called each time the binding is called and may use the previously stored state. | ||
*/ | ||
public class WhitelistBinding { | ||
|
||
/** Information about where this constructor was whitelisted from. */ | ||
public final String origin; | ||
|
||
/** The Java class name this binding represents. */ | ||
public final String targetJavaClassName; | ||
|
||
/** The method name for this binding. */ | ||
public final String methodName; | ||
|
||
/** | ||
* The canonical type name for the return type. | ||
*/ | ||
public final String returnCanonicalTypeName; | ||
|
||
/** | ||
* A {@link List} of {@link String}s that are the Painless type names for the parameters of the | ||
* constructor which can be used to look up the Java constructor through reflection. | ||
*/ | ||
public final List<String> canonicalTypeNameParameters; | ||
|
||
/** Standard constructor. All values must be not {@code null}. */ | ||
public WhitelistBinding(String origin, String targetJavaClassName, | ||
String methodName, String returnCanonicalTypeName, List<String> canonicalTypeNameParameters) { | ||
|
||
this.origin = Objects.requireNonNull(origin); | ||
this.targetJavaClassName = Objects.requireNonNull(targetJavaClassName); | ||
|
||
this.methodName = Objects.requireNonNull(methodName); | ||
this.returnCanonicalTypeName = Objects.requireNonNull(returnCanonicalTypeName); | ||
this.canonicalTypeNameParameters = Objects.requireNonNull(canonicalTypeNameParameters); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -133,6 +133,7 @@ public final class WhitelistLoader { | |
*/ | ||
public static Whitelist loadFromResourceFiles(Class<?> resource, String... filepaths) { | ||
List<WhitelistClass> whitelistClasses = new ArrayList<>(); | ||
List<WhitelistBinding> whitelistBindings = new ArrayList<>(); | ||
|
||
// Execute a single pass through the whitelist text files. This will gather all the | ||
// constructors, methods, augmented methods, and fields for each whitelisted class. | ||
|
@@ -141,8 +142,9 @@ public static Whitelist loadFromResourceFiles(Class<?> resource, String... filep | |
int number = -1; | ||
|
||
try (LineNumberReader reader = new LineNumberReader( | ||
new InputStreamReader(resource.getResourceAsStream(filepath), StandardCharsets.UTF_8))) { | ||
new InputStreamReader(resource.getResourceAsStream(filepath), StandardCharsets.UTF_8))) { | ||
|
||
String parseType = null; | ||
String whitelistClassOrigin = null; | ||
String javaClassName = null; | ||
boolean noImport = false; | ||
|
@@ -165,7 +167,11 @@ public static Whitelist loadFromResourceFiles(Class<?> resource, String... filep | |
// Ensure the final token of the line is '{'. | ||
if (line.endsWith("{") == false) { | ||
throw new IllegalArgumentException( | ||
"invalid class definition: failed to parse class opening bracket [" + line + "]"); | ||
"invalid class definition: failed to parse class opening bracket [" + line + "]"); | ||
} | ||
|
||
if (parseType != null) { | ||
throw new IllegalArgumentException("invalid definition: cannot embed class definition [" + line + "]"); | ||
} | ||
|
||
// Parse the Java class name. | ||
|
@@ -178,41 +184,125 @@ public static Whitelist loadFromResourceFiles(Class<?> resource, String... filep | |
throw new IllegalArgumentException("invalid class definition: failed to parse class name [" + line + "]"); | ||
} | ||
|
||
parseType = "class"; | ||
whitelistClassOrigin = "[" + filepath + "]:[" + number + "]"; | ||
javaClassName = tokens[0]; | ||
|
||
// Reset all the constructors, methods, and fields to support a new class. | ||
whitelistConstructors = new ArrayList<>(); | ||
whitelistMethods = new ArrayList<>(); | ||
whitelistFields = new ArrayList<>(); | ||
} else if (line.startsWith("static ")) { | ||
// Ensure the final token of the line is '{'. | ||
if (line.endsWith("{") == false) { | ||
throw new IllegalArgumentException( | ||
"invalid class definition: failed to parse class opening bracket [" + line + "]"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should the message here indicate "static" opening bracket, not class? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. |
||
} | ||
|
||
// Handle the end of a class, by creating a new WhitelistClass with all the previously gathered | ||
// constructors, methods, augmented methods, and fields, and adding it to the list of whitelisted classes. | ||
if (parseType != null) { | ||
throw new IllegalArgumentException("invalid definition: cannot embed static definition [" + line + "]"); | ||
} | ||
|
||
parseType = "static"; | ||
|
||
// Handle the end of a definition and reset all previously gathered values. | ||
// Expects the following format: '}' '\n' | ||
} else if (line.equals("}")) { | ||
if (javaClassName == null) { | ||
throw new IllegalArgumentException("invalid class definition: extraneous closing bracket"); | ||
if (parseType == null) { | ||
throw new IllegalArgumentException("invalid definition: extraneous closing bracket"); | ||
} | ||
|
||
whitelistClasses.add(new WhitelistClass(whitelistClassOrigin, javaClassName, noImport, | ||
whitelistConstructors, whitelistMethods, whitelistFields)); | ||
// Create a new WhitelistClass with all the previously gathered constructors, methods, | ||
// augmented methods, and fields, and add it to the list of whitelisted classes. | ||
if ("class".equals(parseType)) { | ||
whitelistClasses.add(new WhitelistClass(whitelistClassOrigin, javaClassName, noImport, | ||
whitelistConstructors, whitelistMethods, whitelistFields)); | ||
|
||
whitelistClassOrigin = null; | ||
javaClassName = null; | ||
noImport = false; | ||
whitelistConstructors = null; | ||
whitelistMethods = null; | ||
whitelistFields = null; | ||
} | ||
|
||
// Set all the variables to null to ensure a new class definition is found before other parsable values. | ||
whitelistClassOrigin = null; | ||
javaClassName = null; | ||
noImport = false; | ||
whitelistConstructors = null; | ||
whitelistMethods = null; | ||
whitelistFields = null; | ||
// Reset the parseType. | ||
parseType = null; | ||
|
||
// Handle all other valid cases. | ||
} else { | ||
// Handle static definition types. | ||
// Expects the following format: ID ID '(' ( ID ( ',' ID )* )? ')' 'bound_to' ID '\n' | ||
} else if ("static".equals(parseType)) { | ||
// Mark the origin of this parsable object. | ||
String origin = "[" + filepath + "]:[" + number + "]"; | ||
|
||
// Parse the tokens prior to the method parameters. | ||
int parameterStartIndex = line.indexOf('('); | ||
|
||
if (parameterStartIndex == -1) { | ||
throw new IllegalArgumentException( | ||
"illegal static definition: start of method parameters not found [" + line + "]"); | ||
} | ||
|
||
String[] tokens = line.substring(0, parameterStartIndex).trim().split("\\s+"); | ||
|
||
String methodName; | ||
|
||
// Based on the number of tokens, look up the Java method name. | ||
if (tokens.length == 2) { | ||
methodName = tokens[1]; | ||
} else { | ||
throw new IllegalArgumentException("invalid method definition: unexpected format [" + line + "]"); | ||
} | ||
|
||
String returnCanonicalTypeName = tokens[0]; | ||
|
||
// Parse the method parameters. | ||
int parameterEndIndex = line.indexOf(')'); | ||
|
||
if (parameterEndIndex == -1) { | ||
throw new IllegalArgumentException( | ||
"illegal static definition: end of method parameters not found [" + line + "]"); | ||
} | ||
|
||
String[] canonicalTypeNameParameters = | ||
line.substring(parameterStartIndex + 1, parameterEndIndex).replaceAll("\\s+", "").split(","); | ||
|
||
// Handle the case for a method with no parameters. | ||
if ("".equals(canonicalTypeNameParameters[0])) { | ||
canonicalTypeNameParameters = new String[0]; | ||
} | ||
|
||
// Parse the static type and class. | ||
tokens = line.substring(parameterEndIndex + 1).trim().split("\\s+"); | ||
|
||
String staticType; | ||
String targetJavaClassName; | ||
|
||
// Based on the number of tokens, look up the type and class. | ||
if (tokens.length == 2) { | ||
staticType = tokens[0]; | ||
targetJavaClassName = tokens[1]; | ||
} else { | ||
throw new IllegalArgumentException("invalid static definition: unexpected format [" + line + "]"); | ||
} | ||
|
||
// Check the static type is valid. | ||
if ("bound_to".equals(staticType) == false) { | ||
throw new IllegalArgumentException( | ||
"invalid static definition: unexpected static type [" + staticType + "] [" + line + "]"); | ||
} | ||
|
||
whitelistBindings.add(new WhitelistBinding(origin, targetJavaClassName, | ||
methodName, returnCanonicalTypeName, Arrays.asList(canonicalTypeNameParameters))); | ||
|
||
// Handle class definition types. | ||
} else if ("class".equals(parseType)) { | ||
// Mark the origin of this parsable object. | ||
String origin = "[" + filepath + "]:[" + number + "]"; | ||
|
||
// Ensure we have a defined class before adding any constructors, methods, augmented methods, or fields. | ||
if (javaClassName == null) { | ||
throw new IllegalArgumentException("invalid object definition: expected a class name [" + line + "]"); | ||
if (parseType == null) { | ||
throw new IllegalArgumentException("invalid definition: expected one of ['class', 'static'] [" + line + "]"); | ||
} | ||
|
||
// Handle the case for a constructor definition. | ||
|
@@ -221,7 +311,7 @@ public static Whitelist loadFromResourceFiles(Class<?> resource, String... filep | |
// Ensure the final token of the line is ')'. | ||
if (line.endsWith(")") == false) { | ||
throw new IllegalArgumentException( | ||
"invalid constructor definition: expected a closing parenthesis [" + line + "]"); | ||
"invalid constructor definition: expected a closing parenthesis [" + line + "]"); | ||
} | ||
|
||
// Parse the constructor parameters. | ||
|
@@ -234,34 +324,34 @@ public static Whitelist loadFromResourceFiles(Class<?> resource, String... filep | |
|
||
whitelistConstructors.add(new WhitelistConstructor(origin, Arrays.asList(tokens))); | ||
|
||
// Handle the case for a method or augmented method definition. | ||
// Expects the following format: ID ID? ID '(' ( ID ( ',' ID )* )? ')' '\n' | ||
// Handle the case for a method or augmented method definition. | ||
// Expects the following format: ID ID? ID '(' ( ID ( ',' ID )* )? ')' '\n' | ||
} else if (line.contains("(")) { | ||
// Ensure the final token of the line is ')'. | ||
if (line.endsWith(")") == false) { | ||
throw new IllegalArgumentException( | ||
"invalid method definition: expected a closing parenthesis [" + line + "]"); | ||
"invalid method definition: expected a closing parenthesis [" + line + "]"); | ||
} | ||
|
||
// Parse the tokens prior to the method parameters. | ||
int parameterIndex = line.indexOf('('); | ||
String[] tokens = line.trim().substring(0, parameterIndex).split("\\s+"); | ||
String[] tokens = line.substring(0, parameterIndex).trim().split("\\s+"); | ||
|
||
String javaMethodName; | ||
String methodName; | ||
String javaAugmentedClassName; | ||
|
||
// Based on the number of tokens, look up the Java method name and if provided the Java augmented class. | ||
if (tokens.length == 2) { | ||
javaMethodName = tokens[1]; | ||
methodName = tokens[1]; | ||
javaAugmentedClassName = null; | ||
} else if (tokens.length == 3) { | ||
javaMethodName = tokens[2]; | ||
methodName = tokens[2]; | ||
javaAugmentedClassName = tokens[1]; | ||
} else { | ||
throw new IllegalArgumentException("invalid method definition: unexpected format [" + line + "]"); | ||
} | ||
|
||
String painlessReturnTypeName = tokens[0]; | ||
String returnCanonicalTypeName = tokens[0]; | ||
|
||
// Parse the method parameters. | ||
tokens = line.substring(parameterIndex + 1, line.length() - 1).replaceAll("\\s+", "").split(","); | ||
|
@@ -271,11 +361,11 @@ public static Whitelist loadFromResourceFiles(Class<?> resource, String... filep | |
tokens = new String[0]; | ||
} | ||
|
||
whitelistMethods.add(new WhitelistMethod(origin, javaAugmentedClassName, javaMethodName, | ||
painlessReturnTypeName, Arrays.asList(tokens))); | ||
whitelistMethods.add(new WhitelistMethod(origin, javaAugmentedClassName, methodName, | ||
returnCanonicalTypeName, Arrays.asList(tokens))); | ||
|
||
// Handle the case for a field definition. | ||
// Expects the following format: ID ID '\n' | ||
// Handle the case for a field definition. | ||
// Expects the following format: ID ID '\n' | ||
} else { | ||
// Parse the field tokens. | ||
String[] tokens = line.split("\\s+"); | ||
|
@@ -287,20 +377,23 @@ public static Whitelist loadFromResourceFiles(Class<?> resource, String... filep | |
|
||
whitelistFields.add(new WhitelistField(origin, tokens[1], tokens[0])); | ||
} | ||
} else { | ||
throw new IllegalArgumentException("invalid definition: unable to parse line [" + line + "]"); | ||
} | ||
} | ||
|
||
// Ensure all classes end with a '}' token before the end of the file. | ||
if (javaClassName != null) { | ||
throw new IllegalArgumentException("invalid class definition: expected closing bracket"); | ||
throw new IllegalArgumentException("invalid definition: expected closing bracket"); | ||
} | ||
} catch (Exception exception) { | ||
throw new RuntimeException("error in [" + filepath + "] at line [" + number + "]", exception); | ||
} | ||
} | ||
|
||
ClassLoader loader = AccessController.doPrivileged((PrivilegedAction<ClassLoader>)resource::getClassLoader); | ||
|
||
return new Whitelist(loader, whitelistClasses); | ||
return new Whitelist(loader, whitelistClasses, whitelistBindings); | ||
} | ||
|
||
private WhitelistLoader() {} | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need to distinguish the binding here (whitelist binding) from a binding class? The latter is what most of your description is referring to (except for the first sentence?), but it is confusing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cleaned this up a bit.