[tools][gen] Validate includes to external libraries
Check that conditional external includes are correctly guarded.
Make the CNF logic smarter about expression reduction with assumed truths.
Change-Id: Icde6370be789037676a16625161b38704e96c48f
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/162401
Commit-Queue: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/tools/src/cmd/gen/build/build.go b/tools/src/cmd/gen/build/build.go
index db2bbc0..a408148 100644
--- a/tools/src/cmd/gen/build/build.go
+++ b/tools/src/cmd/gen/build/build.go
@@ -443,6 +443,32 @@
return nil
}
+// checkInclude checks that the include statement is valid
+// file is the file that contains the include
+// include is the include statement
+// includeCondition holds the required conditions for the include
+func checkInclude(file *File, include Include, includeCondition Condition) error {
+ noneIfEmpty := func(cond Condition) string {
+ if len(cond) == 0 {
+ return "<none>"
+ }
+ return cond.String()
+ }
+ sourceConditions := cnf.And(cnf.And(include.Condition, file.Condition), file.Target.Condition)
+ targetConditions := includeCondition
+ if missing := targetConditions.AssumeTrue(sourceConditions); len(missing) > 0 {
+ return fmt.Errorf(`%v:%v #include "%v" requires guard: #if %v
+
+%v build conditions: %v
+%v build conditions: %v`,
+ file.Path(), include.Line, include.Path, strings.ToUpper(missing.String()),
+ file.Path(), noneIfEmpty(sourceConditions),
+ include.Path, targetConditions,
+ )
+ }
+ return nil
+}
+
// buildDependencies walks all the #includes in all files, building the dependency information for
// all targets and files in the project. Errors if any cyclic includes are found.
func buildDependencies(p *Project) error {
@@ -509,30 +535,19 @@
addExternalDependency(dependency)
}
- noneIfEmpty := func(cond Condition) string {
- if len(cond) == 0 {
- return "<none>"
- }
- return cond.String()
+ includeCondition := cnf.And(includeFile.Condition, includeFile.Target.Condition)
+ if err := checkInclude(file, include, includeCondition); err != nil {
+ return err
}
- sourceConditions := cnf.And(cnf.And(include.Condition, file.Condition), file.Target.Condition)
- targetConditions := cnf.And(includeFile.Condition, includeFile.Target.Condition)
- if missing := targetConditions.Remove(sourceConditions); len(missing) > 0 {
- return fmt.Errorf(`%v:%v #include "%v" requires guard: #if %v
-
-%v build conditions: %v
-%v build conditions: %v`,
- file.Path(), include.Line, include.Path, strings.ToUpper(missing.String()),
- file.Path(), noneIfEmpty(sourceConditions),
- include.Path, targetConditions,
- )
- }
-
} else {
// Check for external includes
for _, external := range p.externals.Values() {
if external.includePatternMatch(include.Path) {
addExternalDependency(external)
+
+ if err := checkInclude(file, include, external.Condition); err != nil {
+ return err
+ }
}
}
}
diff --git a/tools/src/cnf/expr.go b/tools/src/cnf/expr.go
index a3dec34..3b151a6 100644
--- a/tools/src/cnf/expr.go
+++ b/tools/src/cnf/expr.go
@@ -27,7 +27,9 @@
package cnf
-import "dawn.googlesource.com/dawn/tools/src/container"
+import (
+ "dawn.googlesource.com/dawn/tools/src/container"
+)
// Expr is a boolean expression, expressed in a Conjunctive Normal Form.
// Expr is an alias to Ands, which represent all the OR expressions that are
@@ -54,17 +56,25 @@
Var string
}
-// Remove returns a new expression with all the And expressions of o removed from e
-func (e Expr) Remove(o Expr) Expr {
- set := container.NewSet[Key]()
- for _, expr := range o {
- set.Add(expr.Key())
+// AssumeTrue returns a new simplified expression assuming o is true
+func (e Expr) AssumeTrue(o Expr) Expr {
+ isTrue := container.NewSet[Key]()
+ for _, ors := range o {
+ isTrue.Add(ors.Key())
}
+
out := Expr{}
- for _, expr := range e {
- if !set.Contains(expr.Key()) {
- out = append(out, expr)
+nextAnd:
+ for _, ors := range e {
+ for _, unary := range ors {
+ if isTrue.Contains(unary.Key()) {
+ continue nextAnd // At least one of the OR expressions is true
+ }
}
+ if isTrue.Contains(ors.Key()) {
+ continue // Whole OR expression is true
+ }
+ out = append(out, ors)
}
return out
}
diff --git a/tools/src/cnf/expr_test.go b/tools/src/cnf/expr_test.go
new file mode 100644
index 0000000..7636ee6
--- /dev/null
+++ b/tools/src/cnf/expr_test.go
@@ -0,0 +1,90 @@
+// Copyright 2023 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package cnf_test
+
+import (
+ "testing"
+
+ "dawn.googlesource.com/dawn/tools/src/cnf"
+)
+
+func TestAssumeTrue(t *testing.T) {
+ for _, test := range []struct {
+ expr string
+ isTrue string
+ expect string
+ }{
+ {expr: ``, isTrue: ``, expect: ``},
+ {expr: `a`, isTrue: ``, expect: `a`},
+ {expr: `a`, isTrue: `!a`, expect: `a`},
+ {expr: `a`, isTrue: `!b`, expect: `a`},
+ {expr: `a`, isTrue: `c`, expect: `a`},
+ {expr: `a`, isTrue: `c`, expect: `a`},
+ {expr: `a || b`, isTrue: ``, expect: `a || b`},
+ {expr: `a || b`, isTrue: `!a`, expect: `a || b`},
+ {expr: `a || b`, isTrue: `!b`, expect: `a || b`},
+ {expr: `a || b`, isTrue: `c`, expect: `a || b`},
+ {expr: `a || b`, isTrue: `c`, expect: `a || b`},
+ {expr: `a && b`, isTrue: ``, expect: `a && b`},
+ {expr: `a && b`, isTrue: `!a`, expect: `a && b`},
+ {expr: `a && b`, isTrue: `!b`, expect: `a && b`},
+ {expr: `a && b`, isTrue: `c`, expect: `a && b`},
+ {expr: `a && b`, isTrue: `c`, expect: `a && b`},
+
+ {expr: `a`, isTrue: `a`, expect: ``},
+ {expr: `a || b`, isTrue: `a`, expect: ``},
+ {expr: `a || b`, isTrue: `b`, expect: ``},
+ {expr: `a && b`, isTrue: `a`, expect: `b`},
+ {expr: `a && b`, isTrue: `b`, expect: `a`},
+
+ {expr: `a`, isTrue: `a && b`, expect: ``},
+ {expr: `a || b`, isTrue: `a && b`, expect: ``},
+ {expr: `a || b`, isTrue: `b && b`, expect: ``},
+ {expr: `a && b`, isTrue: `a && b`, expect: ``},
+
+ {expr: `a && c`, isTrue: `a && b`, expect: `c`},
+ {expr: `(a || b) && c`, isTrue: `a && b`, expect: `c`},
+ {expr: `(a || b) && c`, isTrue: `b && c`, expect: ``},
+ {expr: `a && b && c`, isTrue: `a && b`, expect: `c`},
+ } {
+ expr, err := cnf.Parse(test.expr)
+ if err != nil {
+ t.Errorf(`unexpected error returned from Parse('%v'): %v`, test.expr, err)
+ continue
+ }
+ isTrue, err := cnf.Parse(test.isTrue)
+ if err != nil {
+ t.Errorf(`unexpected error returned from Parse('%v'): %v`, test.isTrue, err)
+ continue
+ }
+ got := expr.AssumeTrue(isTrue).String()
+ if test.expect != got {
+ t.Errorf("('%v').AssumeTrue('%v') returned '%v', expected '%v'", expr, isTrue, got, test.expect)
+ }
+ }
+}