Skip to content

Commit

Permalink
check unexported fields in evaluation stage
Browse files Browse the repository at this point in the history
  • Loading branch information
abichinger committed Nov 20, 2023
1 parent 04546d0 commit 7fd00a3
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 37 deletions.
19 changes: 16 additions & 3 deletions evaluationFailure_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type DebugStruct struct {
}

/*
Represents a test for parsing failures
Represents a test for parsing failures
*/
type EvaluationFailureTest struct {
Name string
Expand All @@ -36,6 +36,7 @@ const (
TOO_FEW_ARGS = "Too few arguments to parameter call"
TOO_MANY_ARGS = "Too many arguments to parameter call"
MISMATCHED_PARAMETERS = "Argument type conversion failed"
UNEXPORTED_ACCESSOR = "Unable to access unexported"
)

// preset parameter map of types that can be used in an evaluation failure test to check typing.
Expand Down Expand Up @@ -247,8 +248,8 @@ func TestLogicalOperatorTyping(test *testing.T) {
}

/*
While there is type-safe transitions checked at parse-time, tested in the "parsing_test" and "parsingFailure_test" files,
we also need to make sure that we receive type mismatch errors during evaluation.
While there is type-safe transitions checked at parse-time, tested in the "parsing_test" and "parsingFailure_test" files,
we also need to make sure that we receive type mismatch errors during evaluation.
*/
func TestComparatorTyping(test *testing.T) {

Expand Down Expand Up @@ -490,6 +491,18 @@ func TestInvalidParameterCalls(test *testing.T) {
Parameters: fooFailureParameters,
Expected: MISMATCHED_PARAMETERS,
},
EvaluationFailureTest{
Name: "Unexported parameter access",
Input: "foo.bar",
Parameters: map[string]interface{}{
"foo": struct {
bar string
}{
bar: "baz",
},
},
Expected: UNEXPORTED_ACCESSOR,
},
}

runEvaluationFailureTests(evaluationTests, test)
Expand Down
20 changes: 14 additions & 6 deletions evaluationStage.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"reflect"
"regexp"
"strings"
"unicode"
)

const (
Expand Down Expand Up @@ -331,6 +332,13 @@ func makeAccessorStage(pair []string) evaluationOperator {

switch coreValue.Kind() {
case reflect.Struct:
// check if field is exported
firstCharacter := getFirstRune(pair[i])
if unicode.ToUpper(firstCharacter) != firstCharacter {
errorMsg := fmt.Sprintf("Unable to access unexported field '%s' in '%s'", pair[i], pair[i-1])
return nil, errors.New(errorMsg)
}

field = coreValue.FieldByName(pair[i])
if field != (reflect.Value{}) {
value = field.Interface()
Expand Down Expand Up @@ -484,8 +492,8 @@ func isFloat64(value interface{}) bool {
}

/*
Addition usually means between numbers, but can also mean string concat.
String concat needs one (or both) of the sides to be a string.
Addition usually means between numbers, but can also mean string concat.
String concat needs one (or both) of the sides to be a string.
*/
func additionTypeCheck(left interface{}, right interface{}) bool {

Expand All @@ -499,8 +507,8 @@ func additionTypeCheck(left interface{}, right interface{}) bool {
}

/*
Comparison can either be between numbers, or lexicographic between two strings,
but never between the two.
Comparison can either be between numbers, or lexicographic between two strings,
but never between the two.
*/
func comparatorTypeCheck(left interface{}, right interface{}) bool {

Expand All @@ -522,8 +530,8 @@ func isArray(value interface{}) bool {
}

/*
Converting a boolean to an interface{} requires an allocation.
We can use interned bools to avoid this cost.
Converting a boolean to an interface{} requires an allocation.
We can use interned bools to avoid this cost.
*/
func boolIface(b bool) interface{} {
if b {
Expand Down
27 changes: 8 additions & 19 deletions parsing.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,17 +188,6 @@ func readToken(stream *lexerStream, state lexerState, functions map[string]Expre
kind = ACCESSOR
splits := strings.Split(tokenString, ".")
tokenValue = splits

// check that none of them are unexported
for i := 1; i < len(splits); i++ {

firstCharacter := getFirstRune(splits[i])

if unicode.ToUpper(firstCharacter) != firstCharacter {
errorMsg := fmt.Sprintf("Unable to access unexported field '%s' in token '%s'", splits[i], tokenString)
return ExpressionToken{}, errors.New(errorMsg), false
}
}
}
break
}
Expand Down Expand Up @@ -298,8 +287,8 @@ func readTokenUntilFalse(stream *lexerStream, condition func(rune) bool) string
}

/*
Returns the string that was read until the given [condition] was false, or whitespace was broken.
Returns false if the stream ended before whitespace was broken or condition was met.
Returns the string that was read until the given [condition] was false, or whitespace was broken.
Returns false if the stream ended before whitespace was broken or condition was met.
*/
func readUntilFalse(stream *lexerStream, includeWhitespace bool, breakWhitespace bool, allowEscaping bool, condition func(rune) bool) (string, bool) {

Expand Down Expand Up @@ -345,8 +334,8 @@ func readUntilFalse(stream *lexerStream, includeWhitespace bool, breakWhitespace
}

/*
Checks to see if any optimizations can be performed on the given [tokens], which form a complete, valid expression.
The returns slice will represent the optimized (or unmodified) list of tokens to use.
Checks to see if any optimizations can be performed on the given [tokens], which form a complete, valid expression.
The returns slice will represent the optimized (or unmodified) list of tokens to use.
*/
func optimizeTokens(tokens []ExpressionToken) ([]ExpressionToken, error) {

Expand Down Expand Up @@ -385,7 +374,7 @@ func optimizeTokens(tokens []ExpressionToken) ([]ExpressionToken, error) {
}

/*
Checks the balance of tokens which have multiple parts, such as parenthesis.
Checks the balance of tokens which have multiple parts, such as parenthesis.
*/
func checkBalance(tokens []ExpressionToken) error {

Expand Down Expand Up @@ -466,9 +455,9 @@ func isNotClosingBracket(character rune) bool {
}

/*
Attempts to parse the [candidate] as a Time.
Tries a series of standardized date formats, returns the Time if one applies,
otherwise returns false through the second return.
Attempts to parse the [candidate] as a Time.
Tries a series of standardized date formats, returns the Time if one applies,
otherwise returns false through the second return.
*/
func tryParseTime(candidate string) (time.Time, bool) {

Expand Down
10 changes: 1 addition & 9 deletions parsingFailure_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,11 @@ const (
INVALID_NUMERIC = "Unable to parse numeric value"
UNDEFINED_FUNCTION = "Undefined function"
HANGING_ACCESSOR = "Hanging accessor on token"
UNEXPORTED_ACCESSOR = "Unable to access unexported"
INVALID_HEX = "Unable to parse hex value"
)

/*
Represents a test for parsing failures
Represents a test for parsing failures
*/
type ParsingFailureTest struct {
Name string
Expand Down Expand Up @@ -178,13 +177,6 @@ func TestParsingFailure(test *testing.T) {
Input: "foo.Bar.",
Expected: HANGING_ACCESSOR,
},
ParsingFailureTest{

// this is expected to change once there are structtags in place that allow aliasing of fields
Name: "Unexported parameter access",
Input: "foo.bar",
Expected: UNEXPORTED_ACCESSOR,
},
ParsingFailureTest{
Name: "Incomplete Hex",
Input: "0x",
Expand Down

0 comments on commit 7fd00a3

Please sign in to comment.