Skip to content

Commit

Permalink
[Frontend-go] Add interface unit test (#1975)
Browse files Browse the repository at this point in the history
* [Frontend-go] Add interface unit test

Signed-off-by: Arthur Chan <arthur.chan@adalogics.com>

* Fix formatting

Signed-off-by: Arthur Chan <arthur.chan@adalogics.com>

---------

Signed-off-by: Arthur Chan <arthur.chan@adalogics.com>
  • Loading branch information
arthurscchan authored Jan 15, 2025
1 parent 3ba9700 commit 93cf063
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 11 deletions.
46 changes: 36 additions & 10 deletions src/fuzz_introspector/frontends/frontend_go.py
Original file line number Diff line number Diff line change
Expand Up @@ -609,11 +609,19 @@ def _process_call_expr_child(
# Package/method call
if call_child.type == 'selector_expression':
target_name = call_child.text.decode()

# Variable call
split_call = target_name.split('.')
if len(split_call) > 1:
var_name = self.var_map.get(split_call[-2])
# For indexing selector
if '[' in split_call[-2] and ']' in split_call[-2]:
var_name = self.var_map.get(split_call[-2].split('[')[0])
if var_name:
if '[' in var_name and ']' in var_name:
var_name = var_name.split(']')[-1]
elif var_name == 'string':
var_name = 'uint8'
else:
var_name = self.var_map.get(split_call[-2])
if var_name:
target_name = f'{var_name}.{split_call[-1]}'

Expand Down Expand Up @@ -692,12 +700,6 @@ def extract_local_variable_type(self,
'FunctionMethod']):
"""Gets the local variable types of the function."""

query = self.tree_sitter_lang.query('( var_declaration ) @vd')
for _, exprs in query.captures(self.root).items():
for decl_node in exprs:
# TODO Handle long variable declaration
pass

query = self.tree_sitter_lang.query('( short_var_declaration ) @vd')
for _, exprs in query.captures(self.root).items():
for decl_node in exprs:
Expand All @@ -713,6 +715,31 @@ def extract_local_variable_type(self,
if decl_name and decl_type:
self.var_map[decl_name] = decl_type

query = self.tree_sitter_lang.query('( for_statement ) @fd')
for _, exprs in query.captures(self.root).items():
for for_node in exprs:
for child in for_node.children:
if child.type == 'range_clause':
left = child.child_by_field_name('left')
right = child.child_by_field_name('right')

for left_child in left.children:
if left_child.type == 'identifier':
decl_name = left_child.text.decode()

if right.type == 'identifier':
decl_type = self.var_map[right.text.decode()]
if '[' in decl_type and ']' in decl_type:
decl_type = decl_type.split(']', 1)[-1]
elif decl_type == 'string':
decl_type = 'uint8'
else:
decl_type = self._detect_variable_type(
right, all_funcs_meths)

if decl_name and decl_type:
self.var_map[decl_name] = decl_type

def extract_callsites(self, all_funcs_meths: dict[str, 'FunctionMethod']):
"""Gets the callsites of the function."""

Expand All @@ -735,9 +762,8 @@ def extract_callsites(self, all_funcs_meths: dict[str, 'FunctionMethod']):
call_expr.start_point.row + 1,
))

callsites = sorted(callsites, key=lambda x: x[1][1])
callsites = sorted(callsites, key=lambda x: x[1][0])
self.base_callsites = [(x[0], x[2]) for x in callsites]

# Process detailed callsites
for dst, src_line in self.base_callsites:
src_loc = self.parent_source.source_file + ':%d,1' % (src_line)
Expand Down
81 changes: 81 additions & 0 deletions src/test/data/source-code/go/test-project-5/fuzzer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright 2025 Google LLC
//
// 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 shapes

import (
"testing"
"strconv"
)

type Shape interface {
Area() float64
}

type Circle struct {
Radius float64
}

func (c Circle) Area() float64 {
return 3.14 * c.Radius * c.Radius
}

func (c Circle) Perimeter() float64 {
return 2 * 3.14 * c.Radius
}

type Rectangle struct {
Width, Height float64
}

func (r Rectangle) Area() float64 {
return r.Width * r.Height
}

func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}

func FuzzShapes(f *testing.F) {
f.Fuzz(func(t *testing.T, radiusString, widthString, heightString string) {
radius, err := strconv.ParseFloat(radiusString, 64)
if err != nil {
return
}

width, err := strconv.ParseFloat(widthString, 64)
if err != nil {
return
}

height, err := strconv.ParseFloat(heightString, 64)
if err != nil {
return
}

shapes := []Shape{
Circle{Radius: radius},
Rectangle{Width: width, Height: height},
}

for _, shape := range shapes {
_ = shape.Area()
}

for i := 0; i < len(shapes); i++ {
_ = shapes[i].Perimeter()
}
})
}
13 changes: 12 additions & 1 deletion src/test/test_frontends_go.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ def test_tree_sitter_go_sample4():


def test_tree_sitter_go_sample5():
"""
Similar to test_tree_sitter_rust_sample2, it is not able to
deteremine what instance the item is used until runtime.
"""
project = oss_fuzz.analyse_folder(
'go',
'src/test/data/source-code/go/test-project-5',
Expand All @@ -117,7 +121,14 @@ def test_tree_sitter_go_sample5():

# Project check
harness = project.get_source_codes_with_harnesses()
assert len(harness) == 0
assert len(harness) == 1

functions_reached = project.get_reachable_functions(harness[0].source_file, harness[0])

# Callsite check
assert 'strconv.ParseFloat' in functions_reached
assert 'Shape.Area' in functions_reached
assert 'Shape.Perimeter' in functions_reached


def test_tree_sitter_go_sample6():
Expand Down

0 comments on commit 93cf063

Please sign in to comment.