|  | // Copyright 2022 The Tint Authors. | 
|  | // | 
|  | // Licensed 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. | 
|  |  | 
|  | #include "src/tint/lang/wgsl/resolver/resolver.h" | 
|  | #include "src/tint/lang/wgsl/resolver/resolver_test_helper.h" | 
|  | #include "src/tint/utils/text/string_stream.h" | 
|  |  | 
|  | #include "gmock/gmock.h" | 
|  |  | 
|  | namespace tint::resolver { | 
|  | namespace { | 
|  |  | 
|  | using namespace tint::builtin::fluent_types;  // NOLINT | 
|  | using namespace tint::number_suffixes;        // NOLINT | 
|  |  | 
|  | struct ResolverAliasAnalysisTest : public resolver::TestHelper, public testing::Test {}; | 
|  |  | 
|  | // Base test harness for tests that pass two pointers to a function. | 
|  | // | 
|  | // fn target(p1 : ptr<function, i32>, p2 : ptr<function, i32>) { | 
|  | //   <test statements> | 
|  | // } | 
|  | // fn caller() { | 
|  | //   var v1 : i32; | 
|  | //   var v2 : i32; | 
|  | //   target(&v1, aliased ? &v1 : &v2); | 
|  | // } | 
|  | struct TwoPointerConfig { | 
|  | builtin::AddressSpace address_space;  // The address space for the pointers. | 
|  | bool aliased;                         // Whether the pointers alias or not. | 
|  | }; | 
|  | class TwoPointers : public ResolverTestWithParam<TwoPointerConfig> { | 
|  | protected: | 
|  | void SetUp() override { | 
|  | utils::Vector<const ast::Statement*, 4> body; | 
|  | if (GetParam().address_space == builtin::AddressSpace::kFunction) { | 
|  | body.Push(Decl(Var("v1", ty.i32()))); | 
|  | body.Push(Decl(Var("v2", ty.i32()))); | 
|  | } else { | 
|  | GlobalVar("v1", builtin::AddressSpace::kPrivate, ty.i32()); | 
|  | GlobalVar("v2", builtin::AddressSpace::kPrivate, ty.i32()); | 
|  | } | 
|  | body.Push(CallStmt(Call("target", AddressOf(Source{{12, 34}}, "v1"), | 
|  | AddressOf(Source{{56, 78}}, GetParam().aliased ? "v1" : "v2")))); | 
|  | Func("caller", utils::Empty, ty.void_(), body); | 
|  | } | 
|  |  | 
|  | void Run(utils::Vector<const ast::Statement*, 4>&& body, const char* err = nullptr) { | 
|  | auto addrspace = GetParam().address_space; | 
|  | Func("target", | 
|  | utils::Vector{ | 
|  | Param("p1", ty.ptr<i32>(addrspace)), | 
|  | Param("p2", ty.ptr<i32>(addrspace)), | 
|  | }, | 
|  | ty.void_(), std::move(body)); | 
|  | if (GetParam().aliased && err) { | 
|  | EXPECT_FALSE(r()->Resolve()); | 
|  | EXPECT_EQ(r()->error(), err); | 
|  | } else { | 
|  | EXPECT_TRUE(r()->Resolve()) << r()->error(); | 
|  | } | 
|  | } | 
|  | }; | 
|  |  | 
|  | TEST_P(TwoPointers, ReadRead) { | 
|  | // _ = *p1; | 
|  | // _ = *p2; | 
|  | Run({ | 
|  | Assign(Phony(), Deref("p1")), | 
|  | Assign(Phony(), Deref("p2")), | 
|  | }); | 
|  | } | 
|  |  | 
|  | TEST_P(TwoPointers, ReadWrite) { | 
|  | // _ = *p1; | 
|  | // *p2 = 42; | 
|  | Run( | 
|  | { | 
|  | Assign(Phony(), Deref("p1")), | 
|  | Assign(Deref("p2"), 42_a), | 
|  | }, | 
|  | R"(56:78 error: invalid aliased pointer argument | 
|  | 12:34 note: aliases with another argument passed here)"); | 
|  | } | 
|  |  | 
|  | TEST_P(TwoPointers, WriteRead) { | 
|  | // *p1 = 42; | 
|  | // _ = *p2; | 
|  | Run( | 
|  | { | 
|  | Assign(Deref("p1"), 42_a), | 
|  | Assign(Phony(), Deref("p2")), | 
|  | }, | 
|  | R"(56:78 error: invalid aliased pointer argument | 
|  | 12:34 note: aliases with another argument passed here)"); | 
|  | } | 
|  |  | 
|  | TEST_P(TwoPointers, WriteWrite) { | 
|  | // *p1 = 42; | 
|  | // *p2 = 42; | 
|  | Run( | 
|  | { | 
|  | Assign(Deref("p1"), 42_a), | 
|  | Assign(Deref("p2"), 42_a), | 
|  | }, | 
|  | R"(56:78 error: invalid aliased pointer argument | 
|  | 12:34 note: aliases with another argument passed here)"); | 
|  | } | 
|  |  | 
|  | TEST_P(TwoPointers, ReadWriteThroughChain) { | 
|  | // fn f2(p1 : ptr<function, i32>, p2 : ptr<function, i32>) { | 
|  | //   _ = *p1; | 
|  | //   *p2 = 42; | 
|  | // } | 
|  | // fn f1(p1 : ptr<function, i32>, p2 : ptr<function, i32>) { | 
|  | //   f2(p1, p2); | 
|  | // } | 
|  | // | 
|  | // f1(p1, p2); | 
|  | Func("f2", | 
|  | utils::Vector{ | 
|  | Param("p1", ty.ptr<i32>(GetParam().address_space)), | 
|  | Param("p2", ty.ptr<i32>(GetParam().address_space)), | 
|  | }, | 
|  | ty.void_(), | 
|  | utils::Vector{ | 
|  | Assign(Phony(), Deref("p1")), | 
|  | Assign(Deref("p2"), 42_a), | 
|  | }); | 
|  | Func("f1", | 
|  | utils::Vector{ | 
|  | Param("p1", ty.ptr<i32>(GetParam().address_space)), | 
|  | Param("p2", ty.ptr<i32>(GetParam().address_space)), | 
|  | }, | 
|  | ty.void_(), | 
|  | utils::Vector{ | 
|  | CallStmt(Call("f2", "p1", "p2")), | 
|  | }); | 
|  | Run( | 
|  | { | 
|  | CallStmt(Call("f1", "p1", "p2")), | 
|  | }, | 
|  | R"(56:78 error: invalid aliased pointer argument | 
|  | 12:34 note: aliases with another argument passed here)"); | 
|  | } | 
|  |  | 
|  | TEST_P(TwoPointers, ReadWriteAcrossDifferentFunctions) { | 
|  | // fn f1(p1 : ptr<function, i32>) { | 
|  | //   _ = *p1; | 
|  | // } | 
|  | // fn f2(p2 : ptr<function, i32>) { | 
|  | //   *p2 = 42; | 
|  | // } | 
|  | // | 
|  | // f1(p1); | 
|  | // f2(p2); | 
|  | Func("f1", | 
|  | utils::Vector<const ast::Parameter*, 4>{ | 
|  | Param("p1", ty.ptr<i32>(GetParam().address_space)), | 
|  | }, | 
|  | ty.void_(), | 
|  | utils::Vector{ | 
|  | Assign(Phony(), Deref("p1")), | 
|  | }); | 
|  | Func("f2", | 
|  | utils::Vector<const ast::Parameter*, 4>{ | 
|  | Param("p2", ty.ptr<i32>(GetParam().address_space)), | 
|  | }, | 
|  | ty.void_(), | 
|  | utils::Vector{ | 
|  | Assign(Deref("p2"), 42_a), | 
|  | }); | 
|  | Run( | 
|  | { | 
|  | CallStmt(Call("f1", "p1")), | 
|  | CallStmt(Call("f2", "p2")), | 
|  | }, | 
|  | R"(56:78 error: invalid aliased pointer argument | 
|  | 12:34 note: aliases with another argument passed here)"); | 
|  | } | 
|  |  | 
|  | INSTANTIATE_TEST_SUITE_P(ResolverAliasAnalysisTest, | 
|  | TwoPointers, | 
|  | ::testing::Values(TwoPointerConfig{builtin::AddressSpace::kFunction, | 
|  | false}, | 
|  | TwoPointerConfig{builtin::AddressSpace::kFunction, true}, | 
|  | TwoPointerConfig{builtin::AddressSpace::kPrivate, false}, | 
|  | TwoPointerConfig{builtin::AddressSpace::kPrivate, true}), | 
|  | [](const ::testing::TestParamInfo<TwoPointers::ParamType>& p) { | 
|  | utils::StringStream ss; | 
|  | ss << (p.param.aliased ? "Aliased" : "Unaliased") << "_" | 
|  | << p.param.address_space; | 
|  | return ss.str(); | 
|  | }); | 
|  |  | 
|  | // Base test harness for tests that pass a pointer to a function that references a module-scope var. | 
|  | // | 
|  | // var<private> global_1 : i32; | 
|  | // var<private> global_2 : i32; | 
|  | // fn target(p1 : ptr<private, i32>) { | 
|  | //   <test statements> | 
|  | // } | 
|  | // fn caller() { | 
|  | //   target(aliased ? &global_1 : &global_2); | 
|  | // } | 
|  | class OnePointerOneModuleScope : public ResolverTestWithParam<bool> { | 
|  | protected: | 
|  | void SetUp() override { | 
|  | GlobalVar("global_1", builtin::AddressSpace::kPrivate, ty.i32()); | 
|  | GlobalVar("global_2", builtin::AddressSpace::kPrivate, ty.i32()); | 
|  | Func("caller", utils::Empty, ty.void_(), | 
|  | utils::Vector{ | 
|  | CallStmt(Call("target", | 
|  | AddressOf(Source{{12, 34}}, GetParam() ? "global_1" : "global_2"))), | 
|  | }); | 
|  | } | 
|  |  | 
|  | void Run(utils::Vector<const ast::Statement*, 4>&& body, const char* err = nullptr) { | 
|  | Func("target", | 
|  | utils::Vector<const ast::Parameter*, 4>{ | 
|  | Param("p1", ty.ptr<i32>(builtin::AddressSpace::kPrivate)), | 
|  | }, | 
|  | ty.void_(), std::move(body)); | 
|  | if (GetParam() && err) { | 
|  | EXPECT_FALSE(r()->Resolve()); | 
|  | EXPECT_EQ(r()->error(), err); | 
|  | } else { | 
|  | EXPECT_TRUE(r()->Resolve()) << r()->error(); | 
|  | } | 
|  | } | 
|  | }; | 
|  |  | 
|  | TEST_P(OnePointerOneModuleScope, ReadRead) { | 
|  | // _ = *p1; | 
|  | // _ = global_1; | 
|  | Run({ | 
|  | Assign(Phony(), Deref("p1")), | 
|  | Assign(Phony(), "global_1"), | 
|  | }); | 
|  | } | 
|  |  | 
|  | TEST_P(OnePointerOneModuleScope, ReadWrite) { | 
|  | // _ = *p1; | 
|  | // global_1 = 42; | 
|  | Run( | 
|  | { | 
|  | Assign(Phony(), Deref("p1")), | 
|  | Assign(Expr(Source{{56, 78}}, "global_1"), 42_a), | 
|  | }, | 
|  | R"(12:34 error: invalid aliased pointer argument | 
|  | 56:78 note: aliases with module-scope variable write in 'target')"); | 
|  | } | 
|  |  | 
|  | TEST_P(OnePointerOneModuleScope, WriteRead) { | 
|  | // *p1 = 42; | 
|  | // _ = global_1; | 
|  | Run( | 
|  | { | 
|  | Assign(Deref("p1"), 42_a), | 
|  | Assign(Phony(), Expr(Source{{56, 78}}, "global_1")), | 
|  | }, | 
|  | R"(12:34 error: invalid aliased pointer argument | 
|  | 56:78 note: aliases with module-scope variable read in 'target')"); | 
|  | } | 
|  |  | 
|  | TEST_P(OnePointerOneModuleScope, WriteWrite) { | 
|  | // *p1 = 42; | 
|  | // global_1 = 42; | 
|  | Run( | 
|  | { | 
|  | Assign(Deref("p1"), 42_a), | 
|  | Assign(Expr(Source{{56, 78}}, "global_1"), 42_a), | 
|  | }, | 
|  | R"(12:34 error: invalid aliased pointer argument | 
|  | 56:78 note: aliases with module-scope variable write in 'target')"); | 
|  | } | 
|  |  | 
|  | TEST_P(OnePointerOneModuleScope, ReadWriteThroughChain_GlobalViaArg) { | 
|  | // fn f2(p1 : ptr<private, i32>) { | 
|  | //   *p1 = 42; | 
|  | // } | 
|  | // fn f1(p1 : ptr<private, i32>) { | 
|  | //   _ = *p1; | 
|  | //   f2(&global_1); | 
|  | // } | 
|  | // | 
|  | // f1(p1); | 
|  | Func("f2", | 
|  | utils::Vector<const ast::Parameter*, 4>{ | 
|  | Param("p1", ty.ptr<i32>(builtin::AddressSpace::kPrivate)), | 
|  | }, | 
|  | ty.void_(), | 
|  | utils::Vector{ | 
|  | Assign(Deref("p1"), 42_a), | 
|  | }); | 
|  | Func("f1", | 
|  | utils::Vector<const ast::Parameter*, 4>{ | 
|  | Param("p1", ty.ptr<i32>(builtin::AddressSpace::kPrivate)), | 
|  | }, | 
|  | ty.void_(), | 
|  | utils::Vector{ | 
|  | Assign(Phony(), Deref("p1")), | 
|  | CallStmt(Call("f2", AddressOf(Source{{56, 78}}, "global_1"))), | 
|  | }); | 
|  | Run( | 
|  | { | 
|  | CallStmt(Call("f1", "p1")), | 
|  | }, | 
|  | R"(12:34 error: invalid aliased pointer argument | 
|  | 56:78 note: aliases with module-scope variable write in 'f1')"); | 
|  | } | 
|  |  | 
|  | TEST_P(OnePointerOneModuleScope, ReadWriteThroughChain_Both) { | 
|  | // fn f2(p1 : ptr<private, i32>) { | 
|  | //   _ = *p1; | 
|  | //   global_1 = 42; | 
|  | // } | 
|  | // fn f1(p1 : ptr<private, i32>) { | 
|  | //   f2(p1); | 
|  | // } | 
|  | // | 
|  | // f1(p1); | 
|  | Func("f2", | 
|  | utils::Vector<const ast::Parameter*, 4>{ | 
|  | Param("p1", ty.ptr<i32>(builtin::AddressSpace::kPrivate)), | 
|  | }, | 
|  | ty.void_(), | 
|  | utils::Vector{ | 
|  | Assign(Phony(), Deref("p1")), | 
|  | Assign(Expr(Source{{56, 78}}, "global_1"), 42_a), | 
|  | }); | 
|  | Func("f1", | 
|  | utils::Vector<const ast::Parameter*, 4>{ | 
|  | Param("p1", ty.ptr<i32>(builtin::AddressSpace::kPrivate)), | 
|  | }, | 
|  | ty.void_(), | 
|  | utils::Vector{ | 
|  | CallStmt(Call("f2", "p1")), | 
|  | }); | 
|  | Run( | 
|  | { | 
|  | CallStmt(Call("f1", "p1")), | 
|  | }, | 
|  | R"(12:34 error: invalid aliased pointer argument | 
|  | 56:78 note: aliases with module-scope variable write in 'f2')"); | 
|  | } | 
|  |  | 
|  | TEST_P(OnePointerOneModuleScope, WriteReadThroughChain_GlobalViaArg) { | 
|  | // fn f2(p1 : ptr<private, i32>) { | 
|  | //   _ = *p1; | 
|  | // } | 
|  | // fn f1(p1 : ptr<private, i32>) { | 
|  | //   *p1 = 42; | 
|  | //   f2(&global_1); | 
|  | // } | 
|  | // | 
|  | // f1(p1); | 
|  | Func("f2", | 
|  | utils::Vector<const ast::Parameter*, 4>{ | 
|  | Param("p1", ty.ptr<i32>(builtin::AddressSpace::kPrivate)), | 
|  | }, | 
|  | ty.void_(), | 
|  | utils::Vector{ | 
|  | Assign(Phony(), Deref("p1")), | 
|  | }); | 
|  | Func("f1", | 
|  | utils::Vector<const ast::Parameter*, 4>{ | 
|  | Param("p1", ty.ptr<i32>(builtin::AddressSpace::kPrivate)), | 
|  | }, | 
|  | ty.void_(), | 
|  | utils::Vector{ | 
|  | Assign(Deref("p1"), 42_a), | 
|  | CallStmt(Call("f2", AddressOf(Source{{56, 78}}, "global_1"))), | 
|  | }); | 
|  | Run( | 
|  | { | 
|  | CallStmt(Call("f1", "p1")), | 
|  | }, | 
|  | R"(12:34 error: invalid aliased pointer argument | 
|  | 56:78 note: aliases with module-scope variable read in 'f1')"); | 
|  | } | 
|  |  | 
|  | TEST_P(OnePointerOneModuleScope, WriteReadThroughChain_Both) { | 
|  | // fn f2(p1 : ptr<private, i32>) { | 
|  | //   *p1 = 42; | 
|  | //   _ = global_1; | 
|  | // } | 
|  | // fn f1(p1 : ptr<private, i32>) { | 
|  | //   f2(p1); | 
|  | // } | 
|  | // | 
|  | // f1(p1); | 
|  | Func("f2", | 
|  | utils::Vector{ | 
|  | Param("p1", ty.ptr<i32>(builtin::AddressSpace::kPrivate)), | 
|  | }, | 
|  | ty.void_(), | 
|  | utils::Vector{ | 
|  | Assign(Deref("p1"), 42_a), | 
|  | Assign(Phony(), Expr(Source{{56, 78}}, "global_1")), | 
|  | }); | 
|  | Func("f1", | 
|  | utils::Vector{ | 
|  | Param("p1", ty.ptr<i32>(builtin::AddressSpace::kPrivate)), | 
|  | }, | 
|  | ty.void_(), | 
|  | utils::Vector{ | 
|  | CallStmt(Call("f2", "p1")), | 
|  | }); | 
|  | Run( | 
|  | { | 
|  | CallStmt(Call("f1", "p1")), | 
|  | }, | 
|  | R"(12:34 error: invalid aliased pointer argument | 
|  | 56:78 note: aliases with module-scope variable read in 'f2')"); | 
|  | } | 
|  |  | 
|  | TEST_P(OnePointerOneModuleScope, ReadWriteAcrossDifferentFunctions) { | 
|  | // fn f1(p1 : ptr<private, i32>) { | 
|  | //   _ = *p1; | 
|  | // } | 
|  | // fn f2() { | 
|  | //   global_1 = 42; | 
|  | // } | 
|  | // | 
|  | // f1(p1); | 
|  | // f2(); | 
|  | Func("f1", | 
|  | utils::Vector{ | 
|  | Param("p1", ty.ptr<i32>(builtin::AddressSpace::kPrivate)), | 
|  | }, | 
|  | ty.void_(), | 
|  | utils::Vector{ | 
|  | Assign(Phony(), Deref("p1")), | 
|  | }); | 
|  | Func("f2", utils::Empty, ty.void_(), | 
|  | utils::Vector{ | 
|  | Assign(Expr(Source{{56, 78}}, "global_1"), 42_a), | 
|  | }); | 
|  | Run( | 
|  | { | 
|  | CallStmt(Call("f1", "p1")), | 
|  | CallStmt(Call("f2")), | 
|  | }, | 
|  | R"(12:34 error: invalid aliased pointer argument | 
|  | 56:78 note: aliases with module-scope variable write in 'f2')"); | 
|  | } | 
|  |  | 
|  | INSTANTIATE_TEST_SUITE_P(ResolverAliasAnalysisTest, | 
|  | OnePointerOneModuleScope, | 
|  | ::testing::Values(false, true), | 
|  | [](const ::testing::TestParamInfo<bool>& p) { | 
|  | return p.param ? "Aliased" : "Unaliased"; | 
|  | }); | 
|  |  | 
|  | // Base test harness for tests that use a potentially aliased pointer in a variety of expressions. | 
|  | // | 
|  | // fn target(p1 : ptr<function, i32>, p2 : ptr<function, i32>) { | 
|  | //   *p1 = 42; | 
|  | //   <test statements> | 
|  | // } | 
|  | // fn caller() { | 
|  | //   var v1 : i32; | 
|  | //   var v2 : i32; | 
|  | //   target(&v1, aliased ? &v1 : &v2); | 
|  | // } | 
|  | class Use : public ResolverTestWithParam<bool> { | 
|  | protected: | 
|  | void SetUp() override { | 
|  | Func("caller", utils::Empty, ty.void_(), | 
|  | utils::Vector{ | 
|  | Decl(Var("v1", ty.i32())), | 
|  | Decl(Var("v2", ty.i32())), | 
|  | CallStmt(Call("target", AddressOf(Source{{12, 34}}, "v1"), | 
|  | AddressOf(Source{{56, 78}}, GetParam() ? "v1" : "v2"))), | 
|  | }); | 
|  | } | 
|  |  | 
|  | void Run(const ast::Statement* stmt, const char* err = nullptr) { | 
|  | Func("target", | 
|  | utils::Vector{ | 
|  | Param("p1", ty.ptr<i32>(builtin::AddressSpace::kFunction)), | 
|  | Param("p2", ty.ptr<i32>(builtin::AddressSpace::kFunction)), | 
|  | }, | 
|  | ty.void_(), | 
|  | utils::Vector{ | 
|  | Assign(Deref("p1"), 42_a), | 
|  | stmt, | 
|  | }); | 
|  | if (GetParam() && err) { | 
|  | EXPECT_FALSE(r()->Resolve()); | 
|  | EXPECT_EQ(r()->error(), err); | 
|  | } else { | 
|  | EXPECT_TRUE(r()->Resolve()) << r()->error(); | 
|  | } | 
|  | } | 
|  | }; | 
|  |  | 
|  | TEST_P(Use, NoAccess) { | 
|  | // Expect no errors even when aliasing occurs. | 
|  | Run(Assign(Phony(), 42_a)); | 
|  | } | 
|  |  | 
|  | TEST_P(Use, Write_Increment) { | 
|  | // (*p2)++; | 
|  | Run(Increment(Deref("p2")), R"(56:78 error: invalid aliased pointer argument | 
|  | 12:34 note: aliases with another argument passed here)"); | 
|  | } | 
|  |  | 
|  | TEST_P(Use, Write_Decrement) { | 
|  | // (*p2)--; | 
|  | Run(Decrement(Deref("p2")), R"(56:78 error: invalid aliased pointer argument | 
|  | 12:34 note: aliases with another argument passed here)"); | 
|  | } | 
|  |  | 
|  | TEST_P(Use, Write_CompoundAssignment_LHS) { | 
|  | // *p2 += 42; | 
|  | Run(CompoundAssign(Deref("p2"), 42_a, ast::BinaryOp::kAdd), | 
|  | R"(56:78 error: invalid aliased pointer argument | 
|  | 12:34 note: aliases with another argument passed here)"); | 
|  | } | 
|  |  | 
|  | TEST_P(Use, Read_CompoundAssignment_RHS) { | 
|  | // var<private> global : i32; | 
|  | // global += *p2; | 
|  | GlobalVar("global", builtin::AddressSpace::kPrivate, ty.i32()); | 
|  | Run(CompoundAssign("global", Deref("p2"), ast::BinaryOp::kAdd), | 
|  | R"(56:78 error: invalid aliased pointer argument | 
|  | 12:34 note: aliases with another argument passed here)"); | 
|  | } | 
|  |  | 
|  | TEST_P(Use, Read_BinaryOp_LHS) { | 
|  | // _ = (*p2) + 1; | 
|  | Run(Assign(Phony(), Add(Deref("p2"), 1_a)), R"(56:78 error: invalid aliased pointer argument | 
|  | 12:34 note: aliases with another argument passed here)"); | 
|  | } | 
|  |  | 
|  | TEST_P(Use, Read_BinaryOp_RHS) { | 
|  | // _ = 1 + (*p2); | 
|  | Run(Assign(Phony(), Add(1_a, Deref("p2"))), R"(56:78 error: invalid aliased pointer argument | 
|  | 12:34 note: aliases with another argument passed here)"); | 
|  | } | 
|  |  | 
|  | TEST_P(Use, Read_UnaryMinus) { | 
|  | // _ = -(*p2); | 
|  | Run(Assign(Phony(), Negation(Deref("p2"))), R"(56:78 error: invalid aliased pointer argument | 
|  | 12:34 note: aliases with another argument passed here)"); | 
|  | } | 
|  |  | 
|  | TEST_P(Use, Read_FunctionCallArg) { | 
|  | // abs(*p2); | 
|  | Run(Assign(Phony(), Call("abs", Deref("p2"))), R"(56:78 error: invalid aliased pointer argument | 
|  | 12:34 note: aliases with another argument passed here)"); | 
|  | } | 
|  |  | 
|  | TEST_P(Use, Read_Bitcast) { | 
|  | // _ = bitcast<f32>(*p2); | 
|  | Run(Assign(Phony(), Bitcast<f32>(Deref("p2"))), | 
|  | R"(56:78 error: invalid aliased pointer argument | 
|  | 12:34 note: aliases with another argument passed here)"); | 
|  | } | 
|  |  | 
|  | TEST_P(Use, Read_Convert) { | 
|  | // _ = f32(*p2); | 
|  | Run(Assign(Phony(), Call<f32>(Deref("p2"))), | 
|  | R"(56:78 error: invalid aliased pointer argument | 
|  | 12:34 note: aliases with another argument passed here)"); | 
|  | } | 
|  |  | 
|  | TEST_P(Use, Read_IndexAccessor) { | 
|  | // var<private> data : array<f32, 4>; | 
|  | // _ = data[*p2]; | 
|  | GlobalVar("data", builtin::AddressSpace::kPrivate, ty.array<f32, 4>()); | 
|  | Run(Assign(Phony(), IndexAccessor("data", Deref("p2"))), | 
|  | R"(56:78 error: invalid aliased pointer argument | 
|  | 12:34 note: aliases with another argument passed here)"); | 
|  | } | 
|  |  | 
|  | TEST_P(Use, Read_LetInitializer) { | 
|  | // let x = *p2; | 
|  | Run(Decl(Let("x", Deref("p2"))), R"(56:78 error: invalid aliased pointer argument | 
|  | 12:34 note: aliases with another argument passed here)"); | 
|  | } | 
|  |  | 
|  | TEST_P(Use, Read_VarInitializer) { | 
|  | // var x = *p2; | 
|  | Run(Decl(Var("x", builtin::AddressSpace::kFunction, Deref("p2"))), | 
|  | R"(56:78 error: invalid aliased pointer argument | 
|  | 12:34 note: aliases with another argument passed here)"); | 
|  | } | 
|  |  | 
|  | TEST_P(Use, Read_ReturnValue) { | 
|  | // fn foo(p : ptr<function, i32>) -> i32 { return *p; } | 
|  | // foo(p2); | 
|  | Func("foo", | 
|  | utils::Vector{ | 
|  | Param("p", ty.ptr<i32>(builtin::AddressSpace::kFunction)), | 
|  | }, | 
|  | ty.i32(), | 
|  | utils::Vector{ | 
|  | Return(Deref("p")), | 
|  | }); | 
|  | Run(Assign(Phony(), Call("foo", "p2")), R"(56:78 error: invalid aliased pointer argument | 
|  | 12:34 note: aliases with another argument passed here)"); | 
|  | } | 
|  |  | 
|  | TEST_P(Use, Read_Switch) { | 
|  | // Switch (*p2) { default {} } | 
|  | Run(Switch(Deref("p2"), utils::Vector{DefaultCase(Block())}), | 
|  | R"(56:78 error: invalid aliased pointer argument | 
|  | 12:34 note: aliases with another argument passed here)"); | 
|  | } | 
|  |  | 
|  | TEST_P(Use, NoAccess_AddressOf_Deref) { | 
|  | // Should not invoke the load-rule, and therefore expect no errors even when aliasing occurs. | 
|  | // let newp = &(*p2); | 
|  | Run(Decl(Let("newp", AddressOf(Deref("p2"))))); | 
|  | } | 
|  |  | 
|  | INSTANTIATE_TEST_SUITE_P(ResolverAliasAnalysisTest, | 
|  | Use, | 
|  | ::testing::Values(false, true), | 
|  | [](const ::testing::TestParamInfo<bool>& p) { | 
|  | return p.param ? "Aliased" : "Unaliased"; | 
|  | }); | 
|  |  | 
|  | // Base test harness for tests that use a potentially aliased pointer in a variety of expressions. | 
|  | // As above, but using the bool type to test expressions that invoke that load-rule for booleans. | 
|  | // | 
|  | // fn target(p1 : ptr<function, bool>, p2 : ptr<function, bool>) { | 
|  | //   *p1 = true; | 
|  | //   <test statements> | 
|  | // } | 
|  | // fn caller() { | 
|  | //   var v1 : bool; | 
|  | //   var v2 : bool; | 
|  | //   target(&v1, aliased ? &v1 : &v2); | 
|  | // } | 
|  | class UseBool : public ResolverTestWithParam<bool> { | 
|  | protected: | 
|  | void SetUp() override { | 
|  | Func("caller", utils::Empty, ty.void_(), | 
|  | utils::Vector{ | 
|  | Decl(Var("v1", ty.bool_())), | 
|  | Decl(Var("v2", ty.bool_())), | 
|  | CallStmt(Call("target", AddressOf(Source{{12, 34}}, "v1"), | 
|  | AddressOf(Source{{56, 78}}, GetParam() ? "v1" : "v2"))), | 
|  | }); | 
|  | } | 
|  |  | 
|  | void Run(const ast::Statement* stmt, const char* err = nullptr) { | 
|  | Func("target", | 
|  | utils::Vector{ | 
|  | Param("p1", ty.ptr<bool>(builtin::AddressSpace::kFunction)), | 
|  | Param("p2", ty.ptr<bool>(builtin::AddressSpace::kFunction)), | 
|  | }, | 
|  | ty.void_(), | 
|  | utils::Vector{ | 
|  | Assign(Deref("p1"), true), | 
|  | stmt, | 
|  | }); | 
|  | if (GetParam() && err) { | 
|  | EXPECT_FALSE(r()->Resolve()); | 
|  | EXPECT_EQ(r()->error(), err); | 
|  | } else { | 
|  | EXPECT_TRUE(r()->Resolve()) << r()->error(); | 
|  | } | 
|  | } | 
|  | }; | 
|  |  | 
|  | TEST_P(UseBool, Read_IfCond) { | 
|  | // if (*p2) {} | 
|  | Run(If(Deref("p2"), Block()), R"(56:78 error: invalid aliased pointer argument | 
|  | 12:34 note: aliases with another argument passed here)"); | 
|  | } | 
|  |  | 
|  | TEST_P(UseBool, Read_WhileCond) { | 
|  | // while (*p2) {} | 
|  | Run(While(Deref("p2"), Block()), R"(56:78 error: invalid aliased pointer argument | 
|  | 12:34 note: aliases with another argument passed here)"); | 
|  | } | 
|  |  | 
|  | TEST_P(UseBool, Read_ForCond) { | 
|  | // for (; *p2; ) {} | 
|  | Run(For(nullptr, Deref("p2"), nullptr, Block()), | 
|  | R"(56:78 error: invalid aliased pointer argument | 
|  | 12:34 note: aliases with another argument passed here)"); | 
|  | } | 
|  |  | 
|  | TEST_P(UseBool, Read_BreakIf) { | 
|  | // loop { continuing { break if (*p2); } } | 
|  | Run(Loop(Block(), Block(BreakIf(Deref("p2")))), | 
|  | R"(56:78 error: invalid aliased pointer argument | 
|  | 12:34 note: aliases with another argument passed here)"); | 
|  | } | 
|  |  | 
|  | INSTANTIATE_TEST_SUITE_P(ResolverAliasAnalysisTest, | 
|  | UseBool, | 
|  | ::testing::Values(false, true), | 
|  | [](const ::testing::TestParamInfo<bool>& p) { | 
|  | return p.param ? "Aliased" : "Unaliased"; | 
|  | }); | 
|  |  | 
|  | TEST_F(ResolverAliasAnalysisTest, NoAccess_MemberAccessor) { | 
|  | // Should not invoke the load-rule, and therefore expect no errors even when aliasing occurs. | 
|  | // | 
|  | // struct S { a : i32 } | 
|  | // fn f2(p1 : ptr<function, S>, p2 : ptr<function, S>) { | 
|  | //   let newp = &((*p2).a); | 
|  | //   (*p1).a = 42; | 
|  | // } | 
|  | // fn f1() { | 
|  | //   var v : S; | 
|  | //   f2(&v, &v); | 
|  | // } | 
|  | Structure("S", utils::Vector{Member("a", ty.i32())}); | 
|  | Func("f2", | 
|  | utils::Vector{ | 
|  | Param("p1", ty.ptr<function>(ty("S"))), | 
|  | Param("p2", ty.ptr<function>(ty("S"))), | 
|  | }, | 
|  | ty.void_(), | 
|  | utils::Vector{ | 
|  | Decl(Let("newp", AddressOf(MemberAccessor(Deref("p2"), "a")))), | 
|  | Assign(MemberAccessor(Deref("p1"), "a"), 42_a), | 
|  | }); | 
|  | Func("f1", utils::Empty, ty.void_(), | 
|  | utils::Vector{ | 
|  | Decl(Var("v", ty("S"))), | 
|  | CallStmt(Call("f2", AddressOf("v"), AddressOf("v"))), | 
|  | }); | 
|  | EXPECT_TRUE(r()->Resolve()) << r()->error(); | 
|  | } | 
|  |  | 
|  | TEST_F(ResolverAliasAnalysisTest, Read_MemberAccessor) { | 
|  | // struct S { a : i32 } | 
|  | // fn f2(p1 : ptr<function, S>, p2 : ptr<function, S>) { | 
|  | //   _ = (*p2).a; | 
|  | //   *p1 = S(); | 
|  | // } | 
|  | // fn f1() { | 
|  | //   var v : S; | 
|  | //   f2(&v, &v); | 
|  | // } | 
|  | Structure("S", utils::Vector{Member("a", ty.i32())}); | 
|  | Func("f2", | 
|  | utils::Vector{ | 
|  | Param("p1", ty.ptr<function>(ty("S"))), | 
|  | Param("p2", ty.ptr<function>(ty("S"))), | 
|  | }, | 
|  | ty.void_(), | 
|  | utils::Vector{ | 
|  | Assign(Phony(), MemberAccessor(Deref("p2"), "a")), | 
|  | Assign(Deref("p1"), Call("S")), | 
|  | }); | 
|  | Func("f1", utils::Empty, ty.void_(), | 
|  | utils::Vector{ | 
|  | Decl(Var("v", ty("S"))), | 
|  | CallStmt( | 
|  | Call("f2", AddressOf(Source{{12, 34}}, "v"), AddressOf(Source{{56, 76}}, "v"))), | 
|  | }); | 
|  | EXPECT_FALSE(r()->Resolve()) << r()->error(); | 
|  | EXPECT_EQ(r()->error(), R"(56:76 error: invalid aliased pointer argument | 
|  | 12:34 note: aliases with another argument passed here)"); | 
|  | } | 
|  |  | 
|  | TEST_F(ResolverAliasAnalysisTest, Write_MemberAccessor) { | 
|  | // struct S { a : i32 } | 
|  | // fn f2(p1 : ptr<function, S>, p2 : ptr<function, S>) { | 
|  | //   _ = *p2; | 
|  | //   (*p1).a = 42; | 
|  | // } | 
|  | // fn f1() { | 
|  | //   var v : S; | 
|  | //   f2(&v, &v); | 
|  | // } | 
|  | Structure("S", utils::Vector{Member("a", ty.i32())}); | 
|  | Func("f2", | 
|  | utils::Vector{ | 
|  | Param("p1", ty.ptr<function>(ty("S"))), | 
|  | Param("p2", ty.ptr<function>(ty("S"))), | 
|  | }, | 
|  | ty.void_(), | 
|  | utils::Vector{ | 
|  | Assign(Phony(), Deref("p2")), | 
|  | Assign(MemberAccessor(Deref("p1"), "a"), 42_a), | 
|  | }); | 
|  | Func("f1", utils::Empty, ty.void_(), | 
|  | utils::Vector{ | 
|  | Decl(Var("v", ty("S"))), | 
|  | CallStmt( | 
|  | Call("f2", AddressOf(Source{{12, 34}}, "v"), AddressOf(Source{{56, 76}}, "v"))), | 
|  | }); | 
|  | EXPECT_FALSE(r()->Resolve()) << r()->error(); | 
|  | EXPECT_EQ(r()->error(), R"(56:76 error: invalid aliased pointer argument | 
|  | 12:34 note: aliases with another argument passed here)"); | 
|  | } | 
|  |  | 
|  | TEST_F(ResolverAliasAnalysisTest, Read_MultiComponentSwizzle) { | 
|  | // fn f2(p1 : ptr<function, vec4<f32>, p2 : ptr<function, vec4<f32>) { | 
|  | //   _ = (*p2).zy; | 
|  | //   *p1 = vec4<f32>(); | 
|  | // } | 
|  | // fn f1() { | 
|  | //   var v : vec4<f32>; | 
|  | //   f2(&v, &v); | 
|  | // } | 
|  | Structure("S", utils::Vector{Member("a", ty.i32())}); | 
|  | Func("f2", | 
|  | utils::Vector{ | 
|  | Param("p1", ty.ptr<function, vec4<f32>>()), | 
|  | Param("p2", ty.ptr<function, vec4<f32>>()), | 
|  | }, | 
|  | ty.void_(), | 
|  | utils::Vector{ | 
|  | Assign(Phony(), MemberAccessor(Deref("p2"), "zy")), | 
|  | Assign(Deref("p1"), Call<vec4<f32>>()), | 
|  | }); | 
|  | Func("f1", utils::Empty, ty.void_(), | 
|  | utils::Vector{ | 
|  | Decl(Var("v", ty.vec4<f32>())), | 
|  | CallStmt( | 
|  | Call("f2", AddressOf(Source{{12, 34}}, "v"), AddressOf(Source{{56, 76}}, "v"))), | 
|  | }); | 
|  | EXPECT_FALSE(r()->Resolve()) << r()->error(); | 
|  | EXPECT_EQ(r()->error(), R"(56:76 error: invalid aliased pointer argument | 
|  | 12:34 note: aliases with another argument passed here)"); | 
|  | } | 
|  |  | 
|  | TEST_F(ResolverAliasAnalysisTest, SinglePointerReadWrite) { | 
|  | // Test that we can both read and write from a single pointer parameter. | 
|  | // | 
|  | // fn f1(p : ptr<function, i32>) { | 
|  | //   _ = *p; | 
|  | //   *p = 42; | 
|  | // } | 
|  | // fn f2() { | 
|  | //   var v : i32; | 
|  | //   f1(&v); | 
|  | // } | 
|  | Func("f1", | 
|  | utils::Vector{ | 
|  | Param("p", ty.ptr<i32>(builtin::AddressSpace::kFunction)), | 
|  | }, | 
|  | ty.void_(), | 
|  | utils::Vector{ | 
|  | Decl(Var("v", ty.i32())), | 
|  | Assign(Phony(), Deref("p")), | 
|  | Assign(Deref("p"), 42_a), | 
|  | }); | 
|  | Func("f2", utils::Empty, ty.void_(), | 
|  | utils::Vector{ | 
|  | Decl(Var("v", ty.i32())), | 
|  | CallStmt(Call("f1", AddressOf("v"))), | 
|  | }); | 
|  | EXPECT_TRUE(r()->Resolve()) << r()->error(); | 
|  | } | 
|  |  | 
|  | TEST_F(ResolverAliasAnalysisTest, AliasingInsideFunction) { | 
|  | // Test that we can use two aliased pointers inside the same function they are created in. | 
|  | // | 
|  | // fn f1() { | 
|  | //   var v : i32; | 
|  | //   let p1 = &v; | 
|  | //   let p2 = &v; | 
|  | //   *p1 = 42; | 
|  | //   *p2 = 42; | 
|  | // } | 
|  | Func("f1", utils::Empty, ty.void_(), | 
|  | utils::Vector{ | 
|  | Decl(Var("v", ty.i32())), | 
|  | Decl(Let("p1", AddressOf("v"))), | 
|  | Decl(Let("p2", AddressOf("v"))), | 
|  | Assign(Deref("p1"), 42_a), | 
|  | Assign(Deref("p2"), 42_a), | 
|  | }); | 
|  | EXPECT_TRUE(r()->Resolve()) << r()->error(); | 
|  | } | 
|  |  | 
|  | TEST_F(ResolverAliasAnalysisTest, NonOverlappingCalls) { | 
|  | // Test that we pass the same pointer to multiple non-overlapping function calls. | 
|  | // | 
|  | // fn f2(p : ptr<function, i32>) { | 
|  | //   *p = 42; | 
|  | // } | 
|  | // fn f3(p : ptr<function, i32>) { | 
|  | //   *p = 42; | 
|  | // } | 
|  | // fn f1() { | 
|  | //   var v : i32; | 
|  | //   f2(&v); | 
|  | //   f3(&v); | 
|  | // } | 
|  | Func("f2", | 
|  | utils::Vector{ | 
|  | Param("p", ty.ptr<i32>(builtin::AddressSpace::kFunction)), | 
|  | }, | 
|  | ty.void_(), | 
|  | utils::Vector{ | 
|  | Assign(Deref("p"), 42_a), | 
|  | }); | 
|  | Func("f3", | 
|  | utils::Vector{ | 
|  | Param("p", ty.ptr<i32>(builtin::AddressSpace::kFunction)), | 
|  | }, | 
|  | ty.void_(), | 
|  | utils::Vector{ | 
|  | Assign(Deref("p"), 42_a), | 
|  | }); | 
|  | Func("f1", utils::Empty, ty.void_(), | 
|  | utils::Vector{ | 
|  | Decl(Var("v", ty.i32())), | 
|  | CallStmt(Call("f2", AddressOf("v"))), | 
|  | CallStmt(Call("f3", AddressOf("v"))), | 
|  | }); | 
|  | EXPECT_TRUE(r()->Resolve()) << r()->error(); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  | }  // namespace tint::resolver |