// Copyright 2020 The gVisor 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.

// Package test is a test package.
package test

import (
	"math/rand"
	"sync"
)

type oneGuarded struct {
	mu sync.Mutex
	// +checklocks:mu
	guardedField int

	unguardedField int
}

func testAccessOne() {
	var tc oneGuarded
	// Valid access
	tc.mu.Lock()
	tc.guardedField = 1
	tc.unguardedField = 1
	tc.mu.Unlock()

	// Valid access as unguarded field is not protected by mu.
	tc.unguardedField = 2

	// Invalid access
	tc.guardedField = 2 // +checklocksfail

	// Invalid read of a guarded field.
	x := tc.guardedField // +checklocksfail
	_ = x
}

func testFunctionCallsNoParameters() {
	// Couple of regular function calls with no parameters.
	funcCallWithValidAccess()
	funcCallWithInvalidAccess()
}

func funcCallWithValidAccess() {
	var tc2 oneGuarded
	// Valid tc2 access
	tc2.mu.Lock()
	tc2.guardedField = 1
	tc2.mu.Unlock()
}

func funcCallWithInvalidAccess() {
	var tc oneGuarded
	var tc2 oneGuarded
	// Invalid access, wrong mutex is held.
	tc.mu.Lock()
	tc2.guardedField = 2 // +checklocksfail
	tc.mu.Unlock()
}

func testParameterPassing() {
	var tc oneGuarded

	// Valid call where a guardedField is passed to a function as a parameter.
	tc.mu.Lock()
	nestedWithGuardByAddr(&tc.guardedField, &tc.unguardedField)
	tc.mu.Unlock()

	// Invalid call where a guardedField is passed to a function as a parameter
	// without holding locks.
	nestedWithGuardByAddr(&tc.guardedField, &tc.unguardedField) // +checklocksfail

	// Valid call where a guardedField is passed to a function as a parameter.
	tc.mu.Lock()
	nestedWithGuardByValue(tc.guardedField, tc.unguardedField)
	tc.mu.Unlock()

	// Invalid call where a guardedField is passed to a function as a parameter
	// without holding locks.
	nestedWithGuardByValue(tc.guardedField, tc.unguardedField) // +checklocksfail
}

func nestedWithGuardByAddr(guardedField, unguardedField *int) {
	*guardedField = 4
	*unguardedField = 5
}

func nestedWithGuardByValue(guardedField, unguardedField int) {
	// read the fields to keep SA4009 static analyzer happy.
	_ = guardedField
	_ = unguardedField
	guardedField = 4
	unguardedField = 5
}

type twoGuarded struct {
	mu sync.Mutex
	// +checklocks:mu
	guardedField1 int
	// +checklocks:mu
	guardedField2 int
}

type twoLocks struct {
	mu       sync.Mutex
	secondMu sync.Mutex

	// +checklocks:mu
	guardedField1 int
	// +checklocks:secondMu
	guardedField2 int
}

type twoLocksDoubleGuard struct {
	mu       sync.Mutex
	secondMu sync.Mutex

	// +checklocks:mu
	// +checklocks:secondMu
	doubleGuardedField int
}

func testTwoLocksDoubleGuard() {
	var tc twoLocksDoubleGuard

	// Double guarded field
	tc.mu.Lock()
	tc.secondMu.Lock()
	tc.doubleGuardedField = 1
	tc.secondMu.Unlock()

	// This should fail as we released the secondMu.
	tc.doubleGuardedField = 2 // +checklocksfail
	tc.mu.Unlock()

	// This should fail as well as now we are not holding any locks.
	//
	// This line triggers two failures one for each mutex, hence the 2 after
	// fail.
	tc.doubleGuardedField = 3 // +checklocksfail:2
}

type rwGuarded struct {
	rwMu sync.RWMutex

	// +checklocks:rwMu
	rwGuardedField int
}

func testRWGuarded() {
	var tc rwGuarded

	// Assignment w/ exclusive lock should pass.
	tc.rwMu.Lock()
	tc.rwGuardedField = 1
	tc.rwMu.Unlock()

	// Assignment w/ RWLock should pass as we don't differentiate between
	// Lock/RLock.
	tc.rwMu.RLock()
	tc.rwGuardedField = 2
	tc.rwMu.RUnlock()

	// Assignment w/o hold Lock() should fail.
	tc.rwGuardedField = 3 // +checklocksfail

	// Reading w/o holding lock should fail.
	x := tc.rwGuardedField + 3 // +checklocksfail
	_ = x
}

type nestedFields struct {
	mu sync.Mutex

	// +checklocks:mu
	nestedStruct struct {
		nested1 int
		nested2 int
	}
}

func testNestedStructGuards() {
	var tc nestedFields
	// Valid access with mu held.
	tc.mu.Lock()
	tc.nestedStruct.nested1 = 1
	tc.nestedStruct.nested2 = 2
	tc.mu.Unlock()

	// Invalid access to nested1 wihout holding mu.
	tc.nestedStruct.nested1 = 1 // +checklocksfail
}

type testCaseMethods struct {
	mu sync.Mutex

	// +checklocks:mu
	guardedField int
}

func (t *testCaseMethods) Method() {
	// Valid access
	t.mu.Lock()
	t.guardedField = 1
	t.mu.Unlock()

	// invalid access
	t.guardedField = 2 // +checklocksfail
}

// +checklocks:t.mu
func (t *testCaseMethods) MethodLocked(a, b, c int) {
	t.guardedField = 3
}

// +checklocksignore
func (t *testCaseMethods) IgnoredMethod() {
	// Invalid access but should not fail as the function is annotated
	// with "// +checklocksignore"
	t.guardedField = 2
}

func testMethodCalls() {
	var tc2 testCaseMethods

	// Valid use, tc2.Method acquires lock.
	tc2.Method()

	// Valid access tc2.mu is held before calling tc2.MethodLocked.
	tc2.mu.Lock()
	tc2.MethodLocked(1, 2, 3)
	tc2.mu.Unlock()

	// Invalid access no locks are being held.
	tc2.MethodLocked(4, 5, 6) // +checklocksfail
}

type noMutex struct {
	f int
	g int
}

func (n noMutex) method() {
	n.f = 1
	n.f = n.g
}

func testNoMutex() {
	var n noMutex
	n.method()
}

func testMultiple() {
	var tc1, tc2, tc3 testCaseMethods

	tc1.mu.Lock()

	// Valid access we are holding tc1's lock.
	tc1.guardedField = 1

	// Invalid access we are not holding tc2 or tc3's lock.
	tc2.guardedField = 2 // +checklocksfail
	tc3.guardedField = 3 // +checklocksfail
	tc1.mu.Unlock()
}

func testConditionalBranchingLocks() {
	var tc2 testCaseMethods
	x := rand.Intn(10)
	if x%2 == 1 {
		tc2.mu.Lock()
	}
	// This is invalid access as tc2.mu is not held if we never entered
	// the if block.
	tc2.guardedField = 1 // +checklocksfail

	var tc3 testCaseMethods
	if x%2 == 1 {
		tc3.mu.Lock()
	} else {
		tc3.mu.Lock()
	}
	// This is valid as tc3.mu is held in if and else blocks.
	tc3.guardedField = 1
}

type testMethodWithParams struct {
	mu sync.Mutex

	// +checklocks:mu
	guardedField int
}

type ptrToTestMethodWithParams *testMethodWithParams

// +checklocks:t.mu
// +checklocks:a.mu
func (t *testMethodWithParams) methodLockedWithParams(a *testMethodWithParams, b *testMethodWithParams) {
	t.guardedField = a.guardedField
	b.guardedField = a.guardedField // +checklocksfail
}

// +checklocks:t.mu
// +checklocks:a.mu
// +checklocks:b.mu
func (t *testMethodWithParams) methodLockedWithPtrType(a *testMethodWithParams, b ptrToTestMethodWithParams) {
	t.guardedField = a.guardedField
	b.guardedField = a.guardedField
}

// +checklocks:a.mu
func standaloneFunctionWithGuard(a *testMethodWithParams) {
	a.guardedField = 1
	a.mu.Unlock()
	a.guardedField = 1 // +checklocksfail
}

type testMethodWithEmbedded struct {
	mu sync.Mutex

	// +checklocks:mu
	guardedField int
	p            *testMethodWithParams
}

// +checklocks:t.mu
func (t *testMethodWithEmbedded) DoLocked() {
	var a, b testMethodWithParams
	t.guardedField = 1
	a.mu.Lock()
	b.mu.Lock()
	t.p.methodLockedWithParams(&a, &b) // +checklocksfail
	a.mu.Unlock()
	b.mu.Unlock()
}

// UnsupportedLockerExample is a test that verifies that trying to annotate a
// field that is not a sync.Mutex/RWMutex results in a failure.
type UnsupportedLockerExample struct {
	mu sync.Locker

	// +checklocks:mu
	x int // +checklocksfail
}

func abc() {
	var mu sync.Mutex
	a := UnsupportedLockerExample{mu: &mu}
	a.x = 1
}