// 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/resolver/resolver.h" #include "src/tint/resolver/resolver_test_helper.h" #include "gmock/gmock.h" using namespace tint::number_suffixes; // NOLINT namespace tint::resolver { namespace { struct ResolverAliasAnalysisTest : public resolver::TestHelper, public testing::Test {}; // Base test harness for tests that pass two pointers to a function. // // fn target(p1 : ptr, p2 : ptr) { // // } // fn caller() { // var v1 : i32; // var v2 : i32; // target(&v1, aliased ? &v1 : &v2); // } struct TwoPointerConfig { ast::AddressSpace address_space; // The address space for the pointers. bool aliased; // Whether the pointers alias or not. }; class TwoPointers : public ResolverTestWithParam { protected: void SetUp() override { utils::Vector body; if (GetParam().address_space == ast::AddressSpace::kFunction) { body.Push(Decl(Var("v1", ty.i32()))); body.Push(Decl(Var("v2", ty.i32()))); } else { GlobalVar("v1", ast::AddressSpace::kPrivate, ty.i32()); GlobalVar("v2", ast::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&& body, const char* err = nullptr) { auto addrspace = GetParam().address_space; Func("target", utils::Vector{ Param("p1", ty.pointer(addrspace)), Param("p2", ty.pointer(addrspace)), }, ty.void_(), std::move(body)); if (GetParam().aliased && err) { EXPECT_TRUE(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 warning: 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 warning: 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 warning: invalid aliased pointer argument 12:34 note: aliases with another argument passed here)"); } TEST_P(TwoPointers, ReadWriteThroughChain) { // fn f2(p1 : ptr, p2 : ptr) { // _ = *p1; // *p2 = 42; // } // fn f1(p1 : ptr, p2 : ptr) { // f2(p1, p2); // } // // f1(p1, p2); Func("f2", utils::Vector{ Param("p1", ty.pointer(GetParam().address_space)), Param("p2", ty.pointer(GetParam().address_space)), }, ty.void_(), utils::Vector{ Assign(Phony(), Deref("p1")), Assign(Deref("p2"), 42_a), }); Func("f1", utils::Vector{ Param("p1", ty.pointer(GetParam().address_space)), Param("p2", ty.pointer(GetParam().address_space)), }, ty.void_(), utils::Vector{ CallStmt(Call("f2", "p1", "p2")), }); Run( { CallStmt(Call("f1", "p1", "p2")), }, R"(56:78 warning: invalid aliased pointer argument 12:34 note: aliases with another argument passed here)"); } TEST_P(TwoPointers, ReadWriteAcrossDifferentFunctions) { // fn f1(p1 : ptr) { // _ = *p1; // } // fn f2(p2 : ptr) { // *p2 = 42; // } // // f1(p1); // f2(p2); Func("f1", utils::Vector{ Param("p1", ty.pointer(GetParam().address_space)), }, ty.void_(), utils::Vector{ Assign(Phony(), Deref("p1")), }); Func("f2", utils::Vector{ Param("p2", ty.pointer(GetParam().address_space)), }, ty.void_(), utils::Vector{ Assign(Deref("p2"), 42_a), }); Run( { CallStmt(Call("f1", "p1")), CallStmt(Call("f2", "p2")), }, R"(56:78 warning: invalid aliased pointer argument 12:34 note: aliases with another argument passed here)"); } INSTANTIATE_TEST_SUITE_P(ResolverAliasAnalysisTest, TwoPointers, ::testing::Values(TwoPointerConfig{ast::AddressSpace::kFunction, false}, TwoPointerConfig{ast::AddressSpace::kFunction, true}, TwoPointerConfig{ast::AddressSpace::kPrivate, false}, TwoPointerConfig{ast::AddressSpace::kPrivate, true}), [](const ::testing::TestParamInfo& p) { std::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 global_1 : i32; // var global_2 : i32; // fn target(p1 : ptr) { // // } // fn caller() { // target(aliased ? &global_1 : &global_2); // } class OnePointerOneModuleScope : public ResolverTestWithParam { protected: void SetUp() override { GlobalVar("global_1", ast::AddressSpace::kPrivate, ty.i32()); GlobalVar("global_2", ast::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&& body, const char* err = nullptr) { Func("target", utils::Vector{ Param("p1", ty.pointer(ast::AddressSpace::kPrivate)), }, ty.void_(), std::move(body)); if (GetParam() && err) { EXPECT_TRUE(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 warning: 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 warning: 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 warning: invalid aliased pointer argument 56:78 note: aliases with module-scope variable write in 'target')"); } TEST_P(OnePointerOneModuleScope, ReadWriteThroughChain_GlobalViaArg) { // fn f2(p1 : ptr) { // *p1 = 42; // } // fn f1(p1 : ptr) { // _ = *p1; // f2(&global_1); // } // // f1(p1); Func("f2", utils::Vector{ Param("p1", ty.pointer(ast::AddressSpace::kPrivate)), }, ty.void_(), utils::Vector{ Assign(Deref("p1"), 42_a), }); Func("f1", utils::Vector{ Param("p1", ty.pointer(ast::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 warning: invalid aliased pointer argument 56:78 note: aliases with module-scope variable write in 'f1')"); } TEST_P(OnePointerOneModuleScope, ReadWriteThroughChain_Both) { // fn f2(p1 : ptr) { // _ = *p1; // global_1 = 42; // } // fn f1(p1 : ptr) { // f2(p1); // } // // f1(p1); Func("f2", utils::Vector{ Param("p1", ty.pointer(ast::AddressSpace::kPrivate)), }, ty.void_(), utils::Vector{ Assign(Phony(), Deref("p1")), Assign(Expr(Source{{56, 78}}, "global_1"), 42_a), }); Func("f1", utils::Vector{ Param("p1", ty.pointer(ast::AddressSpace::kPrivate)), }, ty.void_(), utils::Vector{ CallStmt(Call("f2", "p1")), }); Run( { CallStmt(Call("f1", "p1")), }, R"(12:34 warning: invalid aliased pointer argument 56:78 note: aliases with module-scope variable write in 'f2')"); } TEST_P(OnePointerOneModuleScope, WriteReadThroughChain_GlobalViaArg) { // fn f2(p1 : ptr) { // _ = *p1; // } // fn f1(p1 : ptr) { // *p1 = 42; // f2(&global_1); // } // // f1(p1); Func("f2", utils::Vector{ Param("p1", ty.pointer(ast::AddressSpace::kPrivate)), }, ty.void_(), utils::Vector{ Assign(Phony(), Deref("p1")), }); Func("f1", utils::Vector{ Param("p1", ty.pointer(ast::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 warning: invalid aliased pointer argument 56:78 note: aliases with module-scope variable read in 'f1')"); } TEST_P(OnePointerOneModuleScope, WriteReadThroughChain_Both) { // fn f2(p1 : ptr) { // *p1 = 42; // _ = global_1; // } // fn f1(p1 : ptr) { // f2(p1); // } // // f1(p1); Func("f2", utils::Vector{ Param("p1", ty.pointer(ast::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.pointer(ast::AddressSpace::kPrivate)), }, ty.void_(), utils::Vector{ CallStmt(Call("f2", "p1")), }); Run( { CallStmt(Call("f1", "p1")), }, R"(12:34 warning: invalid aliased pointer argument 56:78 note: aliases with module-scope variable read in 'f2')"); } TEST_P(OnePointerOneModuleScope, ReadWriteAcrossDifferentFunctions) { // fn f1(p1 : ptr) { // _ = *p1; // } // fn f2() { // global_1 = 42; // } // // f1(p1); // f2(); Func("f1", utils::Vector{ Param("p1", ty.pointer(ast::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 warning: 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& 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, p2 : ptr) { // *p1 = 42; // // } // fn caller() { // var v1 : i32; // var v2 : i32; // target(&v1, aliased ? &v1 : &v2); // } class Use : public ResolverTestWithParam { 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.pointer(ast::AddressSpace::kFunction)), Param("p2", ty.pointer(ast::AddressSpace::kFunction)), }, ty.void_(), utils::Vector{ Assign(Deref("p1"), 42_a), stmt, }); if (GetParam() && err) { EXPECT_TRUE(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 warning: 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 warning: 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 warning: invalid aliased pointer argument 12:34 note: aliases with another argument passed here)"); } TEST_P(Use, Read_CompoundAssignment_RHS) { // var global : i32; // global += *p2; GlobalVar("global", ast::AddressSpace::kPrivate, ty.i32()); Run(CompoundAssign("global", Deref("p2"), ast::BinaryOp::kAdd), R"(56:78 warning: 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 warning: 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 warning: 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 warning: invalid aliased pointer argument 12:34 note: aliases with another argument passed here)"); } TEST_P(Use, Read_FunctionCallArg) { // abs(*p2); Run(CallStmt(Call("abs", Deref("p2"))), R"(56:78 warning: invalid aliased pointer argument 12:34 note: aliases with another argument passed here)"); } TEST_P(Use, Read_Bitcast) { // _ = bitcast(*p2); Run(Assign(Phony(), Bitcast(Deref("p2"))), R"(56:78 warning: invalid aliased pointer argument 12:34 note: aliases with another argument passed here)"); } TEST_P(Use, Read_Convert) { // _ = f32(*p2); Run(Assign(Phony(), Construct(Deref("p2"))), R"(56:78 warning: invalid aliased pointer argument 12:34 note: aliases with another argument passed here)"); } TEST_P(Use, Read_IndexAccessor) { // var data : array; // _ = data[*p2]; GlobalVar("data", ast::AddressSpace::kPrivate, ty.array()); Run(Assign(Phony(), IndexAccessor("data", Deref("p2"))), R"(56:78 warning: 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 warning: 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", ast::AddressSpace::kFunction, Deref("p2"))), R"(56:78 warning: invalid aliased pointer argument 12:34 note: aliases with another argument passed here)"); } TEST_P(Use, Read_ReturnValue) { // fn foo(p : ptr) -> i32 { return *p; } // foo(p2); Func("foo", utils::Vector{ Param("p", ty.pointer(ast::AddressSpace::kFunction)), }, ty.i32(), utils::Vector{ Return(Deref("p")), }); Run(Assign(Phony(), Call("foo", "p2")), R"(56:78 warning: 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 warning: 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& 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, p2 : ptr) { // *p1 = true; // // } // fn caller() { // var v1 : bool; // var v2 : bool; // target(&v1, aliased ? &v1 : &v2); // } class UseBool : public ResolverTestWithParam { 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.pointer(ast::AddressSpace::kFunction)), Param("p2", ty.pointer(ast::AddressSpace::kFunction)), }, ty.void_(), utils::Vector{ Assign(Deref("p1"), true), stmt, }); if (GetParam() && err) { EXPECT_TRUE(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 warning: 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 warning: 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 warning: 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 warning: 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& 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, p2 : ptr) { // 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.pointer(ty.type_name("S"), ast::AddressSpace::kFunction)), Param("p2", ty.pointer(ty.type_name("S"), ast::AddressSpace::kFunction)), }, 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.type_name("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, p2 : ptr) { // _ = (*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.pointer(ty.type_name("S"), ast::AddressSpace::kFunction)), Param("p2", ty.pointer(ty.type_name("S"), ast::AddressSpace::kFunction)), }, ty.void_(), utils::Vector{ Assign(Phony(), MemberAccessor(Deref("p2"), "a")), Assign(Deref("p1"), Construct(ty.type_name("S"))), }); Func("f1", utils::Empty, ty.void_(), utils::Vector{ Decl(Var("v", ty.type_name("S"))), CallStmt( Call("f2", AddressOf(Source{{12, 34}}, "v"), AddressOf(Source{{56, 76}}, "v"))), }); EXPECT_TRUE(r()->Resolve()) << r()->error(); EXPECT_EQ(r()->error(), R"(56:76 warning: 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, p2 : ptr) { // _ = *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.pointer(ty.type_name("S"), ast::AddressSpace::kFunction)), Param("p2", ty.pointer(ty.type_name("S"), ast::AddressSpace::kFunction)), }, 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.type_name("S"))), CallStmt( Call("f2", AddressOf(Source{{12, 34}}, "v"), AddressOf(Source{{56, 76}}, "v"))), }); EXPECT_TRUE(r()->Resolve()) << r()->error(); EXPECT_EQ(r()->error(), R"(56:76 warning: 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) { // _ = *p; // *p = 42; // } // fn f2() { // var v : i32; // f1(&v); // } Func("f1", utils::Vector{ Param("p", ty.pointer(ast::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) { // *p = 42; // } // fn f3(p : ptr) { // *p = 42; // } // fn f1() { // var v : i32; // f2(&v); // f3(&v); // } Func("f2", utils::Vector{ Param("p", ty.pointer(ast::AddressSpace::kFunction)), }, ty.void_(), utils::Vector{ Assign(Deref("p"), 42_a), }); Func("f3", utils::Vector{ Param("p", ty.pointer(ast::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