1// Copyright 2020 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5// Package constraint implements parsing and evaluation of build constraint lines. 6// See https://golang.org/cmd/go/#hdr-Build_constraints for documentation about build constraints themselves. 7// 8// This package parses both the original “// +build” syntax and the “//go:build” syntax that was added in Go 1.17. 9// See https://golang.org/design/draft-gobuild for details about the “//go:build” syntax. 10package constraint 11 12import ( 13 "errors" 14 "strings" 15 "unicode" 16 "unicode/utf8" 17) 18 19// maxSize is a limit used to control the complexity of expressions, in order 20// to prevent stack exhaustion issues due to recursion. 21const maxSize = 1000 22 23// An Expr is a build tag constraint expression. 24// The underlying concrete type is *[AndExpr], *[OrExpr], *[NotExpr], or *[TagExpr]. 25type Expr interface { 26 // String returns the string form of the expression, 27 // using the boolean syntax used in //go:build lines. 28 String() string 29 30 // Eval reports whether the expression evaluates to true. 31 // It calls ok(tag) as needed to find out whether a given build tag 32 // is satisfied by the current build configuration. 33 Eval(ok func(tag string) bool) bool 34 35 // The presence of an isExpr method explicitly marks the type as an Expr. 36 // Only implementations in this package should be used as Exprs. 37 isExpr() 38} 39 40// A TagExpr is an [Expr] for the single tag Tag. 41type TagExpr struct { 42 Tag string // for example, “linux” or “cgo” 43} 44 45func (x *TagExpr) isExpr() {} 46 47func (x *TagExpr) Eval(ok func(tag string) bool) bool { 48 return ok(x.Tag) 49} 50 51func (x *TagExpr) String() string { 52 return x.Tag 53} 54 55func tag(tag string) Expr { return &TagExpr{tag} } 56 57// A NotExpr represents the expression !X (the negation of X). 58type NotExpr struct { 59 X Expr 60} 61 62func (x *NotExpr) isExpr() {} 63 64func (x *NotExpr) Eval(ok func(tag string) bool) bool { 65 return !x.X.Eval(ok) 66} 67 68func (x *NotExpr) String() string { 69 s := x.X.String() 70 switch x.X.(type) { 71 case *AndExpr, *OrExpr: 72 s = "(" + s + ")" 73 } 74 return "!" + s 75} 76 77func not(x Expr) Expr { return &NotExpr{x} } 78 79// An AndExpr represents the expression X && Y. 80type AndExpr struct { 81 X, Y Expr 82} 83 84func (x *AndExpr) isExpr() {} 85 86func (x *AndExpr) Eval(ok func(tag string) bool) bool { 87 // Note: Eval both, to make sure ok func observes all tags. 88 xok := x.X.Eval(ok) 89 yok := x.Y.Eval(ok) 90 return xok && yok 91} 92 93func (x *AndExpr) String() string { 94 return andArg(x.X) + " && " + andArg(x.Y) 95} 96 97func andArg(x Expr) string { 98 s := x.String() 99 if _, ok := x.(*OrExpr); ok { 100 s = "(" + s + ")" 101 } 102 return s 103} 104 105func and(x, y Expr) Expr { 106 return &AndExpr{x, y} 107} 108 109// An OrExpr represents the expression X || Y. 110type OrExpr struct { 111 X, Y Expr 112} 113 114func (x *OrExpr) isExpr() {} 115 116func (x *OrExpr) Eval(ok func(tag string) bool) bool { 117 // Note: Eval both, to make sure ok func observes all tags. 118 xok := x.X.Eval(ok) 119 yok := x.Y.Eval(ok) 120 return xok || yok 121} 122 123func (x *OrExpr) String() string { 124 return orArg(x.X) + " || " + orArg(x.Y) 125} 126 127func orArg(x Expr) string { 128 s := x.String() 129 if _, ok := x.(*AndExpr); ok { 130 s = "(" + s + ")" 131 } 132 return s 133} 134 135func or(x, y Expr) Expr { 136 return &OrExpr{x, y} 137} 138 139// A SyntaxError reports a syntax error in a parsed build expression. 140type SyntaxError struct { 141 Offset int // byte offset in input where error was detected 142 Err string // description of error 143} 144 145func (e *SyntaxError) Error() string { 146 return e.Err 147} 148 149var errNotConstraint = errors.New("not a build constraint") 150 151// Parse parses a single build constraint line of the form “//go:build ...” or “// +build ...” 152// and returns the corresponding boolean expression. 153func Parse(line string) (Expr, error) { 154 if text, ok := splitGoBuild(line); ok { 155 return parseExpr(text) 156 } 157 if text, ok := splitPlusBuild(line); ok { 158 return parsePlusBuildExpr(text) 159 } 160 return nil, errNotConstraint 161} 162 163// IsGoBuild reports whether the line of text is a “//go:build” constraint. 164// It only checks the prefix of the text, not that the expression itself parses. 165func IsGoBuild(line string) bool { 166 _, ok := splitGoBuild(line) 167 return ok 168} 169 170// splitGoBuild splits apart the leading //go:build prefix in line from the build expression itself. 171// It returns "", false if the input is not a //go:build line or if the input contains multiple lines. 172func splitGoBuild(line string) (expr string, ok bool) { 173 // A single trailing newline is OK; otherwise multiple lines are not. 174 if len(line) > 0 && line[len(line)-1] == '\n' { 175 line = line[:len(line)-1] 176 } 177 if strings.Contains(line, "\n") { 178 return "", false 179 } 180 181 if !strings.HasPrefix(line, "//go:build") { 182 return "", false 183 } 184 185 line = strings.TrimSpace(line) 186 line = line[len("//go:build"):] 187 188 // If strings.TrimSpace finds more to trim after removing the //go:build prefix, 189 // it means that the prefix was followed by a space, making this a //go:build line 190 // (as opposed to a //go:buildsomethingelse line). 191 // If line is empty, we had "//go:build" by itself, which also counts. 192 trim := strings.TrimSpace(line) 193 if len(line) == len(trim) && line != "" { 194 return "", false 195 } 196 197 return trim, true 198} 199 200// An exprParser holds state for parsing a build expression. 201type exprParser struct { 202 s string // input string 203 i int // next read location in s 204 205 tok string // last token read 206 isTag bool 207 pos int // position (start) of last token 208 209 size int 210} 211 212// parseExpr parses a boolean build tag expression. 213func parseExpr(text string) (x Expr, err error) { 214 defer func() { 215 if e := recover(); e != nil { 216 if e, ok := e.(*SyntaxError); ok { 217 err = e 218 return 219 } 220 panic(e) // unreachable unless parser has a bug 221 } 222 }() 223 224 p := &exprParser{s: text} 225 x = p.or() 226 if p.tok != "" { 227 panic(&SyntaxError{Offset: p.pos, Err: "unexpected token " + p.tok}) 228 } 229 return x, nil 230} 231 232// or parses a sequence of || expressions. 233// On entry, the next input token has not yet been lexed. 234// On exit, the next input token has been lexed and is in p.tok. 235func (p *exprParser) or() Expr { 236 x := p.and() 237 for p.tok == "||" { 238 x = or(x, p.and()) 239 } 240 return x 241} 242 243// and parses a sequence of && expressions. 244// On entry, the next input token has not yet been lexed. 245// On exit, the next input token has been lexed and is in p.tok. 246func (p *exprParser) and() Expr { 247 x := p.not() 248 for p.tok == "&&" { 249 x = and(x, p.not()) 250 } 251 return x 252} 253 254// not parses a ! expression. 255// On entry, the next input token has not yet been lexed. 256// On exit, the next input token has been lexed and is in p.tok. 257func (p *exprParser) not() Expr { 258 p.size++ 259 if p.size > maxSize { 260 panic(&SyntaxError{Offset: p.pos, Err: "build expression too large"}) 261 } 262 p.lex() 263 if p.tok == "!" { 264 p.lex() 265 if p.tok == "!" { 266 panic(&SyntaxError{Offset: p.pos, Err: "double negation not allowed"}) 267 } 268 return not(p.atom()) 269 } 270 return p.atom() 271} 272 273// atom parses a tag or a parenthesized expression. 274// On entry, the next input token HAS been lexed. 275// On exit, the next input token has been lexed and is in p.tok. 276func (p *exprParser) atom() Expr { 277 // first token already in p.tok 278 if p.tok == "(" { 279 pos := p.pos 280 defer func() { 281 if e := recover(); e != nil { 282 if e, ok := e.(*SyntaxError); ok && e.Err == "unexpected end of expression" { 283 e.Err = "missing close paren" 284 } 285 panic(e) 286 } 287 }() 288 x := p.or() 289 if p.tok != ")" { 290 panic(&SyntaxError{Offset: pos, Err: "missing close paren"}) 291 } 292 p.lex() 293 return x 294 } 295 296 if !p.isTag { 297 if p.tok == "" { 298 panic(&SyntaxError{Offset: p.pos, Err: "unexpected end of expression"}) 299 } 300 panic(&SyntaxError{Offset: p.pos, Err: "unexpected token " + p.tok}) 301 } 302 tok := p.tok 303 p.lex() 304 return tag(tok) 305} 306 307// lex finds and consumes the next token in the input stream. 308// On return, p.tok is set to the token text, 309// p.isTag reports whether the token was a tag, 310// and p.pos records the byte offset of the start of the token in the input stream. 311// If lex reaches the end of the input, p.tok is set to the empty string. 312// For any other syntax error, lex panics with a SyntaxError. 313func (p *exprParser) lex() { 314 p.isTag = false 315 for p.i < len(p.s) && (p.s[p.i] == ' ' || p.s[p.i] == '\t') { 316 p.i++ 317 } 318 if p.i >= len(p.s) { 319 p.tok = "" 320 p.pos = p.i 321 return 322 } 323 switch p.s[p.i] { 324 case '(', ')', '!': 325 p.pos = p.i 326 p.i++ 327 p.tok = p.s[p.pos:p.i] 328 return 329 330 case '&', '|': 331 if p.i+1 >= len(p.s) || p.s[p.i+1] != p.s[p.i] { 332 panic(&SyntaxError{Offset: p.i, Err: "invalid syntax at " + string(rune(p.s[p.i]))}) 333 } 334 p.pos = p.i 335 p.i += 2 336 p.tok = p.s[p.pos:p.i] 337 return 338 } 339 340 tag := p.s[p.i:] 341 for i, c := range tag { 342 if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' { 343 tag = tag[:i] 344 break 345 } 346 } 347 if tag == "" { 348 c, _ := utf8.DecodeRuneInString(p.s[p.i:]) 349 panic(&SyntaxError{Offset: p.i, Err: "invalid syntax at " + string(c)}) 350 } 351 352 p.pos = p.i 353 p.i += len(tag) 354 p.tok = p.s[p.pos:p.i] 355 p.isTag = true 356} 357 358// IsPlusBuild reports whether the line of text is a “// +build” constraint. 359// It only checks the prefix of the text, not that the expression itself parses. 360func IsPlusBuild(line string) bool { 361 _, ok := splitPlusBuild(line) 362 return ok 363} 364 365// splitPlusBuild splits apart the leading // +build prefix in line from the build expression itself. 366// It returns "", false if the input is not a // +build line or if the input contains multiple lines. 367func splitPlusBuild(line string) (expr string, ok bool) { 368 // A single trailing newline is OK; otherwise multiple lines are not. 369 if len(line) > 0 && line[len(line)-1] == '\n' { 370 line = line[:len(line)-1] 371 } 372 if strings.Contains(line, "\n") { 373 return "", false 374 } 375 376 if !strings.HasPrefix(line, "//") { 377 return "", false 378 } 379 line = line[len("//"):] 380 // Note the space is optional; "//+build" is recognized too. 381 line = strings.TrimSpace(line) 382 383 if !strings.HasPrefix(line, "+build") { 384 return "", false 385 } 386 line = line[len("+build"):] 387 388 // If strings.TrimSpace finds more to trim after removing the +build prefix, 389 // it means that the prefix was followed by a space, making this a +build line 390 // (as opposed to a +buildsomethingelse line). 391 // If line is empty, we had "// +build" by itself, which also counts. 392 trim := strings.TrimSpace(line) 393 if len(line) == len(trim) && line != "" { 394 return "", false 395 } 396 397 return trim, true 398} 399 400// parsePlusBuildExpr parses a legacy build tag expression (as used with “// +build”). 401func parsePlusBuildExpr(text string) (Expr, error) { 402 // Only allow up to 100 AND/OR operators for "old" syntax. 403 // This is much less than the limit for "new" syntax, 404 // but uses of old syntax were always very simple. 405 const maxOldSize = 100 406 size := 0 407 408 var x Expr 409 for _, clause := range strings.Fields(text) { 410 var y Expr 411 for _, lit := range strings.Split(clause, ",") { 412 var z Expr 413 var neg bool 414 if strings.HasPrefix(lit, "!!") || lit == "!" { 415 z = tag("ignore") 416 } else { 417 if strings.HasPrefix(lit, "!") { 418 neg = true 419 lit = lit[len("!"):] 420 } 421 if isValidTag(lit) { 422 z = tag(lit) 423 } else { 424 z = tag("ignore") 425 } 426 if neg { 427 z = not(z) 428 } 429 } 430 if y == nil { 431 y = z 432 } else { 433 if size++; size > maxOldSize { 434 return nil, errComplex 435 } 436 y = and(y, z) 437 } 438 } 439 if x == nil { 440 x = y 441 } else { 442 if size++; size > maxOldSize { 443 return nil, errComplex 444 } 445 x = or(x, y) 446 } 447 } 448 if x == nil { 449 x = tag("ignore") 450 } 451 return x, nil 452} 453 454// isValidTag reports whether the word is a valid build tag. 455// Tags must be letters, digits, underscores or dots. 456// Unlike in Go identifiers, all digits are fine (e.g., "386"). 457func isValidTag(word string) bool { 458 if word == "" { 459 return false 460 } 461 for _, c := range word { 462 if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' { 463 return false 464 } 465 } 466 return true 467} 468 469var errComplex = errors.New("expression too complex for // +build lines") 470 471// PlusBuildLines returns a sequence of “// +build” lines that evaluate to the build expression x. 472// If the expression is too complex to convert directly to “// +build” lines, PlusBuildLines returns an error. 473func PlusBuildLines(x Expr) ([]string, error) { 474 // Push all NOTs to the expression leaves, so that //go:build !(x && y) can be treated as !x || !y. 475 // This rewrite is both efficient and commonly needed, so it's worth doing. 476 // Essentially all other possible rewrites are too expensive and too rarely needed. 477 x = pushNot(x, false) 478 479 // Split into AND of ORs of ANDs of literals (tag or NOT tag). 480 var split [][][]Expr 481 for _, or := range appendSplitAnd(nil, x) { 482 var ands [][]Expr 483 for _, and := range appendSplitOr(nil, or) { 484 var lits []Expr 485 for _, lit := range appendSplitAnd(nil, and) { 486 switch lit.(type) { 487 case *TagExpr, *NotExpr: 488 lits = append(lits, lit) 489 default: 490 return nil, errComplex 491 } 492 } 493 ands = append(ands, lits) 494 } 495 split = append(split, ands) 496 } 497 498 // If all the ORs have length 1 (no actual OR'ing going on), 499 // push the top-level ANDs to the bottom level, so that we get 500 // one // +build line instead of many. 501 maxOr := 0 502 for _, or := range split { 503 if maxOr < len(or) { 504 maxOr = len(or) 505 } 506 } 507 if maxOr == 1 { 508 var lits []Expr 509 for _, or := range split { 510 lits = append(lits, or[0]...) 511 } 512 split = [][][]Expr{{lits}} 513 } 514 515 // Prepare the +build lines. 516 var lines []string 517 for _, or := range split { 518 line := "// +build" 519 for _, and := range or { 520 clause := "" 521 for i, lit := range and { 522 if i > 0 { 523 clause += "," 524 } 525 clause += lit.String() 526 } 527 line += " " + clause 528 } 529 lines = append(lines, line) 530 } 531 532 return lines, nil 533} 534 535// pushNot applies DeMorgan's law to push negations down the expression, 536// so that only tags are negated in the result. 537// (It applies the rewrites !(X && Y) => (!X || !Y) and !(X || Y) => (!X && !Y).) 538func pushNot(x Expr, not bool) Expr { 539 switch x := x.(type) { 540 default: 541 // unreachable 542 return x 543 case *NotExpr: 544 if _, ok := x.X.(*TagExpr); ok && !not { 545 return x 546 } 547 return pushNot(x.X, !not) 548 case *TagExpr: 549 if not { 550 return &NotExpr{X: x} 551 } 552 return x 553 case *AndExpr: 554 x1 := pushNot(x.X, not) 555 y1 := pushNot(x.Y, not) 556 if not { 557 return or(x1, y1) 558 } 559 if x1 == x.X && y1 == x.Y { 560 return x 561 } 562 return and(x1, y1) 563 case *OrExpr: 564 x1 := pushNot(x.X, not) 565 y1 := pushNot(x.Y, not) 566 if not { 567 return and(x1, y1) 568 } 569 if x1 == x.X && y1 == x.Y { 570 return x 571 } 572 return or(x1, y1) 573 } 574} 575 576// appendSplitAnd appends x to list while splitting apart any top-level && expressions. 577// For example, appendSplitAnd({W}, X && Y && Z) = {W, X, Y, Z}. 578func appendSplitAnd(list []Expr, x Expr) []Expr { 579 if x, ok := x.(*AndExpr); ok { 580 list = appendSplitAnd(list, x.X) 581 list = appendSplitAnd(list, x.Y) 582 return list 583 } 584 return append(list, x) 585} 586 587// appendSplitOr appends x to list while splitting apart any top-level || expressions. 588// For example, appendSplitOr({W}, X || Y || Z) = {W, X, Y, Z}. 589func appendSplitOr(list []Expr, x Expr) []Expr { 590 if x, ok := x.(*OrExpr); ok { 591 list = appendSplitOr(list, x.X) 592 list = appendSplitOr(list, x.Y) 593 return list 594 } 595 return append(list, x) 596} 597