mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-06-13 18:13:47 +00:00
Track reads and writes to pointer parameters for each function in the Resolver, as well as accesses to module-scope variables. At function call sites, check the root identifiers of each pointer argument to determine if problematic aliasing occurs. The MSL backend passes pointers to sub-objects to functions when handling workgroup storage variables, which triggers the alias analysis. Add a validation override for this scenario. Bug: tint:1675 Change-Id: I81a40d1309df65521cc5ad39764d6a09a260f51e Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/110167 Kokoro: Kokoro <noreply+kokoro@google.com> Reviewed-by: Ben Clayton <bclayton@google.com> Commit-Queue: James Price <jrprice@google.com>
898 lines
28 KiB
C++
898 lines
28 KiB
C++
// 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<function, i32>, p2 : ptr<function, i32>) {
|
|
// <test statements>
|
|
// }
|
|
// 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<TwoPointerConfig> {
|
|
protected:
|
|
void SetUp() override {
|
|
utils::Vector<const ast::Statement*, 4> 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<const ast::Statement*, 4>&& body, const char* err = nullptr) {
|
|
auto addrspace = GetParam().address_space;
|
|
Func("target",
|
|
utils::Vector{
|
|
Param("p1", ty.pointer<i32>(addrspace)),
|
|
Param("p2", ty.pointer<i32>(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<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.pointer<i32>(GetParam().address_space)),
|
|
Param("p2", ty.pointer<i32>(GetParam().address_space)),
|
|
},
|
|
ty.void_(),
|
|
utils::Vector{
|
|
Assign(Phony(), Deref("p1")),
|
|
Assign(Deref("p2"), 42_a),
|
|
});
|
|
Func("f1",
|
|
utils::Vector{
|
|
Param("p1", ty.pointer<i32>(GetParam().address_space)),
|
|
Param("p2", ty.pointer<i32>(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<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.pointer<i32>(GetParam().address_space)),
|
|
},
|
|
ty.void_(),
|
|
utils::Vector{
|
|
Assign(Phony(), Deref("p1")),
|
|
});
|
|
Func("f2",
|
|
utils::Vector<const ast::Parameter*, 4>{
|
|
Param("p2", ty.pointer<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 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<TwoPointers::ParamType>& 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<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", 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<const ast::Statement*, 4>&& body, const char* err = nullptr) {
|
|
Func("target",
|
|
utils::Vector<const ast::Parameter*, 4>{
|
|
Param("p1", ty.pointer<i32>(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<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.pointer<i32>(ast::AddressSpace::kPrivate)),
|
|
},
|
|
ty.void_(),
|
|
utils::Vector{
|
|
Assign(Deref("p1"), 42_a),
|
|
});
|
|
Func("f1",
|
|
utils::Vector<const ast::Parameter*, 4>{
|
|
Param("p1", ty.pointer<i32>(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<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.pointer<i32>(ast::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.pointer<i32>(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<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.pointer<i32>(ast::AddressSpace::kPrivate)),
|
|
},
|
|
ty.void_(),
|
|
utils::Vector{
|
|
Assign(Phony(), Deref("p1")),
|
|
});
|
|
Func("f1",
|
|
utils::Vector<const ast::Parameter*, 4>{
|
|
Param("p1", ty.pointer<i32>(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<private, i32>) {
|
|
// *p1 = 42;
|
|
// _ = global_1;
|
|
// }
|
|
// fn f1(p1 : ptr<private, i32>) {
|
|
// f2(p1);
|
|
// }
|
|
//
|
|
// f1(p1);
|
|
Func("f2",
|
|
utils::Vector{
|
|
Param("p1", ty.pointer<i32>(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<i32>(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<private, i32>) {
|
|
// _ = *p1;
|
|
// }
|
|
// fn f2() {
|
|
// global_1 = 42;
|
|
// }
|
|
//
|
|
// f1(p1);
|
|
// f2();
|
|
Func("f1",
|
|
utils::Vector{
|
|
Param("p1", ty.pointer<i32>(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<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.pointer<i32>(ast::AddressSpace::kFunction)),
|
|
Param("p2", ty.pointer<i32>(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<private> 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<f32>(*p2);
|
|
Run(Assign(Phony(), Bitcast<f32>(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<f32>(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<private> data : array<f32, 4>;
|
|
// _ = data[*p2];
|
|
GlobalVar("data", ast::AddressSpace::kPrivate, ty.array<f32, 4>());
|
|
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<function, i32>) -> i32 { return *p; }
|
|
// foo(p2);
|
|
Func("foo",
|
|
utils::Vector{
|
|
Param("p", ty.pointer<i32>(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<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.pointer<bool>(ast::AddressSpace::kFunction)),
|
|
Param("p2", ty.pointer<bool>(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<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.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<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.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<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.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<function, i32>) {
|
|
// _ = *p;
|
|
// *p = 42;
|
|
// }
|
|
// fn f2() {
|
|
// var v : i32;
|
|
// f1(&v);
|
|
// }
|
|
Func("f1",
|
|
utils::Vector{
|
|
Param("p", ty.pointer<i32>(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<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.pointer<i32>(ast::AddressSpace::kFunction)),
|
|
},
|
|
ty.void_(),
|
|
utils::Vector{
|
|
Assign(Deref("p"), 42_a),
|
|
});
|
|
Func("f3",
|
|
utils::Vector{
|
|
Param("p", ty.pointer<i32>(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
|