diff --git a/ast/ast.go b/ast/ast.go index 778fbc0..5dc2383 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -690,10 +690,15 @@ func (f *ForOf) Children() []Node { // Children returns the node's child nodes. func (o *Of) Children() []Node { - nodes := []Node{o.Quantifier, o.Strings} + // Because this node can have children that are exclusively rules or + // strings we need to only add them if they are non-nil. + nodes := []Node{o.Quantifier} if o.Rules != nil { nodes = append(nodes, o.Rules) } + if o.Strings != nil { + nodes = append(nodes, o.Strings) + } return nodes } @@ -1233,5 +1238,3 @@ func (o *Operation) AsProto() *pb.Expression { } return expr } - - diff --git a/error/error.go b/error/error.go index 8047a17..3e6841d 100644 --- a/error/error.go +++ b/error/error.go @@ -30,6 +30,7 @@ const ( UnevenNumberOfDigitsError InvalidAsciiError InvalidUTF8Error + UndefinedStringIdentifierError ) type Error struct { diff --git a/parser/adapter.go b/parser/adapter.go index 342d5fc..f71a442 100644 --- a/parser/adapter.go +++ b/parser/adapter.go @@ -32,6 +32,10 @@ func Parse(input io.Reader) (rs *ast.RuleSet, err error) { Imports: make([]string, 0), Rules: make([]*ast.Rule, 0), }, + // Used to collect the strings as they are parsed on a per-rule basis. + // When the condition is parsed this is used as a lookup table to check + // for undefined strings. + strings: make(map[string]bool), } lexer.scanner.In = input lexer.scanner.Out = ioutil.Discard @@ -51,6 +55,7 @@ type lexer struct { scanner Scanner err gyperror.Error ruleSet *ast.RuleSet + strings map[string]bool } // Lex provides the interface expected by the goyacc parser. This function is diff --git a/parser/grammar.y b/parser/grammar.y index e6b190c..0de5929 100644 --- a/parser/grammar.y +++ b/parser/grammar.y @@ -314,6 +314,9 @@ rule { $4.Condition = $10 $$ = $4 + + // Clear the strings map for the next rule being parsed. + asLexer(yrlex).strings = make(map[string]bool) } ; @@ -470,10 +473,14 @@ meta_declaration string_declarations : string_declaration { + lexer := asLexer(yrlex) + lexer.strings[$1.GetIdentifier()] = true $$ = []ast.String{$1} } | string_declarations string_declaration { + lexer := asLexer(yrlex) + lexer.strings[$2.GetIdentifier()] = true $$ = append($1, $2) } ; @@ -829,21 +836,51 @@ expression } | _STRING_IDENTIFIER_ { + identifier := strings.TrimPrefix($1, "$") + // Exclude anonymous ($) strings. + if identifier != "" { + lexer := asLexer(yrlex) + if _, ok := lexer.strings[identifier]; !ok { + return lexer.setError( + gyperror.UndefinedStringIdentifierError, + `undefined string identifier: %s`, $1) + } + } $$ = &ast.StringIdentifier{ - Identifier: strings.TrimPrefix($1, "$"), + Identifier: identifier, } } | _STRING_IDENTIFIER_ _AT_ primary_expression { + identifier := strings.TrimPrefix($1, "$") + // Exclude anonymous ($) strings. + if identifier != "" { + lexer := asLexer(yrlex) + if _, ok := lexer.strings[identifier]; !ok { + return lexer.setError( + gyperror.UndefinedStringIdentifierError, + `undefined string identifier: %s`, $1) + } + } $$ = &ast.StringIdentifier{ - Identifier: strings.TrimPrefix($1, "$"), + Identifier: identifier, At: $3, } } | _STRING_IDENTIFIER_ _IN_ range { + identifier := strings.TrimPrefix($1, "$") + // Exclude anonymous ($) strings. + if identifier != "" { + lexer := asLexer(yrlex) + if _, ok := lexer.strings[identifier]; !ok { + return lexer.setError( + gyperror.UndefinedStringIdentifierError, + `undefined string identifier: %s`, $1) + } + } $$ = &ast.StringIdentifier{ - Identifier: strings.TrimPrefix($1, "$"), + Identifier: identifier, In: $3, } } @@ -1011,6 +1048,12 @@ string_set } | _THEM_ { + lexer := asLexer(yrlex) + if len(lexer.strings) == 0 { + return lexer.setError( + gyperror.UndefinedStringIdentifierError, + `undefined string identifier: %s`, ast.KeywordThem) + } $$ = ast.KeywordThem } ; @@ -1031,16 +1074,48 @@ string_enumeration string_enumeration_item : _STRING_IDENTIFIER_ { + identifier := strings.TrimPrefix($1, "$") + lexer := asLexer(yrlex) + // Anonymous strings ($) in string enumerations are an error. + if _, ok := lexer.strings[identifier]; !ok || identifier == "" { + return lexer.setError( + gyperror.UndefinedStringIdentifierError, + `undefined string identifier: %s`, $1) + } $$ = &ast.StringIdentifier{ - Identifier: strings.TrimPrefix($1, "$"), + Identifier: identifier, } } | _STRING_IDENTIFIER_WITH_WILDCARD_ - { - $$ = &ast.StringIdentifier{ - Identifier: strings.TrimPrefix($1, "$"), + { + identifier := strings.TrimSuffix($1, "*") + lexer := asLexer(yrlex) + // There must be at least one defined string. + if len(identifier) == 0 && len(lexer.strings) == 0 { + return lexer.setError( + gyperror.UndefinedStringIdentifierError, + `undefined string identifier: %s`, $1) + } + + // There must be at least one string that will match the wildcard. + identifier = strings.TrimPrefix(identifier, "$") + match := false + for s, _ := range lexer.strings { + if strings.HasPrefix(s, identifier) { + match = true + break } } + if !match { + return lexer.setError( + gyperror.UndefinedStringIdentifierError, + `undefined string identifier: %s`, $1) + } + // Can't use "identifier" here as that has the asterisk stripped already. + $$ = &ast.StringIdentifier{ + Identifier: strings.TrimPrefix($1, "$"), + } + } ; @@ -1158,39 +1233,93 @@ primary_expression } | _STRING_COUNT_ _IN_ range { + identifier := strings.TrimPrefix($1, "#") + if identifier != "" { + lexer := asLexer(yrlex) + if _, ok := lexer.strings[identifier]; !ok { + return lexer.setError( + gyperror.UndefinedStringIdentifierError, + `undefined string identifier: %s`, $1) + } + } $$ = &ast.StringCount{ - Identifier: strings.TrimPrefix($1, "#"), + Identifier: identifier, In: $3, } } | _STRING_COUNT_ { + identifier := strings.TrimPrefix($1, "#") + if identifier != "" { + lexer := asLexer(yrlex) + if _, ok := lexer.strings[identifier]; !ok { + return lexer.setError( + gyperror.UndefinedStringIdentifierError, + `undefined string identifier: %s`, $1) + } + } $$ = &ast.StringCount{ - Identifier: strings.TrimPrefix($1, "#"), + Identifier: identifier, } } | _STRING_OFFSET_ '[' primary_expression ']' { + identifier := strings.TrimPrefix($1, "@") + if identifier != "" { + lexer := asLexer(yrlex) + if _, ok := lexer.strings[identifier]; !ok { + return lexer.setError( + gyperror.UndefinedStringIdentifierError, + `undefined string identifier: %s`, $1) + } + } $$ = &ast.StringOffset{ - Identifier: strings.TrimPrefix($1, "@"), + Identifier: identifier, Index: $3, } } | _STRING_OFFSET_ { + identifier := strings.TrimPrefix($1, "@") + if identifier != "" { + lexer := asLexer(yrlex) + if _, ok := lexer.strings[identifier]; !ok { + return lexer.setError( + gyperror.UndefinedStringIdentifierError, + `undefined string identifier: %s`, $1) + } + } $$ = &ast.StringOffset{ - Identifier: strings.TrimPrefix($1, "@"), + Identifier: identifier, } } | _STRING_LENGTH_ '[' primary_expression ']' { + identifier := strings.TrimPrefix($1, "!") + if identifier != "" { + lexer := asLexer(yrlex) + if _, ok := lexer.strings[identifier]; !ok { + return lexer.setError( + gyperror.UndefinedStringIdentifierError, + `undefined string identifier: %s`, $1) + } + } $$ = &ast.StringLength{ - Identifier: strings.TrimPrefix($1, "!"), + Identifier: identifier, Index: $3, } } | _STRING_LENGTH_ { + identifier := strings.TrimPrefix($1, "!") + if identifier != "" { + lexer := asLexer(yrlex) + if _, ok := lexer.strings[identifier]; !ok { + return lexer.setError( + gyperror.UndefinedStringIdentifierError, + `undefined string identifier: %s`, $1) + } + } $$ = &ast.StringLength{ Identifier: strings.TrimPrefix($1, "!"), } @@ -1272,4 +1401,4 @@ func operation(operator ast.OperatorType, left, right ast.Expression) (n ast.Exp } } return n -} +} \ No newline at end of file diff --git a/parser/parser.go b/parser/parser.go index 329f9c9..5936ed3 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -237,7 +237,7 @@ const yrEofCode = 1 const yrErrCode = 2 const yrInitialStackSize = 16 -//line parser/grammar.y:1256 +//line parser/grammar.y:1385 // This function takes an operator and two operands and returns a Expression // representing the operation. If the left operand is an operation of the @@ -936,47 +936,50 @@ yrdefault: { yrDollar[4].rule.Condition = yrDollar[10].expr yrVAL.rule = yrDollar[4].rule + + // Clear the strings map for the next rule being parsed. + asLexer(yrlex).strings = make(map[string]bool) } case 10: yrDollar = yrS[yrpt-0 : yrpt+1] -//line parser/grammar.y:323 +//line parser/grammar.y:326 { yrVAL.metas = []*ast.Meta{} } case 11: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:327 +//line parser/grammar.y:330 { yrVAL.metas = yrDollar[3].metas } case 12: yrDollar = yrS[yrpt-0 : yrpt+1] -//line parser/grammar.y:335 +//line parser/grammar.y:338 { yrVAL.yss = []ast.String{} } case 13: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:339 +//line parser/grammar.y:342 { yrVAL.yss = yrDollar[3].yss } case 14: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:347 +//line parser/grammar.y:350 { yrVAL.expr = yrDollar[3].expr } case 15: yrDollar = yrS[yrpt-0 : yrpt+1] -//line parser/grammar.y:355 +//line parser/grammar.y:358 { yrVAL.mod = 0 yrVAL.lineno = -1 } case 16: yrDollar = yrS[yrpt-2 : yrpt+1] -//line parser/grammar.y:360 +//line parser/grammar.y:363 { yrVAL.mod = yrDollar[1].mod | yrDollar[2].mod @@ -988,39 +991,39 @@ yrdefault: } case 17: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:374 +//line parser/grammar.y:377 { yrVAL.mod = ModPrivate yrVAL.lineno = yrDollar[1].lineno } case 18: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:379 +//line parser/grammar.y:382 { yrVAL.mod = ModGlobal yrVAL.lineno = yrDollar[1].lineno } case 19: yrDollar = yrS[yrpt-0 : yrpt+1] -//line parser/grammar.y:388 +//line parser/grammar.y:391 { yrVAL.ss = []string{} } case 20: yrDollar = yrS[yrpt-2 : yrpt+1] -//line parser/grammar.y:392 +//line parser/grammar.y:395 { yrVAL.ss = yrDollar[2].ss } case 21: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:400 +//line parser/grammar.y:403 { yrVAL.ss = []string{yrDollar[1].s} } case 22: yrDollar = yrS[yrpt-2 : yrpt+1] -//line parser/grammar.y:404 +//line parser/grammar.y:407 { lexer := asLexer(yrlex) @@ -1035,19 +1038,19 @@ yrdefault: } case 23: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:421 +//line parser/grammar.y:424 { yrVAL.metas = []*ast.Meta{yrDollar[1].meta} } case 24: yrDollar = yrS[yrpt-2 : yrpt+1] -//line parser/grammar.y:425 +//line parser/grammar.y:428 { yrVAL.metas = append(yrDollar[1].metas, yrDollar[2].meta) } case 25: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:433 +//line parser/grammar.y:436 { yrVAL.meta = &ast.Meta{ Key: yrDollar[1].s, @@ -1056,7 +1059,7 @@ yrdefault: } case 26: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:440 +//line parser/grammar.y:443 { yrVAL.meta = &ast.Meta{ Key: yrDollar[1].s, @@ -1065,7 +1068,7 @@ yrdefault: } case 27: yrDollar = yrS[yrpt-4 : yrpt+1] -//line parser/grammar.y:447 +//line parser/grammar.y:450 { yrVAL.meta = &ast.Meta{ Key: yrDollar[1].s, @@ -1074,7 +1077,7 @@ yrdefault: } case 28: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:454 +//line parser/grammar.y:457 { yrVAL.meta = &ast.Meta{ Key: yrDollar[1].s, @@ -1083,7 +1086,7 @@ yrdefault: } case 29: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:461 +//line parser/grammar.y:464 { yrVAL.meta = &ast.Meta{ Key: yrDollar[1].s, @@ -1092,19 +1095,23 @@ yrdefault: } case 30: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:472 +//line parser/grammar.y:475 { + lexer := asLexer(yrlex) + lexer.strings[yrDollar[1].ys.GetIdentifier()] = true yrVAL.yss = []ast.String{yrDollar[1].ys} } case 31: yrDollar = yrS[yrpt-2 : yrpt+1] -//line parser/grammar.y:476 +//line parser/grammar.y:481 { + lexer := asLexer(yrlex) + lexer.strings[yrDollar[2].ys.GetIdentifier()] = true yrVAL.yss = append(yrDollar[1].yss, yrDollar[2].ys) } case 32: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:484 +//line parser/grammar.y:491 { if err := validateUTF8(yrDollar[3].s); err != nil { return asLexer(yrlex).setError( @@ -1113,7 +1120,7 @@ yrdefault: } case 33: yrDollar = yrS[yrpt-5 : yrpt+1] -//line parser/grammar.y:491 +//line parser/grammar.y:498 { yrVAL.ys = &ast.TextString{ BaseString: ast.BaseString{ @@ -1136,7 +1143,7 @@ yrdefault: } case 34: yrDollar = yrS[yrpt-4 : yrpt+1] -//line parser/grammar.y:512 +//line parser/grammar.y:519 { yrVAL.ys = &ast.RegexpString{ BaseString: ast.BaseString{ @@ -1153,7 +1160,7 @@ yrdefault: } case 35: yrDollar = yrS[yrpt-4 : yrpt+1] -//line parser/grammar.y:527 +//line parser/grammar.y:534 { yrVAL.ys = &ast.HexString{ BaseString: ast.BaseString{ @@ -1166,13 +1173,13 @@ yrdefault: } case 36: yrDollar = yrS[yrpt-0 : yrpt+1] -//line parser/grammar.y:542 +//line parser/grammar.y:549 { yrVAL.smod = stringModifiers{} } case 37: yrDollar = yrS[yrpt-2 : yrpt+1] -//line parser/grammar.y:546 +//line parser/grammar.y:553 { if yrDollar[1].smod.modifiers&yrDollar[2].smod.modifiers != 0 { return asLexer(yrlex).setError( @@ -1194,49 +1201,49 @@ yrdefault: } case 38: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:569 +//line parser/grammar.y:576 { yrVAL.smod = stringModifiers{modifiers: ModWide} } case 39: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:570 +//line parser/grammar.y:577 { yrVAL.smod = stringModifiers{modifiers: ModASCII} } case 40: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:571 +//line parser/grammar.y:578 { yrVAL.smod = stringModifiers{modifiers: ModNocase} } case 41: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:572 +//line parser/grammar.y:579 { yrVAL.smod = stringModifiers{modifiers: ModFullword} } case 42: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:573 +//line parser/grammar.y:580 { yrVAL.smod = stringModifiers{modifiers: ModPrivate} } case 43: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:574 +//line parser/grammar.y:581 { yrVAL.smod = stringModifiers{modifiers: ModBase64} } case 44: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:575 +//line parser/grammar.y:582 { yrVAL.smod = stringModifiers{modifiers: ModBase64Wide} } case 45: yrDollar = yrS[yrpt-4 : yrpt+1] -//line parser/grammar.y:577 +//line parser/grammar.y:584 { if err := validateAscii(yrDollar[3].s); err != nil { return asLexer(yrlex).setError( @@ -1256,7 +1263,7 @@ yrdefault: } case 46: yrDollar = yrS[yrpt-4 : yrpt+1] -//line parser/grammar.y:595 +//line parser/grammar.y:602 { if err := validateAscii(yrDollar[3].s); err != nil { return asLexer(yrlex).setError( @@ -1276,7 +1283,7 @@ yrdefault: } case 47: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:613 +//line parser/grammar.y:620 { yrVAL.smod = stringModifiers{ modifiers: ModXor, @@ -1286,7 +1293,7 @@ yrdefault: } case 48: yrDollar = yrS[yrpt-4 : yrpt+1] -//line parser/grammar.y:621 +//line parser/grammar.y:628 { yrVAL.smod = stringModifiers{ modifiers: ModXor, @@ -1296,7 +1303,7 @@ yrdefault: } case 49: yrDollar = yrS[yrpt-6 : yrpt+1] -//line parser/grammar.y:629 +//line parser/grammar.y:636 { lexer := asLexer(yrlex) @@ -1326,73 +1333,73 @@ yrdefault: } case 50: yrDollar = yrS[yrpt-0 : yrpt+1] -//line parser/grammar.y:661 +//line parser/grammar.y:668 { yrVAL.mod = 0 } case 51: yrDollar = yrS[yrpt-2 : yrpt+1] -//line parser/grammar.y:665 +//line parser/grammar.y:672 { yrVAL.mod = yrDollar[1].mod | yrDollar[2].mod } case 52: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:672 +//line parser/grammar.y:679 { yrVAL.mod = ModWide } case 53: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:673 +//line parser/grammar.y:680 { yrVAL.mod = ModASCII } case 54: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:674 +//line parser/grammar.y:681 { yrVAL.mod = ModNocase } case 55: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:675 +//line parser/grammar.y:682 { yrVAL.mod = ModFullword } case 56: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:676 +//line parser/grammar.y:683 { yrVAL.mod = ModPrivate } case 57: yrDollar = yrS[yrpt-0 : yrpt+1] -//line parser/grammar.y:682 +//line parser/grammar.y:689 { yrVAL.mod = 0 } case 58: yrDollar = yrS[yrpt-2 : yrpt+1] -//line parser/grammar.y:686 +//line parser/grammar.y:693 { yrVAL.mod = yrDollar[1].mod | yrDollar[2].mod } case 59: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:693 +//line parser/grammar.y:700 { yrVAL.mod = ModPrivate } case 60: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:699 +//line parser/grammar.y:706 { yrVAL.expr = &ast.Identifier{Identifier: yrDollar[1].s} } case 61: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:703 +//line parser/grammar.y:710 { yrVAL.expr = &ast.MemberAccess{ Container: yrDollar[1].expr, @@ -1401,7 +1408,7 @@ yrdefault: } case 62: yrDollar = yrS[yrpt-4 : yrpt+1] -//line parser/grammar.y:710 +//line parser/grammar.y:717 { yrVAL.expr = &ast.Subscripting{ Array: yrDollar[1].expr, @@ -1410,7 +1417,7 @@ yrdefault: } case 63: yrDollar = yrS[yrpt-4 : yrpt+1] -//line parser/grammar.y:717 +//line parser/grammar.y:724 { yrVAL.expr = &ast.FunctionCall{ Callable: yrDollar[1].expr, @@ -1419,55 +1426,55 @@ yrdefault: } case 64: yrDollar = yrS[yrpt-0 : yrpt+1] -//line parser/grammar.y:728 +//line parser/grammar.y:735 { yrVAL.exprs = []ast.Expression{} } case 65: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:732 +//line parser/grammar.y:739 { yrVAL.exprs = yrDollar[1].exprs } case 66: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:739 +//line parser/grammar.y:746 { yrVAL.exprs = []ast.Expression{yrDollar[1].expr} } case 67: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:743 +//line parser/grammar.y:750 { yrVAL.exprs = append(yrDollar[1].exprs, yrDollar[3].expr) } case 68: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:751 +//line parser/grammar.y:758 { yrVAL.reg = yrDollar[1].reg } case 69: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:759 +//line parser/grammar.y:766 { yrVAL.expr = yrDollar[1].expr } case 70: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:767 +//line parser/grammar.y:774 { yrVAL.expr = ast.KeywordTrue } case 71: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:771 +//line parser/grammar.y:778 { yrVAL.expr = ast.KeywordFalse } case 72: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:775 +//line parser/grammar.y:782 { yrVAL.expr = &ast.Operation{ Operator: ast.OpMatches, @@ -1476,7 +1483,7 @@ yrdefault: } case 73: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:782 +//line parser/grammar.y:789 { yrVAL.expr = &ast.Operation{ Operator: ast.OpContains, @@ -1485,7 +1492,7 @@ yrdefault: } case 74: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:789 +//line parser/grammar.y:796 { yrVAL.expr = &ast.Operation{ Operator: ast.OpIContains, @@ -1494,7 +1501,7 @@ yrdefault: } case 75: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:796 +//line parser/grammar.y:803 { yrVAL.expr = &ast.Operation{ Operator: ast.OpStartsWith, @@ -1503,7 +1510,7 @@ yrdefault: } case 76: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:803 +//line parser/grammar.y:810 { yrVAL.expr = &ast.Operation{ Operator: ast.OpIStartsWith, @@ -1512,7 +1519,7 @@ yrdefault: } case 77: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:810 +//line parser/grammar.y:817 { yrVAL.expr = &ast.Operation{ Operator: ast.OpEndsWith, @@ -1521,7 +1528,7 @@ yrdefault: } case 78: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:817 +//line parser/grammar.y:824 { yrVAL.expr = &ast.Operation{ Operator: ast.OpIEndsWith, @@ -1530,7 +1537,7 @@ yrdefault: } case 79: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:824 +//line parser/grammar.y:831 { yrVAL.expr = &ast.Operation{ Operator: ast.OpIEquals, @@ -1539,33 +1546,63 @@ yrdefault: } case 80: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:831 +//line parser/grammar.y:838 { + identifier := strings.TrimPrefix(yrDollar[1].s, "$") + // Exclude anonymous ($) strings. + if identifier != "" { + lexer := asLexer(yrlex) + if _, ok := lexer.strings[identifier]; !ok { + return lexer.setError( + gyperror.UndefinedStringIdentifierError, + `undefined string identifier: %s`, yrDollar[1].s) + } + } yrVAL.expr = &ast.StringIdentifier{ - Identifier: strings.TrimPrefix(yrDollar[1].s, "$"), + Identifier: identifier, } } case 81: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:837 +//line parser/grammar.y:854 { + identifier := strings.TrimPrefix(yrDollar[1].s, "$") + // Exclude anonymous ($) strings. + if identifier != "" { + lexer := asLexer(yrlex) + if _, ok := lexer.strings[identifier]; !ok { + return lexer.setError( + gyperror.UndefinedStringIdentifierError, + `undefined string identifier: %s`, yrDollar[1].s) + } + } yrVAL.expr = &ast.StringIdentifier{ - Identifier: strings.TrimPrefix(yrDollar[1].s, "$"), + Identifier: identifier, At: yrDollar[3].expr, } } case 82: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:844 +//line parser/grammar.y:871 { + identifier := strings.TrimPrefix(yrDollar[1].s, "$") + // Exclude anonymous ($) strings. + if identifier != "" { + lexer := asLexer(yrlex) + if _, ok := lexer.strings[identifier]; !ok { + return lexer.setError( + gyperror.UndefinedStringIdentifierError, + `undefined string identifier: %s`, yrDollar[1].s) + } + } yrVAL.expr = &ast.StringIdentifier{ - Identifier: strings.TrimPrefix(yrDollar[1].s, "$"), + Identifier: identifier, In: yrDollar[3].rng, } } case 83: yrDollar = yrS[yrpt-9 : yrpt+1] -//line parser/grammar.y:851 +//line parser/grammar.y:888 { yrVAL.expr = &ast.ForIn{ Quantifier: yrDollar[2].expr, @@ -1576,7 +1613,7 @@ yrdefault: } case 84: yrDollar = yrS[yrpt-8 : yrpt+1] -//line parser/grammar.y:860 +//line parser/grammar.y:897 { yrVAL.expr = &ast.ForOf{ Quantifier: yrDollar[2].expr, @@ -1586,7 +1623,7 @@ yrdefault: } case 85: yrDollar = yrS[yrpt-5 : yrpt+1] -//line parser/grammar.y:868 +//line parser/grammar.y:905 { yrVAL.expr = &ast.Of{ Quantifier: yrDollar[1].expr, @@ -1596,7 +1633,7 @@ yrdefault: } case 86: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:876 +//line parser/grammar.y:913 { yrVAL.expr = &ast.Of{ Quantifier: yrDollar[1].expr, @@ -1605,7 +1642,7 @@ yrdefault: } case 87: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:883 +//line parser/grammar.y:920 { yrVAL.expr = &ast.Of{ Quantifier: yrDollar[1].expr, @@ -1614,7 +1651,7 @@ yrdefault: } case 88: yrDollar = yrS[yrpt-4 : yrpt+1] -//line parser/grammar.y:890 +//line parser/grammar.y:927 { yrVAL.expr = &ast.Of{ Quantifier: &ast.Percentage{yrDollar[1].expr}, @@ -1623,7 +1660,7 @@ yrdefault: } case 89: yrDollar = yrS[yrpt-4 : yrpt+1] -//line parser/grammar.y:897 +//line parser/grammar.y:934 { yrVAL.expr = &ast.Of{ Quantifier: &ast.Percentage{yrDollar[1].expr}, @@ -1632,31 +1669,31 @@ yrdefault: } case 90: yrDollar = yrS[yrpt-2 : yrpt+1] -//line parser/grammar.y:904 +//line parser/grammar.y:941 { yrVAL.expr = &ast.Not{yrDollar[2].expr} } case 91: yrDollar = yrS[yrpt-2 : yrpt+1] -//line parser/grammar.y:908 +//line parser/grammar.y:945 { yrVAL.expr = &ast.Defined{yrDollar[2].expr} } case 92: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:912 +//line parser/grammar.y:949 { yrVAL.expr = operation(ast.OpAnd, yrDollar[1].expr, yrDollar[3].expr) } case 93: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:916 +//line parser/grammar.y:953 { yrVAL.expr = operation(ast.OpOr, yrDollar[1].expr, yrDollar[3].expr) } case 94: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:920 +//line parser/grammar.y:957 { yrVAL.expr = &ast.Operation{ Operator: ast.OpLessThan, @@ -1665,7 +1702,7 @@ yrdefault: } case 95: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:927 +//line parser/grammar.y:964 { yrVAL.expr = &ast.Operation{ Operator: ast.OpGreaterThan, @@ -1674,7 +1711,7 @@ yrdefault: } case 96: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:934 +//line parser/grammar.y:971 { yrVAL.expr = &ast.Operation{ Operator: ast.OpLessOrEqual, @@ -1683,7 +1720,7 @@ yrdefault: } case 97: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:941 +//line parser/grammar.y:978 { yrVAL.expr = &ast.Operation{ Operator: ast.OpGreaterOrEqual, @@ -1692,7 +1729,7 @@ yrdefault: } case 98: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:948 +//line parser/grammar.y:985 { yrVAL.expr = &ast.Operation{ Operator: ast.OpEqual, @@ -1701,7 +1738,7 @@ yrdefault: } case 99: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:955 +//line parser/grammar.y:992 { yrVAL.expr = &ast.Operation{ Operator: ast.OpNotEqual, @@ -1710,31 +1747,31 @@ yrdefault: } case 100: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:962 +//line parser/grammar.y:999 { yrVAL.expr = yrDollar[1].expr } case 101: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:966 +//line parser/grammar.y:1003 { yrVAL.expr = &ast.Group{yrDollar[2].expr} } case 102: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:974 +//line parser/grammar.y:1011 { yrVAL.node = &ast.Enum{Values: yrDollar[2].exprs} } case 103: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:978 +//line parser/grammar.y:1015 { yrVAL.node = yrDollar[1].rng } case 104: yrDollar = yrS[yrpt-5 : yrpt+1] -//line parser/grammar.y:986 +//line parser/grammar.y:1023 { yrVAL.rng = &ast.Range{ Start: yrDollar[2].expr, @@ -1743,155 +1780,193 @@ yrdefault: } case 105: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:997 +//line parser/grammar.y:1034 { yrVAL.exprs = []ast.Expression{yrDollar[1].expr} } case 106: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:1001 +//line parser/grammar.y:1038 { yrVAL.exprs = append(yrDollar[1].exprs, yrDollar[3].expr) } case 107: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:1009 +//line parser/grammar.y:1046 { yrVAL.node = &ast.Enum{Values: yrDollar[2].exprs} } case 108: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:1013 +//line parser/grammar.y:1050 { + lexer := asLexer(yrlex) + if len(lexer.strings) == 0 { + return lexer.setError( + gyperror.UndefinedStringIdentifierError, + `undefined string identifier: %s`, ast.KeywordThem) + } yrVAL.node = ast.KeywordThem } case 109: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:1021 +//line parser/grammar.y:1064 { yrVAL.exprs = []ast.Expression{yrDollar[1].si} } case 110: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:1025 +//line parser/grammar.y:1068 { yrVAL.exprs = append(yrDollar[1].exprs, yrDollar[3].si) } case 111: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:1033 +//line parser/grammar.y:1076 { + identifier := strings.TrimPrefix(yrDollar[1].s, "$") + lexer := asLexer(yrlex) + // Anonymous strings ($) in string enumerations are an error. + if _, ok := lexer.strings[identifier]; !ok || identifier == "" { + return lexer.setError( + gyperror.UndefinedStringIdentifierError, + `undefined string identifier: %s`, yrDollar[1].s) + } yrVAL.si = &ast.StringIdentifier{ - Identifier: strings.TrimPrefix(yrDollar[1].s, "$"), + Identifier: identifier, } } case 112: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:1039 +//line parser/grammar.y:1090 { + identifier := strings.TrimSuffix(yrDollar[1].s, "*") + lexer := asLexer(yrlex) + // There must be at least one defined string. + if len(identifier) == 0 && len(lexer.strings) == 0 { + return lexer.setError( + gyperror.UndefinedStringIdentifierError, + `undefined string identifier: %s`, yrDollar[1].s) + } + + // There must be at least one string that will match the wildcard. + identifier = strings.TrimPrefix(identifier, "$") + match := false + for s, _ := range lexer.strings { + if strings.HasPrefix(s, identifier) { + match = true + break + } + } + if !match { + return lexer.setError( + gyperror.UndefinedStringIdentifierError, + `undefined string identifier: %s`, yrDollar[1].s) + } + // Can't use "identifier" here as that has the asterisk stripped already. yrVAL.si = &ast.StringIdentifier{ Identifier: strings.TrimPrefix(yrDollar[1].s, "$"), } } case 113: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:1049 +//line parser/grammar.y:1124 { yrVAL.node = &ast.Enum{Values: yrDollar[2].exprs} } case 114: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:1057 +//line parser/grammar.y:1132 { yrVAL.exprs = []ast.Expression{yrDollar[1].ident} } case 115: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:1061 +//line parser/grammar.y:1136 { yrVAL.exprs = append(yrDollar[1].exprs, yrDollar[3].ident) } case 116: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:1069 +//line parser/grammar.y:1144 { yrVAL.ident = &ast.Identifier{Identifier: yrDollar[1].s} } case 117: yrDollar = yrS[yrpt-2 : yrpt+1] -//line parser/grammar.y:1073 +//line parser/grammar.y:1148 { yrVAL.ident = &ast.Identifier{Identifier: yrDollar[1].s + "*"} } case 118: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:1081 +//line parser/grammar.y:1156 { yrVAL.expr = yrDollar[1].expr } case 119: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:1085 +//line parser/grammar.y:1160 { yrVAL.expr = ast.KeywordAll } case 120: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:1089 +//line parser/grammar.y:1164 { yrVAL.expr = ast.KeywordAny } case 121: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:1093 +//line parser/grammar.y:1168 { yrVAL.expr = ast.KeywordNone } case 122: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:1101 +//line parser/grammar.y:1176 { yrVAL.ss = []string{yrDollar[1].s} } case 123: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:1105 +//line parser/grammar.y:1180 { yrVAL.ss = append(yrDollar[1].ss, yrDollar[3].s) } case 124: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:1112 +//line parser/grammar.y:1187 { yrVAL.node = yrDollar[1].expr } case 125: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:1116 +//line parser/grammar.y:1191 { yrVAL.node = yrDollar[1].node } case 126: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:1124 +//line parser/grammar.y:1199 { yrVAL.expr = &ast.Group{yrDollar[2].expr} } case 127: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:1128 +//line parser/grammar.y:1203 { yrVAL.expr = ast.KeywordFilesize } case 128: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:1132 +//line parser/grammar.y:1207 { yrVAL.expr = ast.KeywordEntrypoint } case 129: yrDollar = yrS[yrpt-4 : yrpt+1] -//line parser/grammar.y:1136 +//line parser/grammar.y:1211 { yrVAL.expr = &ast.FunctionCall{ Callable: &ast.Identifier{Identifier: yrDollar[1].s}, @@ -1900,19 +1975,19 @@ yrdefault: } case 130: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:1143 +//line parser/grammar.y:1218 { yrVAL.expr = &ast.LiteralInteger{yrDollar[1].i64} } case 131: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:1147 +//line parser/grammar.y:1222 { yrVAL.expr = &ast.LiteralFloat{yrDollar[1].f64} } case 132: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:1151 +//line parser/grammar.y:1226 { if err := validateUTF8(yrDollar[1].s); err != nil { return asLexer(yrlex).setError( @@ -1923,136 +1998,190 @@ yrdefault: } case 133: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:1160 +//line parser/grammar.y:1235 { + identifier := strings.TrimPrefix(yrDollar[1].s, "#") + if identifier != "" { + lexer := asLexer(yrlex) + if _, ok := lexer.strings[identifier]; !ok { + return lexer.setError( + gyperror.UndefinedStringIdentifierError, + `undefined string identifier: %s`, yrDollar[1].s) + } + } yrVAL.expr = &ast.StringCount{ - Identifier: strings.TrimPrefix(yrDollar[1].s, "#"), + Identifier: identifier, In: yrDollar[3].rng, } } case 134: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:1167 +//line parser/grammar.y:1251 { + identifier := strings.TrimPrefix(yrDollar[1].s, "#") + if identifier != "" { + lexer := asLexer(yrlex) + if _, ok := lexer.strings[identifier]; !ok { + return lexer.setError( + gyperror.UndefinedStringIdentifierError, + `undefined string identifier: %s`, yrDollar[1].s) + } + } yrVAL.expr = &ast.StringCount{ - Identifier: strings.TrimPrefix(yrDollar[1].s, "#"), + Identifier: identifier, } } case 135: yrDollar = yrS[yrpt-4 : yrpt+1] -//line parser/grammar.y:1173 +//line parser/grammar.y:1266 { + identifier := strings.TrimPrefix(yrDollar[1].s, "@") + if identifier != "" { + lexer := asLexer(yrlex) + if _, ok := lexer.strings[identifier]; !ok { + return lexer.setError( + gyperror.UndefinedStringIdentifierError, + `undefined string identifier: %s`, yrDollar[1].s) + } + } yrVAL.expr = &ast.StringOffset{ - Identifier: strings.TrimPrefix(yrDollar[1].s, "@"), + Identifier: identifier, Index: yrDollar[3].expr, } } case 136: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:1180 +//line parser/grammar.y:1282 { + identifier := strings.TrimPrefix(yrDollar[1].s, "@") + if identifier != "" { + lexer := asLexer(yrlex) + if _, ok := lexer.strings[identifier]; !ok { + return lexer.setError( + gyperror.UndefinedStringIdentifierError, + `undefined string identifier: %s`, yrDollar[1].s) + } + } yrVAL.expr = &ast.StringOffset{ - Identifier: strings.TrimPrefix(yrDollar[1].s, "@"), + Identifier: identifier, } } case 137: yrDollar = yrS[yrpt-4 : yrpt+1] -//line parser/grammar.y:1186 +//line parser/grammar.y:1297 { + identifier := strings.TrimPrefix(yrDollar[1].s, "!") + if identifier != "" { + lexer := asLexer(yrlex) + if _, ok := lexer.strings[identifier]; !ok { + return lexer.setError( + gyperror.UndefinedStringIdentifierError, + `undefined string identifier: %s`, yrDollar[1].s) + } + } yrVAL.expr = &ast.StringLength{ - Identifier: strings.TrimPrefix(yrDollar[1].s, "!"), + Identifier: identifier, Index: yrDollar[3].expr, } } case 138: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:1193 +//line parser/grammar.y:1313 { + identifier := strings.TrimPrefix(yrDollar[1].s, "!") + if identifier != "" { + lexer := asLexer(yrlex) + if _, ok := lexer.strings[identifier]; !ok { + return lexer.setError( + gyperror.UndefinedStringIdentifierError, + `undefined string identifier: %s`, yrDollar[1].s) + } + } yrVAL.expr = &ast.StringLength{ Identifier: strings.TrimPrefix(yrDollar[1].s, "!"), } } case 139: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:1199 +//line parser/grammar.y:1328 { yrVAL.expr = yrDollar[1].expr } case 140: yrDollar = yrS[yrpt-2 : yrpt+1] -//line parser/grammar.y:1203 +//line parser/grammar.y:1332 { yrVAL.expr = &ast.Minus{yrDollar[2].expr} } case 141: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:1207 +//line parser/grammar.y:1336 { yrVAL.expr = operation(ast.OpAdd, yrDollar[1].expr, yrDollar[3].expr) } case 142: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:1211 +//line parser/grammar.y:1340 { yrVAL.expr = operation(ast.OpSub, yrDollar[1].expr, yrDollar[3].expr) } case 143: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:1215 +//line parser/grammar.y:1344 { yrVAL.expr = operation(ast.OpMul, yrDollar[1].expr, yrDollar[3].expr) } case 144: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:1219 +//line parser/grammar.y:1348 { yrVAL.expr = operation(ast.OpDiv, yrDollar[1].expr, yrDollar[3].expr) } case 145: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:1223 +//line parser/grammar.y:1352 { yrVAL.expr = operation(ast.OpMod, yrDollar[1].expr, yrDollar[3].expr) } case 146: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:1227 +//line parser/grammar.y:1356 { yrVAL.expr = operation(ast.OpBitXor, yrDollar[1].expr, yrDollar[3].expr) } case 147: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:1231 +//line parser/grammar.y:1360 { yrVAL.expr = operation(ast.OpBitAnd, yrDollar[1].expr, yrDollar[3].expr) } case 148: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:1235 +//line parser/grammar.y:1364 { yrVAL.expr = operation(ast.OpBitOr, yrDollar[1].expr, yrDollar[3].expr) } case 149: yrDollar = yrS[yrpt-2 : yrpt+1] -//line parser/grammar.y:1239 +//line parser/grammar.y:1368 { yrVAL.expr = &ast.BitwiseNot{yrDollar[2].expr} } case 150: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:1243 +//line parser/grammar.y:1372 { yrVAL.expr = operation(ast.OpShiftLeft, yrDollar[1].expr, yrDollar[3].expr) } case 151: yrDollar = yrS[yrpt-3 : yrpt+1] -//line parser/grammar.y:1247 +//line parser/grammar.y:1376 { yrVAL.expr = operation(ast.OpShiftRight, yrDollar[1].expr, yrDollar[3].expr) } case 152: yrDollar = yrS[yrpt-1 : yrpt+1] -//line parser/grammar.y:1251 +//line parser/grammar.y:1380 { yrVAL.expr = yrDollar[1].reg } diff --git a/tests/grammar_test.go b/tests/grammar_test.go index 4e2dd47..2d8bd8b 100644 --- a/tests/grammar_test.go +++ b/tests/grammar_test.go @@ -286,6 +286,7 @@ rule AND_OR_PRECEDENCE_NO_PARENS { $foo1 = "foo1" $foo2 = /foo2/ $foo3 = { AA BB CC } + $foo4 = "I AM A STRING! ;)" condition: $foo1 or $foo2 or $foo3 and $foo4 } @@ -295,6 +296,7 @@ rule AND_OR_PRECEDENCE_PARENS { $foo1 = "foo1" $foo2 = /foo2/ $foo3 = { AA BB CC } + $foo4 = "I AM A STRING! ;)" condition: ($foo1 or $foo2 or $foo3) and $foo4 } @@ -459,6 +461,237 @@ func TestDuplicateStringModifiers(t *testing.T) { } } +func TestUndefinedStringIdentifier(t *testing.T) { + _, err := gyp.ParseString(` + rule UNDEFINED_STRING_IDENTIFIER { + strings: + $a = "AXSERS" + condition: + $s + }`) + if assert.Error(t, err) { + assert.Equal(t, `line 7: undefined string identifier: $s`, err.Error()) + } +} + +func TestUndefinedStringKeywordNoStrings(t *testing.T) { + _, err := gyp.ParseString(` + rule UNDEFINED_STRING_KEYWORD_NO_STRINGS { + condition: + 1 of them + }`) + if assert.Error(t, err) { + assert.Equal(t, `line 4: undefined string identifier: them`, err.Error()) + } +} + +func TestUndefinedStringIdentifierWildcard(t *testing.T) { + _, err := gyp.ParseString(` + rule UNDEFINED_STRING_IDENTIFIER_WITH_WILDCARD { + condition: + 1 of ($s*) + }`) + if assert.Error(t, err) { + assert.Equal(t, `line 4: undefined string identifier: $s*`, err.Error()) + } +} + +// Make sure wildcard expansion still works. +func TestUndefinedStringIdentifierWildcardExpansion(t *testing.T) { + _, err := gyp.ParseString(` + rule UNDEFINED_STRING_IDENTIFIER_WITH_WILDCARD_2 { + strings: + $s0 = "AXSERS" + condition: + 1 of ($s*) and $x + }`) + if assert.Error(t, err) { + assert.Equal(t, `line 7: undefined string identifier: $x`, err.Error()) + } +} + +func TestUndefinedStringCount(t *testing.T) { + _, err := gyp.ParseString(` + rule UNDEFINED_STRING_COUNT { + condition: + #s > 0 + }`) + if assert.Error(t, err) { + assert.Equal(t, `line 4: undefined string identifier: #s`, err.Error()) + } +} + +// Make sure anonymous string counts are allowed. +func TestUndefinedStringCountAnonymous(t *testing.T) { + _, err := gyp.ParseString(` + rule UNDEFINED_STRING_COUNT_ANONYMOUS { + strings: + $s = "AXSERS" + condition: + for any of them: (# > 10) + }`) + assert.NoError(t, err) +} + +func TestUndefinedStringOffset(t *testing.T) { + _, err := gyp.ParseString(` + rule UNDEFINED_STRING_OFFSET { + condition: + @s[0] > 0 + }`) + if assert.Error(t, err) { + assert.Equal(t, `line 4: undefined string identifier: @s`, err.Error()) + } +} + +// Make sure anonymous string offsets are allowed. +func TestUndefinedStringOffsetAnonymous(t *testing.T) { + _, err := gyp.ParseString(` + rule UNDEFINED_STRING_OFFSET_ANONYMOUS { + strings: + $s = "AXSERS" + condition: + for any of them: (@ > 10) + }`) + assert.NoError(t, err) +} + +func TestUndefinedStringOffsetAnonymousExpression(t *testing.T) { + _, err := gyp.ParseString(` + rule UNDEFINED_STRING_OFFSET_ANONYMOUS_EXPRESSION { + strings: + $s = "AXSERS" + condition: + for any of them: (@[1] > 10) + }`) + assert.NoError(t, err) +} + +func TestUndefinedStringLength(t *testing.T) { + _, err := gyp.ParseString(` + rule UNDEFINED_STRING_LENGTH { + condition: + !s == 40 + }`) + if assert.Error(t, err) { + assert.Equal(t, `line 4: undefined string identifier: !s`, err.Error()) + } +} + +// Make sure anonymous string lengths are allowed. +func TestUndefinedStringLengthAnonymous(t *testing.T) { + _, err := gyp.ParseString(` + rule UNDEFINED_STRING_LENGTH_ANONYMOUS { + strings: + $s = "AXSERS" + condition: + for any of them: (! > 5) + }`) + assert.NoError(t, err) +} + +func TestUndefinedStringLengthAnonymousExpression(t *testing.T) { + _, err := gyp.ParseString(` + rule UNDEFINED_STRING_LENGTH_ANONYMOUS_EXPRESSION { + strings: + $s = "AXSERS" + condition: + for any of them: (![1] > 5) + }`) + assert.NoError(t, err) +} + +func TestUndefinedStringAt(t *testing.T) { + _, err := gyp.ParseString(` + rule UNDEFINED_STRING_AT { + condition: + $s at 10 + }`) + if assert.Error(t, err) { + assert.Equal(t, `line 5: undefined string identifier: $s`, err.Error()) + } +} + +// Make sure anonymous string at are allowed. +func TestUndefinedStringAtAnonymous(t *testing.T) { + _, err := gyp.ParseString(` + rule UNDEFINED_STRING_AT_ANONYMOUS { + strings: + $s = "AXSERS" + condition: + for any of them: ($ at 5) + }`) + assert.NoError(t, err) +} + +func TestUndefinedStringInRange(t *testing.T) { + _, err := gyp.ParseString(` + rule UNDEFINED_STRING_IN_RANGE { + condition: + $s in (0..10) + }`) + if assert.Error(t, err) { + assert.Equal(t, `line 4: undefined string identifier: $s`, err.Error()) + } +} + +// Make sure anonymous string in range is allowed. +func TestUndefinedStringInRangeAnonymous(t *testing.T) { + _, err := gyp.ParseString(` + rule UNDEFINED_STRING_IN_RANGE_ANONYMOUS { + strings: + $s = "AXSERS" + condition: + for any of them: ($ in (0..10)) + }`) + assert.NoError(t, err) +} + +func TestUndefinedStringInRangeAnonymousExpression(t *testing.T) { + _, err := gyp.ParseString(` + rule UNDEFINED_STRING_IN_RANGE_ANONYMOUS_EXPRESSION { + strings: + $s = "AXSERS" + condition: + for any of them: ($ in (0..10)) + }`) + assert.NoError(t, err) +} + +func TestUndefinedStringCountInRange(t *testing.T) { + _, err := gyp.ParseString(` + rule UNDEFINED_STRING_COUNT_IN_RANGE { + condition: + #s in (0..10) == 2 + }`) + if assert.Error(t, err) { + assert.Equal(t, `line 4: undefined string identifier: #s`, err.Error()) + } +} + +// Make sure anonymous string count in range is allowed. +func TestUndefinedStringCountInRangeAnonymous(t *testing.T) { + _, err := gyp.ParseString(` + rule UNDEFINED_STRING_COUNT_IN_RANGE_ANONYMOUS { + strings: + $s = "AXSERS" + condition: + for any of them: (# in (0..10) == 2) + }`) + assert.NoError(t, err) +} + +func TestUndefinedStringEnumerationAnonymous(t *testing.T) { + _, err := gyp.ParseString(` + rule UNDEFINED_STRING_ENUMERATION_ANONYMOUS { + condition: + any of ($) + }`) + if assert.Error(t, err) { + assert.Equal(t, `line 4: undefined string identifier: $`, err.Error()) + } +} + func TestBase64AlphabetLength(t *testing.T) { _, err := gyp.ParseString(` rule BASE64 {