Browse Source

Externalize RC (github.com/01-edu/rc)

pull/768/head
xpetit 4 years ago
parent
commit
be12211b79
No known key found for this signature in database
GPG Key ID: 97C60669182C17A5
  1. 4
      go/tests/Dockerfile
  2. 7
      go/tests/go.mod
  3. 6
      go/tests/go.sum
  4. 139
      go/tests/rc/README.md
  5. 892
      go/tests/rc/rc.go
  6. 151
      go/tests/rc/rc_test.go
  7. 10
      go/tests/rc/tests/abc/main.go
  8. 214
      go/tests/rc/tests/doopprog/main.go
  9. 86
      go/tests/rc/tests/eightqueens.go
  10. 0
      go/tests/rc/tests/empty/empty
  11. 0
      go/tests/rc/tests/empty/empty.go
  12. 25
      go/tests/rc/tests/example.go
  13. 11
      go/tests/rc/tests/grand-prix-go-burdeux/isnegative.go
  14. 5
      go/tests/rc/tests/moreThanOnePackage/func.go
  15. 7
      go/tests/rc/tests/moreThanOnePackage/main.go
  16. 16
      go/tests/rc/tests/nesting/main.go
  17. 29
      go/tests/rc/tests/printalphabet/printalphabet.go
  18. 10
      go/tests/rc/tests/testLiteralRestriction/main.go
  19. 19
      go/tests/rc/tests/testingRepetitions/main.go
  20. 11
      go/tests/rc/tests/testingSimpleFunc.go
  21. 12
      go/tests/rc/tests/testingWrapping.go
  22. 23
      go/tests/rc/tests/util/util.go
  23. 5
      go/tests/rc/tests/utilDepth2/wrapper.go

4
go/tests/Dockerfile

@ -15,8 +15,8 @@ COPY base base
COPY func func COPY func func
COPY lib lib COPY lib lib
COPY prog prog COPY prog prog
COPY rc rc RUN go get github.com/01-edu/rc
RUN go install ./rc ./prog/... RUN go install ./prog/...
RUN rm -rf /piscine-go RUN rm -rf /piscine-go
COPY entrypoint.sh /usr/local/bin COPY entrypoint.sh /usr/local/bin

7
go/tests/go.mod

@ -2,11 +2,6 @@ module github.com/01-edu/public/go/tests
go 1.16 go 1.16
require ( require student v0.0.0
github.com/01-edu/z01 v0.1.0
github.com/mattn/go-runewidth v0.0.10 // indirect
github.com/tatsushid/go-prettytable v0.0.0-20141013043238-ed2d14c29939
student v0.0.0
)
replace student => ../../../piscine-go replace student => ../../../piscine-go

6
go/tests/go.sum

@ -1,8 +1,2 @@
github.com/01-edu/z01 v0.1.0 h1:Rr1p92CKYXUgVFw872iGDABXpdkuKf2jmS+KV6r1034= github.com/01-edu/z01 v0.1.0 h1:Rr1p92CKYXUgVFw872iGDABXpdkuKf2jmS+KV6r1034=
github.com/01-edu/z01 v0.1.0/go.mod h1:BH7t35JaNFuP83rTJDc5nkSfgmC/HYVcJsUcdFqYZNo= github.com/01-edu/z01 v0.1.0/go.mod h1:BH7t35JaNFuP83rTJDc5nkSfgmC/HYVcJsUcdFqYZNo=
github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/tatsushid/go-prettytable v0.0.0-20141013043238-ed2d14c29939 h1:BhIUXV2ySTLrKgh/Hnts+QTQlIbWtomXt3LMdzME0A0=
github.com/tatsushid/go-prettytable v0.0.0-20141013043238-ed2d14c29939/go.mod h1:omGxs4/6hNjxPKUTjmaNkPzehSnNJOJN6pMEbrlYIT4=

139
go/tests/rc/README.md

@ -1,139 +0,0 @@
### RC (Restrictions Checker)
This program analyzes a go source file and displays in standard output the imports, functions, slice types and loops used without authorization.
### By default:
- Allowed
- All functions declared inside the source file are allowed
- Slices of all types are allowed
- Loops are allowed
- Relative imports are allowed
- Disallowed
- NO absolute imports are allowed
- NO built-in functions are allowed.
- NO casting is allowed
### Flags
- `-h` for help
- `-cast` allows casting to every built-in type.
- `-no-for` prohibits the use of `for` loops in the program or function.
- `-allow-builtin` allows all builtin functions and casting to builtin types
- `-no-slices` disallows the use of all slices types
- `-no-these-slices=type1,type2`: disallows the slices of type1 and type2
- `-no-relative-imports`: disallows the use of relative imports
- `--no-lit="{PATTERN}"`: disallows character and string literals that match the pattern `PATTERN` which represent a Regular Expression
### Arguments:
- Flags can be passed at any point (in the beginning, middle or end) of the argument list
- The remaining arguments represent the allowed functions
- Allowed imports and functions from a package
- `<package>.*` for full imports (all functions from that package are allowed)
- `<package>`.`<function>` for partial imports (only the function is allowed)
- `<package>`.`<function>#amount` the function is only allowed to be used `amount` number of times
- Ex: `fmt.*` (all functions from `fmt` are allowed), `github.com/01-edu/z01.PrintRune` (only `z01.PrintRune` is allowed), `fmt.Println#2` (fmt.Println can only be used 2 times or less)
- Allowed built-in functions
- Use the name of the built-in function
- It is possible to limit the number of calls of a functions like with the imports using the '#' character
- Ex: `make`, `append`, `len`, `print#2`.
### Example:
- To allow the import of the whole `fmt` package, `z01.PrintRune` and the built-in functions `len` for the file `main.go`
Note: The imports must be written exactly the way they are written inside the source code, example:
```console
_$ rc main.go fmt.* github.com/01-edu/z01.PrintRune len
```
- Import "fmt" is allowed by executing
```console
_$ rc sourcefile.go fmt.*
```
- Import "go/parser" is allowed by executing
```console
_$ rc sourcefile.go go/parser.*
```
- Import "github.com/01-edu/z01" is allowed by executing
```console
_$ rc sourcefile.go github.com/01-edu/z01.*
```
- Disallow literals
- Use the flag `--no-lit="{PATTERN}"`
```console
_$ rc -no-slices --no-lit=[b-yB-Y] main.go fmt.* github.com/01-edu/z01.PrintRune len
```
- Allow all type of casting
```console
_$ rc -cast sourcefile.go fmt.* github.com/01-edu/z01 os.* strconv.* make len append
```
- this will allow all type of casting in the file sourcefile.go
- Disallow the use of the slices of type `string` and `int`
```console
_$ rc -no-these-slices=string,int sourcefile.go
```
- To allow just one type of casting
```console
_$ rc sourcefile.go fmt.* github.com/01-edu/z01 os.* strconv.* make len append rune
```
- this will allow the casting to `rune`, but not `int8`, ..., `string`, `float32`, ...
### How to read the error message
Let us look to an example snipped of code, let us imagine this code in a file called `main.go`:
```go
package main
import "fmt"
func main() {
for _, v := range "abcdefghijklmnopqrstuvwxyz" {
fmt.Println(v)
}
fmt.Println()
}
```
Now let us run the `rc` and understand the message
```console
_$ rc main.go github.com/01-edu/z01.PrintRune
Parsing:
Ok
Cheating:
TYPE: NAME: LOCATION:
illegal-import fmt main.go:3:8
illegal-access fmt.Println main.go:7:3
illegal-access fmt.Println main.go:10:2
illegal-definition main main.go:5:1
```
The important part is printed after the `Cheating` tag:
- The import of of the package `fmt` is not allowed
- In go the dot (.) is also known as the access operator for that reason the use of fmt.Println is shown as an illegal-access
- Finally the main function is shown as illegal-definition because the function is using disallowed functions that does not mean that the function can not be defined it just mean that the definition of the function must be changed to not use disallowed functions.
- Notice that the third column of the output with the tag "LOCATION:" show the location in the following way filepath:line:column
This mean that you have to substitute the illegal function for ones that are allowed or write your own function with allowed functions

892
go/tests/rc/rc.go

@ -1,892 +0,0 @@
package main
import (
"flag"
"fmt"
"go/ast"
"go/parser"
"go/token"
"os"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
table "github.com/tatsushid/go-prettytable"
)
type strBoolMap map[string]bool
// Implementation of the flag.Value interface
func (a *strBoolMap) String() (res string) {
for k, _ := range *a {
res += k
}
return res
}
// Implementation of the flag.Value interface
func (a *strBoolMap) Set(str string) error {
if *a == nil {
*a = make(map[string]bool)
}
s := strings.Split(str, ",")
for _, v := range s {
(*a)[v] = true
}
return nil
}
// Flag that groups a boolean value and a regular expression
type regexpFlag struct {
active bool
reg *regexp.Regexp
}
// Implementation of the flag.Value interface
func (r *regexpFlag) String() string {
if r.reg != nil {
return r.reg.String()
}
return ""
}
// Implementation of the flag.Value interface
func (r *regexpFlag) Set(s string) error {
r.active = true
r.reg = regexp.MustCompile(s)
return nil
}
var (
allowedFun = make(map[string]map[string]bool)
allowedRep = make(map[string]int)
// Flags
noArrays bool
noSlices bool
noRelativeImports bool
noTheseSlices strBoolMap
casting bool
noFor bool
noLit regexpFlag
allowBuiltin bool
)
type illegal struct {
T string
Name string
Pos string
}
func (i *illegal) String() string {
return i.T + " " + i.Name + " " + i.Pos
}
func init() {
flag.Var(&noTheseSlices, "no-these-slices", "Disallowes the slice types passed in the flag as a comma-separated list without spaces\nLike so: -no-these-slices=int,string,bool")
flag.Var(&noLit, "no-lit",
`The use of basic literals (strings or characters) matching the pattern -no-lit="{PATTERN}"
passed to the program would not be allowed`,
)
flag.BoolVar(&noRelativeImports, "no-relative-imports", false, `Disallowes the use of relative imports`)
flag.BoolVar(&noFor, "no-for", false, `The "for" instruction is not allowed`)
flag.BoolVar(&casting, "cast", false, "Allowes casting")
flag.BoolVar(&noArrays, "no-array", false, "Deprecated: use -no-slices")
flag.BoolVar(&noSlices, "no-slices", false, "Disallowes all slice types")
flag.BoolVar(&allowBuiltin, "allow-builtin", false, "Allowes all builtin functions and casting")
sort.Sort(sort.StringSlice(os.Args[1:]))
}
func main() {
flag.Parse()
filename := goFile(flag.Args())
if _, err := os.Stat(filename); err != nil {
fmt.Printf("\t%s\n", err)
os.Exit(1)
}
err := parseArgs(flag.Args())
if err != nil {
fmt.Printf("\t%s\n", err)
os.Exit(1)
}
load := make(loadedSource)
currentPath := filepath.Dir(filename)
err = loadProgram(currentPath, load)
if err != nil {
fmt.Printf("\t%s\n", err)
os.Exit(1)
}
info := analyzeProgram(filename, currentPath, load)
if info.illegals != nil {
fmt.Println("Cheating:")
printIllegals(info.illegals)
os.Exit(1)
}
}
func goFile(args []string) string {
for _, v := range args {
if strings.HasSuffix(v, ".go") {
return v
}
}
return ""
}
// Returns the smallest block containing the position pos. It can
// return nil if `pos` is not inside any ast.BlockStmt
func smallestBlock(pos token.Pos, blocks []*ast.BlockStmt) (minBlock *ast.BlockStmt) {
var minSize token.Pos
for _, v := range blocks {
if pos > v.Pos() && pos < v.End() {
size := v.End() - v.Pos()
if minBlock == nil || size < minSize {
minBlock = v
minSize = size
}
}
}
return minBlock
}
// Used to mark an ast.Object as a function parameter
type data struct {
parameter bool
}
func fillScope(funcDefs []*function, scope *ast.Scope, scopes map[*ast.BlockStmt]*ast.Scope) {
for _, fun := range funcDefs {
scope.Insert(fun.obj)
for _, name := range fun.params {
obj := ast.NewObj(ast.Fun, name)
obj.Data = data{
parameter: true,
}
scopes[fun.body].Insert(obj)
}
}
}
// Create the scopes for a BlockStmt contained inside another BlockStmt
func createChildScope(
block *ast.BlockStmt,
l *loadVisitor, scopes map[*ast.BlockStmt]*ast.Scope) {
blocks := l.blocks
// The smalles block containing the beggining of the block
parentBlock := smallestBlock(block.Pos(), blocks)
if scopes[parentBlock] == nil {
createChildScope(parentBlock, l, scopes)
}
scopes[block] = ast.NewScope(scopes[parentBlock])
}
// Returns true if `block` is contained inside another ast.BlockStmt
func isContained(block *ast.BlockStmt, blocks []*ast.BlockStmt) bool {
for _, v := range blocks {
if block == v {
continue
}
if block.Pos() > v.Pos() && block.End() < v.End() {
return true
}
}
return false
}
// Creates all the scopes in the package
func createScopes(l *loadVisitor, pkgScope *ast.Scope) map[*ast.BlockStmt]*ast.Scope {
scopes := make(map[*ast.BlockStmt]*ast.Scope)
if l.blocks == nil {
return nil
}
for _, b := range l.blocks {
if !isContained(b, l.blocks) {
scopes[b] = ast.NewScope(pkgScope)
}
}
for _, b := range l.blocks {
if scopes[b] == nil {
createChildScope(b, l, scopes)
}
}
return scopes
}
type blockVisitor struct {
funct []*function
// All functions defined in the scope in any
// way: as a funcDecl, GenDecl or AssigmentStmt
oneBlock bool
// Indicates if the visitor already encounter a
// blockStmt
}
func (b *blockVisitor) Visit(n ast.Node) ast.Visitor {
switch t := n.(type) {
case *ast.BlockStmt:
if b.oneBlock {
return nil
}
return b
case *ast.FuncDecl, *ast.GenDecl, *ast.AssignStmt:
def := extractFunction(t)
if def == nil || def.obj == nil {
return b
}
b.funct = append(b.funct, def)
return nil
default:
return b
}
}
type loadedSource map[string]*loadVisitor
// Returns information about the function defined in the block node
func functionsInfo(block ast.Node) []*function {
b := &blockVisitor{}
ast.Walk(b, block)
return b.funct
}
func (l *loadVisitor) set() {
l.functions = make(map[string]ast.Node)
l.absImports = make(map[string]*element)
l.relImports = make(map[string]*element)
l.objFunc = make(map[*ast.Object]ast.Node)
l.fset = token.NewFileSet()
l.scopes = make(map[*ast.BlockStmt]*ast.Scope)
}
func loadProgram(path string, load loadedSource) error {
l := &loadVisitor{}
l.set()
pkgs, err := parser.ParseDir(l.fset, path, nil, parser.AllErrors)
if err != nil {
return err
}
if len(pkgs) > 1 {
packages := []string{}
for pkgName := range pkgs {
packages = append(packages, pkgName)
}
return fmt.Errorf("There should be only one package in this directory: Found packages: %v", packages)
}
for _, pkg := range pkgs {
ast.Walk(l, pkg)
l.pkgScope = ast.NewScope(nil)
functions := functionsInfo(pkg)
for _, f := range functions {
l.pkgScope.Insert(f.obj)
}
l.scopes = createScopes(l, l.pkgScope)
fillScope(functions, l.pkgScope, l.scopes)
for block, scope := range l.scopes {
functions := functionsInfo(block)
fillScope(functions, scope, l.scopes)
}
load[path] = l
l.files = pkg.Files
}
for _, relativePath := range l.relImports {
if load[relativePath.name] == nil {
newPath := filepath.Clean(path + "/" + relativePath.name)
err = loadProgram(newPath, load)
if err != nil {
return err
}
}
}
return nil
}
func smallestScopeContaining(pos token.Pos, path string, load loadedSource) *ast.Scope {
pack := load[path]
sm := smallestBlock(pos, pack.blocks)
if sm == nil {
return pack.pkgScope
}
return pack.scopes[sm]
}
func lookupDefinitionObj(el *element, path string, load loadedSource) *ast.Object {
scope := smallestScopeContaining(el.pos, path, load)
for scope != nil {
obj := scope.Lookup(el.name)
if obj != nil {
return obj
}
scope = scope.Outer
}
return nil
}
type visitor struct {
fset *token.FileSet
uses []*element
selections map[string][]*element
arrays []*occurrence
lits []*occurrence
fors []*occurrence
callRepetition map[string]int
oneTime bool
}
func (v *visitor) getPos(n ast.Node) string {
return v.fset.Position(n.Pos()).String()
}
func (v *visitor) Visit(n ast.Node) ast.Visitor {
switch t := n.(type) {
case *ast.FuncDecl, *ast.GenDecl, *ast.AssignStmt:
//Avoids analyzing a nested declarations
//Since this is handle by the functions `isAllowed`
fdef := extractFunction(t)
if fdef == nil || fdef.obj == nil {
return v
}
if v.oneTime {
return nil
}
v.oneTime = true
return v
case *ast.BasicLit:
if t.Kind != token.CHAR && t.Kind != token.STRING {
return nil
}
v.lits = append(v.lits, &occurrence{pos: v.getPos(n), name: t.Value})
case *ast.ArrayType:
if op, ok := t.Elt.(*ast.Ident); ok {
v.arrays = append(v.arrays, &occurrence{
name: op.Name,
pos: v.getPos(n),
})
}
case *ast.ForStmt:
v.fors = append(v.fors, &occurrence{
name: "for",
pos: v.getPos(n),
})
case *ast.CallExpr:
if fun, ok := t.Fun.(*ast.Ident); ok {
v.uses = append(v.uses, &element{
name: fun.Name,
pos: fun.Pos(),
})
v.callRepetition[fun.Name]++
}
case *ast.SelectorExpr:
if x, ok := t.X.(*ast.Ident); ok {
v.selections[x.Name] = append(v.selections[x.Name], &element{
name: t.Sel.Name,
pos: n.Pos(),
})
v.callRepetition[x.Name+"."+t.Sel.Name]++
}
}
return v
}
func (v *visitor) set(fset *token.FileSet) {
v.selections = make(map[string][]*element)
v.callRepetition = make(map[string]int)
v.fset = fset
}
func (info *info) add(v *visitor) {
info.fors = append(info.fors, v.fors...)
info.lits = append(info.lits, v.lits...)
info.arrays = append(info.arrays, v.arrays...)
for name, v := range v.callRepetition {
info.callRepetition[name] += v
}
}
// Returns the info structure with all the ocurrences of the element
// of the analised in the project
// TODO: Refactor so this function has only one responsibility
func isAllowed(function *element, path string, load loadedSource, walked map[ast.Node]bool, info *info) bool {
functionObj := lookupDefinitionObj(function, path, load)
definedLocally := functionObj != nil
explicitlyAllowed := allowedFun["builtin"]["*"] || allowedFun["builtin"][function.name]
isFunctionParameter := func(function *ast.Object) bool {
arg, ok := function.Data.(data)
return ok && arg.parameter
}
DoesntCallMoreFunctions := func(functionDefinition ast.Node, v *visitor) bool {
if !walked[functionDefinition] {
ast.Walk(v, functionDefinition)
info.add(v)
walked[functionDefinition] = true
}
return v.uses == nil && v.selections == nil
}
appendIllegalCall := func(function *element) {
info.illegals = append(info.illegals, &illegal{
T: "illegal-call",
Name: function.name,
Pos: load[path].fset.Position(function.pos).String(),
})
}
if !definedLocally && !explicitlyAllowed {
appendIllegalCall(function)
return false
}
functionDefinition := load[path].objFunc[functionObj]
v := &visitor{}
v.set(load[path].fset)
if explicitlyAllowed || isFunctionParameter(functionObj) ||
DoesntCallMoreFunctions(functionDefinition, v) {
return true
}
allowed := true
for _, functionCall := range v.uses {
if !isAllowed(functionCall, path, load, walked, info) {
appendIllegalCall(functionCall)
allowed = false
}
}
for pck, funcNames := range v.selections {
pathToFunction := func() string { return load[path].relImports[pck].name }
isRelativeImport := load[path].relImports[pck] != nil
for _, fun := range funcNames {
appendIllegalAccess := func() {
info.illegals = append(info.illegals, &illegal{
T: "illegal-access",
Name: pck + "." + fun.name,
Pos: load[path].fset.Position(fun.pos).String(),
})
allowed = false
}
absoluteImport := load[path].absImports[pck]
importExplicitlyAllowed := absoluteImport == nil ||
allowedFun[absoluteImport.name][fun.name] ||
allowedFun[absoluteImport.name]["*"]
if !isRelativeImport && !importExplicitlyAllowed {
appendIllegalAccess()
} else if isRelativeImport &&
!isAllowed(newElement(fun.name), filepath.Clean(path+"/"+pathToFunction()), load, walked, info) {
appendIllegalAccess()
}
}
}
if !allowed {
info.illegals = append(info.illegals, &illegal{
T: "illegal-definition",
Name: functionObj.Name,
Pos: load[path].fset.Position(functionDefinition.Pos()).String(),
})
}
return allowed
}
func removeRepetitions(slc []*illegal) (result []*illegal) {
in := make(map[string]bool)
for _, v := range slc {
if in[v.Pos] {
continue
}
result = append(result, v)
in[v.Pos] = true
}
return result
}
type occurrence struct {
name string
pos string
}
type info struct {
arrays []*occurrence
lits []*occurrence
fors []*occurrence
callRepetition map[string]int
illegals []*illegal // functions, selections that are not allowed
}
func newElement(name string) *element {
return &element{
name: name,
pos: token.Pos(0),
}
}
func analyzeProgram(filename, path string, load loadedSource) *info {
fset := load[path].fset
file := load[path].files[filename]
functions := functionsInfo(file)
info := &info{
callRepetition: make(map[string]int),
}
info.illegals = append(info.illegals, analyzeImports(file, fset, noRelativeImports)...)
walked := make(map[ast.Node]bool)
for _, fun := range functions {
function := newElement(fun.obj.Name)
isAllowed(function, path, load, walked, info)
}
info.illegals = append(info.illegals, analyzeLoops(info.fors, noFor)...)
info.illegals = append(info.illegals, analyzeArrayTypes(info.arrays, noArrays || noSlices, noTheseSlices)...)
info.illegals = append(info.illegals, analyzeLits(info.lits, noLit)...)
info.illegals = append(info.illegals, analyzeRepetition(info.callRepetition, allowedRep)...)
info.illegals = removeRepetitions(info.illegals)
return info
}
func parseArgs(toAllow []string) error {
allowBuiltins()
allowCasting()
for _, v := range toAllow {
err := allowFunction(v)
if err != nil {
return err
}
}
return nil
}
func allowFunction(functionPath string) error {
functionName := functionName(functionPath)
packageName := packageName(functionPath)
// for github.com/01-edu/z01 shortName = z01
packageShortName := filepath.Base(packageName)
restrictsRepetitions := strings.ContainsRune(functionPath, '#')
if restrictsRepetitions {
allowedReps, err := repetitionsAllowed(functionPath)
if err != nil {
return err
}
allowedRep[packageShortName+"."+functionName] = allowedReps
}
if allowedFun[packageName] == nil {
allowedFun[packageName] = make(map[string]bool)
}
allowedFun[packageName][functionName] = true
return nil
}
func functionName(functionPath string) string {
segmentedPath := strings.Split(functionPath, ".")
return strings.Split(segmentedPath[len(segmentedPath)-1], "#")[0]
}
func packageName(functionPath string) string {
segmentedPath := strings.Split(functionPath, ".")
hasNoPackage := len(segmentedPath) < 2
if hasNoPackage {
return "builtin"
}
return strings.Join(segmentedPath[:len(segmentedPath)-1], ".")
}
// Assumes that `functionPath` contains `#`
func repetitionsAllowed(functionPath string) (int, error) {
segmentedPath := strings.Split(functionPath, "#")
repetitions := segmentedPath[len(segmentedPath)-1]
allowedReps, err := strconv.Atoi(repetitions)
if err != nil {
return allowedReps, fmt.Errorf("After the '#' there should be an integer" +
" representing the maximum number of allowed occurrences")
}
return allowedReps, nil
}
func allowBuiltins() {
if allowedFun["builtin"] == nil {
allowedFun["builtin"] = make(map[string]bool)
}
if allowBuiltin {
allowedFun["builtin"]["*"] = true
}
}
func allowCasting() {
if allowedFun["builtin"] == nil {
allowedFun["builtin"] = make(map[string]bool)
}
predeclaredTypes := []string{"bool", "byte", "complex64", "complex128",
"error", "float32", "float64", "int", "int8",
"int16", "int32", "int64", "rune", "string",
"uint", "uint8", "uint16", "uint32", "uint64",
"uintptr",
}
if casting {
for _, v := range predeclaredTypes {
allowedFun["builtin"][v] = true
}
}
}
func printIllegals(illegals []*illegal) {
tbl, err := table.NewTable([]table.Column{
{Header: "\tTYPE:"},
{Header: "NAME:", MinWidth: 7},
{Header: "LOCATION:"},
}...)
if err != nil {
panic(err)
}
tbl.Separator = "\t"
for _, v := range illegals {
tbl.AddRow("\t"+v.T, v.Name, v.Pos)
}
tbl.Print()
}
func analyzeRepetition(callRepetition map[string]int, allowRep map[string]int) (illegals []*illegal) {
for name, rep := range allowedRep {
if callRepetition[name] > rep {
diff := callRepetition[name] - rep
illegals = append(illegals, &illegal{
T: "illegal-amount",
Name: name + " exeding max repetitions by " + strconv.Itoa(diff),
Pos: "all the project",
})
}
}
return illegals
}
func analyzeLits(litOccu []*occurrence, noLit regexpFlag) (illegals []*illegal) {
if noLit.active {
for _, v := range litOccu {
if noLit.reg.Match([]byte(v.name)) {
illegals = append(illegals, &illegal{
T: "illegal-lit",
Name: v.name,
Pos: v.pos,
})
}
}
}
return illegals
}
func analyzeArrayTypes(arrays []*occurrence, noArrays bool, noTheseSlices map[string]bool) (illegals []*illegal) {
for _, v := range arrays {
if noArrays || noTheseSlices[v.name] {
illegals = append(illegals, &illegal{
T: "illegal-slice",
Name: v.name,
Pos: v.pos,
})
}
}
return illegals
}
func analyzeLoops(fors []*occurrence, noFor bool) (illegals []*illegal) {
if noFor {
for _, v := range fors {
illegals = append(illegals, &illegal{
T: "illegal-loop",
Name: v.name,
Pos: v.pos,
})
}
}
return illegals
}
type importVisitor struct {
imports map[string]*element
}
func (i *importVisitor) Visit(n ast.Node) ast.Visitor {
if imp, ok := n.(*ast.ImportSpec); ok {
path, _ := strconv.Unquote(imp.Path.Value)
var name string
if imp.Name != nil {
name = imp.Name.Name
} else {
name = filepath.Base(path)
}
el := &element{
name: path,
pos: n.Pos(),
}
i.imports[name] = el
}
return i
}
func analyzeImports(file ast.Node, fset *token.FileSet, noRelImp bool) (illegals []*illegal) {
i := &importVisitor{
imports: make(map[string]*element),
}
ast.Walk(i, file)
for _, path := range i.imports {
isRelativeImport := isRelativeImport(path.name)
if (noRelativeImports && isRelativeImport) || (allowedFun[path.name] == nil && !isRelativeImport) {
illegals = append(illegals, &illegal{
T: "illegal-import",
Name: path.name,
Pos: fset.Position(path.pos).String(),
})
}
}
return illegals
}
type element struct {
name string
pos token.Pos
}
type loadVisitor struct {
relImports map[string]*element
absImports map[string]*element
functions map[string]ast.Node
fset *token.FileSet
objFunc map[*ast.Object]ast.Node
blocks []*ast.BlockStmt
scopes map[*ast.BlockStmt]*ast.Scope
// nil after the visit
// used to keep the result of the createScope function
pkgScope *ast.Scope
files map[string]*ast.File
}
func (l *loadVisitor) String() (res string) {
res = "files"
for f, _ := range l.files {
res += f + ","
}
return res
}
// Returns all the parameter of a function that identify a function
func functionsInTheParameters(params *ast.FieldList) []string {
var funcs []string
for _, param := range params.List {
if _, ok := param.Type.(*ast.FuncType); ok {
for _, name := range param.Names {
funcs = append(funcs, name.Name)
}
}
}
return funcs
}
type function struct {
obj *ast.Object // the ast.Object that represents a function
params []string
// the name of the parameter that represent
// functions
body *ast.BlockStmt
}
// Returns information about a node representing a function declaration
func extractFunction(n ast.Node) *function {
function := &function{}
switch t := n.(type) {
case *ast.FuncDecl:
function.obj = t.Name.Obj
function.params = functionsInTheParameters(t.Type.Params)
function.body = t.Body
return function
case *ast.GenDecl:
for _, v := range t.Specs {
if val, ok := v.(*ast.ValueSpec); ok {
for i, value := range val.Values {
if funcLit, ok := value.(*ast.FuncLit); ok {
function.obj = val.Names[i].Obj
function.params = functionsInTheParameters(funcLit.Type.Params)
function.body = funcLit.Body
}
}
}
}
return function
case *ast.AssignStmt:
for i, right := range t.Rhs {
if funcLit, ok := right.(*ast.FuncLit); ok {
if ident, ok := t.Lhs[i].(*ast.Ident); ok {
function.obj = ident.Obj
function.params = functionsInTheParameters(funcLit.Type.Params)
}
}
return function
}
default:
return function
}
return function
}
func (l *loadVisitor) Visit(n ast.Node) ast.Visitor {
switch t := n.(type) {
case *ast.ImportSpec:
path, _ := strconv.Unquote(t.Path.Value)
var name string
if t.Name != nil {
name = t.Name.Name
} else {
name = filepath.Base(path)
}
el := &element{
name: path,
pos: n.Pos(),
}
if isRelativeImport(path) {
l.relImports[name] = el
} else {
l.absImports[name] = el
}
case *ast.FuncDecl, *ast.GenDecl, *ast.AssignStmt:
fdef := extractFunction(t)
if fdef == nil || fdef.obj == nil {
return l
}
l.objFunc[fdef.obj] = n
case *ast.BlockStmt:
l.blocks = append(l.blocks, t)
}
return l
}
// Returns true if the string matches the format of a relative import
func isRelativeImport(s string) bool {
return strings.HasPrefix(s, ".")
}

151
go/tests/rc/rc_test.go

@ -1,151 +0,0 @@
package main
import (
"sort"
"strings"
"testing"
"github.com/01-edu/z01"
)
func TestFlags(t *testing.T) {
argsAndSolution := map[string]string{
"tests/testingSimpleFunc.go": `Cheating:
TYPE: NAME: LOCATION:
illegal-import regexp tests/testingSimpleFunc.go:4:2
illegal-call len tests/testingSimpleFunc.go:10:9
illegal-access regexp.MustCompile tests/testingSimpleFunc.go:8:8
illegal-definition SimpleFunc tests/testingSimpleFunc.go:7:1
`,
"-no-for -no-lit=[a-z] tests/printalphabet/printalphabet.go": `Cheating:
TYPE: NAME: LOCATION:
illegal-import fmt tests/printalphabet/printalphabet.go:4:2
illegal-import github.com/01-edu/z01 tests/printalphabet/printalphabet.go:6:2
illegal-call append tests/printalphabet/printalphabet.go:11:7
illegal-definition fillArray tests/printalphabet/printalphabet.go:9:1
illegal-call int tests/printalphabet/printalphabet.go:17:7
illegal-access z01.PrintRune tests/printalphabet/printalphabet.go:19:3
illegal-access z01.PrintRune tests/printalphabet/printalphabet.go:21:2
illegal-definition main tests/printalphabet/printalphabet.go:15:1
illegal-access fmt.Println tests/printalphabet/printalphabet.go:26:3
illegal-definition defFun tests/printalphabet/printalphabet.go:25:2
illegal-call defFun tests/printalphabet/printalphabet.go:28:2
illegal-definition testingScope tests/printalphabet/printalphabet.go:24:1
illegal-loop for tests/printalphabet/printalphabet.go:10:2
illegal-lit 'a' tests/printalphabet/printalphabet.go:10:11
illegal-lit 'z' tests/printalphabet/printalphabet.go:10:21
illegal-lit 'a' tests/printalphabet/printalphabet.go:16:14
illegal-lit 'b' tests/printalphabet/printalphabet.go:16:19
illegal-lit 'c' tests/printalphabet/printalphabet.go:16:24
illegal-lit 'd' tests/printalphabet/printalphabet.go:16:29
illegal-lit 'e' tests/printalphabet/printalphabet.go:16:34
illegal-lit 'f' tests/printalphabet/printalphabet.go:16:39
illegal-lit 'a' tests/printalphabet/printalphabet.go:17:11
illegal-lit '\n' tests/printalphabet/printalphabet.go:21:16
illegal-lit "Hello" tests/printalphabet/printalphabet.go:28:9
`,
"-cast tests/eightqueens.go": `Cheating:
TYPE: NAME: LOCATION:
illegal-import github.com/01-edu/z01 tests/eightqueens.go:4:2
illegal-access z01.PrintRune tests/eightqueens.go:49:5
illegal-access z01.PrintRune tests/eightqueens.go:55:2
illegal-definition printQueens tests/eightqueens.go:42:1
`,
"-no-array tests/printalphabet/printalphabet.go": `Cheating:
TYPE: NAME: LOCATION:
illegal-import fmt tests/printalphabet/printalphabet.go:4:2
illegal-import github.com/01-edu/z01 tests/printalphabet/printalphabet.go:6:2
illegal-call append tests/printalphabet/printalphabet.go:11:7
illegal-definition fillArray tests/printalphabet/printalphabet.go:9:1
illegal-call int tests/printalphabet/printalphabet.go:17:7
illegal-access z01.PrintRune tests/printalphabet/printalphabet.go:19:3
illegal-access z01.PrintRune tests/printalphabet/printalphabet.go:21:2
illegal-definition main tests/printalphabet/printalphabet.go:15:1
illegal-access fmt.Println tests/printalphabet/printalphabet.go:26:3
illegal-definition defFun tests/printalphabet/printalphabet.go:25:2
illegal-call defFun tests/printalphabet/printalphabet.go:28:2
illegal-definition testingScope tests/printalphabet/printalphabet.go:24:1
illegal-slice rune tests/printalphabet/printalphabet.go:9:18
illegal-slice rune tests/printalphabet/printalphabet.go:16:7
`,
"-no-slices tests/printalphabet/printalphabet.go": `Cheating:
TYPE: NAME: LOCATION:
illegal-import fmt tests/printalphabet/printalphabet.go:4:2
illegal-import github.com/01-edu/z01 tests/printalphabet/printalphabet.go:6:2
illegal-call append tests/printalphabet/printalphabet.go:11:7
illegal-definition fillArray tests/printalphabet/printalphabet.go:9:1
illegal-call int tests/printalphabet/printalphabet.go:17:7
illegal-access z01.PrintRune tests/printalphabet/printalphabet.go:19:3
illegal-access z01.PrintRune tests/printalphabet/printalphabet.go:21:2
illegal-definition main tests/printalphabet/printalphabet.go:15:1
illegal-access fmt.Println tests/printalphabet/printalphabet.go:26:3
illegal-definition defFun tests/printalphabet/printalphabet.go:25:2
illegal-call defFun tests/printalphabet/printalphabet.go:28:2
illegal-definition testingScope tests/printalphabet/printalphabet.go:24:1
illegal-slice rune tests/printalphabet/printalphabet.go:9:18
illegal-slice rune tests/printalphabet/printalphabet.go:16:7
`,
"-no-these-slices=int,rune tests/printalphabet/printalphabet.go": `Cheating:
TYPE: NAME: LOCATION:
illegal-import fmt tests/printalphabet/printalphabet.go:4:2
illegal-import github.com/01-edu/z01 tests/printalphabet/printalphabet.go:6:2
illegal-call append tests/printalphabet/printalphabet.go:11:7
illegal-definition fillArray tests/printalphabet/printalphabet.go:9:1
illegal-call int tests/printalphabet/printalphabet.go:17:7
illegal-access z01.PrintRune tests/printalphabet/printalphabet.go:19:3
illegal-access z01.PrintRune tests/printalphabet/printalphabet.go:21:2
illegal-definition main tests/printalphabet/printalphabet.go:15:1
illegal-access fmt.Println tests/printalphabet/printalphabet.go:26:3
illegal-definition defFun tests/printalphabet/printalphabet.go:25:2
illegal-call defFun tests/printalphabet/printalphabet.go:28:2
illegal-definition testingScope tests/printalphabet/printalphabet.go:24:1
illegal-slice rune tests/printalphabet/printalphabet.go:9:18
illegal-slice rune tests/printalphabet/printalphabet.go:16:7
`,
`-allow-builtin tests/doopprog/main.go fmt.Println strconv.Atoi os.Args`: ``,
`-cast tests/doopprog/main.go fmt.Println strconv.Atoi os.Args len`: ``,
`tests/testingWrapping.go`: `Cheating:
TYPE: NAME: LOCATION:
illegal-call len tests/utilDepth2/wrapper.go:4:9
illegal-definition LenWrapper tests/utilDepth2/wrapper.go:3:1
illegal-access util2.LenWrapper tests/util/util.go:10:9
illegal-definition LenWrapperU tests/util/util.go:9:1
illegal-access util.LenWrapperU tests/testingWrapping.go:8:9
illegal-definition Length tests/testingWrapping.go:7:1
`,
`tests/testingWrapping.go len`: ``,
`tests/empty/empty len`: ` stat : no such file or directory
`,
`tests/empty/empty.go tests/empty/empty`: ` tests/empty/empty.go:1:1: expected ';', found 'EOF' (and 2 more errors)
`,
`tests/abc/main.go --cast github.com/01-edu/z01.PrintRune#2 --no-lit=[b-mB-Y]`: ``,
`itoa.go len --cast`: ` stat itoa.go: no such file or directory
`,
}
Compare(t, argsAndSolution)
}
func Compare(t *testing.T, argsAndSol map[string]string) {
for args, expected := range argsAndSol {
a := strings.Split(args, " ")
_, err := z01.MainOut("../rc", a...)
if err != nil && !EqualResult(expected, err.Error()) {
t.Errorf("./rc %s prints %q\n instead of %q\n", args, err.Error(), expected)
}
}
}
func EqualResult(sol, out string) bool {
// split
solSli := strings.Split(sol, "\n")
outSli := strings.Split(out, "\n")
// sort
sort.Sort(sort.StringSlice(solSli))
sort.Sort(sort.StringSlice(outSli))
// join
sol = strings.Join(solSli, " ")
out = strings.Join(outSli, " ")
// compare
return sol == out
}

10
go/tests/rc/tests/abc/main.go

@ -1,10 +0,0 @@
package main
import "github.com/01-edu/z01"
func main() {
for r := 'a'; r <= 'z'; r++ {
z01.PrintRune(r)
}
z01.PrintRune('\n')
}

214
go/tests/rc/tests/doopprog/main.go

@ -1,214 +0,0 @@
// package main
// import (
// "fmt"
// "os"
// "strconv"
// )
// level 3: doopprog
package main
import (
"fmt"
"os"
"strconv"
)
func main() {
args := os.Args
if len(args) != 4 {
return
}
if args[2] != "+" && args[2] != "-" && args[2] != "/" && args[2] != "*" && args[2] != "%" {
fmt.Println(0)
return
}
// arg1 := getArg(args[1])
arg1, err := strconv.Atoi(args[1])
if err != nil {
fmt.Println(0)
return
}
//fmt.Println("Arg1: ", arg1)
arg2, err := strconv.Atoi(args[3])
if err != nil {
fmt.Println(0)
return
}
//fmt.Println("Arg2: ", arg2)
if args[2] == "+" {
Add(int64(arg1), int64(arg2))
} else if args[2] == "-" {
Subs(int64(arg1), int64(arg2))
} else if args[2] == "/" {
Div(int64(arg1), int64(arg2))
} else if args[2] == "*" {
Multip(int64(arg1), int64(arg2))
} else if args[2] == "%" {
Modulo(int64(arg1), int64(arg2))
}
}
var max int64 = 9223372036854775807
var min int64 = -9223372036854775808
func Add(x, y int64) {
if x >= 0 && y < 0 {
Subs(x, -y)
return
} else if x < 0 && y >= 0 {
Subs(y, -x)
return
} else if x < 0 && y < 0 {
if x >= min-y {
fmt.Println(x + y)
return
} else {
fmt.Println(0)
return
}
}
if x <= max-y {
fmt.Println(x + y)
} else {
fmt.Println(0)
return
}
}
func Subs(x, y int64) {
if x >= 0 && y < 0 {
Add(x, -y)
return
} else if x < 0 && y >= 0 {
Add(-y, x)
return
} else if x < 0 && y < 0 {
Subs(-y, -x)
return
}
if x >= min+y {
fmt.Println(x - y)
return
} else {
fmt.Println(0)
return
}
}
func Multip(x, y int64) {
if x >= 0 && y >= 0 {
if x <= max/y {
fmt.Println(x * y)
return
} else {
fmt.Println(0)
return
}
} else if x < 0 && y < 0 {
if x > max/y {
fmt.Println(x * y)
return
} else {
fmt.Println(0)
return
}
} else {
if x >= min/y {
fmt.Println(x * y)
return
} else {
fmt.Println(0)
return
}
}
}
func Div(x, y int64) {
if y == 0 {
fmt.Println("No division by 0")
return
}
if x == min && y == -1 {
fmt.Println(0)
return
}
fmt.Println(x / y)
return
}
func Modulo(x, y int64) {
if y == 0 {
fmt.Println("No modulo by 0")
return
}
if y == min && x == -1 {
fmt.Println(0)
return
}
fmt.Println(x % y)
return
}
//9223372036854775807
// func main() {
// if len(os.Args) == 4 {
// var result int
// firstArg, err := strconv.Atoi(os.Args[1])
// if err != nil {
// fmt.Println(0)
// return
// }
// operator := os.Args[2]
// secondArg, err1 := strconv.Atoi(os.Args[3])
// if err1 != nil {
// fmt.Println(0)
// return
// }
// if secondArg == 0 && operator == "/" {
// fmt.Println("No division by 0")
// return
// } else if secondArg == 0 && operator == "%" {
// fmt.Println("No modulo by 0")
// return
// } else if operator == "+" {
// result = firstArg + secondArg
// if !((result > firstArg) == (secondArg > 0)) {
// fmt.Println(0)
// return
// }
// } else if operator == "-" {
// result = firstArg - secondArg
// if !((result < firstArg) == (secondArg > 0)) {
// fmt.Println(0)
// return
// }
// } else if operator == "/" {
// result = firstArg / secondArg
// } else if operator == "*" {
// result = firstArg * secondArg
// if firstArg != 0 && (result/firstArg != secondArg) {
// fmt.Println(0)
// return
// }
// } else if operator == "%" {
// result = firstArg % secondArg
// }
// fmt.Println(result)
// }
// }

86
go/tests/rc/tests/eightqueens.go

@ -1,86 +0,0 @@
package solutions
import (
"github.com/01-edu/z01"
)
const size = 8
// board is a chessboard composed of boolean squares, a true square means a queen is on it
// a false square means it is a free square
var board [size][size]bool
// goodDirection check that there is no queen on the segment that starts at (x, y)
// coordinates, points into the direction vector (vx, vy) and ends at the edge of the board
func goodDirection(x, y, vx, vy int) bool {
// x and y are still on board
for 0 <= x && x < size &&
0 <= y && y < size {
if board[x][y] {
// Not a good line : the square is already occupied
return false
}
x = x + vx // Move x in the right direction
y = y + vy // Move y in the right direction
}
// All clear
return true
}
// goodSquare makes all the necessary line checks for the queens movements
func goodSquare(x, y int) bool {
return goodDirection(x, y, +0, -1) &&
goodDirection(x, y, +1, -1) &&
goodDirection(x, y, +1, +0) &&
goodDirection(x, y, +1, +1) &&
goodDirection(x, y, +0, +1) &&
goodDirection(x, y, -1, +1) &&
goodDirection(x, y, -1, +0) &&
goodDirection(x, y, -1, -1)
}
func printQueens() {
x := 0
for x < size {
y := 0
for y < size {
if board[x][y] {
// We have found a queen, let's print her y
z01.PrintRune(rune(y) + '1')
}
y++
}
x++
}
z01.PrintRune('\n')
}
// tryX tries, for a given x (column) to find a y (row) so that the queen on (x, y) is a part
// of the solution to the problem
func tryX(x int) {
y := 0
for y < size {
if goodSquare(x, y) {
// Since the square is good for the queen, let's put one on it:
board[x][y] = true
if x == size-1 {
// x is the biggest possible x, it means that we just placed the last
// queen on the board, so the solution is complete and we can print it
printQueens()
} else {
// let's try to put another queen on the next empty x (column)
tryX(x + 1)
}
// remove the queen of the board, to try other y values
board[x][y] = false
}
y++
}
}
func EightQueens() {
// try the first column
tryX(0)
}

25
go/tests/rc/tests/example.go

@ -1,25 +0,0 @@
package solutions
import (
"fmt"
)
func thisIsAFunc() {
fmt.Println("This is a function")
}
var ThisToo = func(s string) {
fmt.Printf("ThisToo prints %s\n", s)
}
func aux(s string) int {
return 1
}
func youCanAlso(f func(string), s string) {
aux := func(s string) int {
return len(s)
}
aux(s)
f(s)
}

11
go/tests/rc/tests/grand-prix-go-burdeux/isnegative.go

@ -1,11 +0,0 @@
package piscine
import "github.com/01-edu/z01"
func IsNegative(nb int) {
if nb < 0 {
z01.PrintRune('T')
} else {
z01.PrintRune('F')
}
}

5
go/tests/rc/tests/moreThanOnePackage/func.go

@ -1,5 +0,0 @@
package piscine
func hello() {
println("Nothing here")
}

7
go/tests/rc/tests/moreThanOnePackage/main.go

@ -1,7 +0,0 @@
package main
import "fmt"
func main() {
fmt.Println("Hello world")
}

16
go/tests/rc/tests/nesting/main.go

@ -1,16 +0,0 @@
package main
import "fmt"
func myFunction(s string) bool {
return s > "m"
}
func main() {
// myFunction := func(s string) bool {
// return s < "m"
// }
fmt.Printf("Does %s comes before \"m\" %v\n", "name", myFunction("name"))
fmt.Printf("Does %s comes before \"m\" %v\n", "change", myFunction("change"))
}

29
go/tests/rc/tests/printalphabet/printalphabet.go

@ -1,29 +0,0 @@
package main
import (
"fmt"
"github.com/01-edu/z01"
)
func fillArray(a []rune) {
for i := 'a'; i <= 'z'; i++ {
a = append(a, i)
}
}
func main() {
a := []rune{'a', 'b', 'c', 'd', 'e', 'f'}
b := int('a')
for _, v := range a {
z01.PrintRune(v)
}
z01.PrintRune('\n')
}
func testingScope() {
defFun := func(s string) {
fmt.Println(s)
}
defFun("Hello")
}

10
go/tests/rc/tests/testLiteralRestriction/main.go

@ -1,10 +0,0 @@
package main
import "github.com/01-edu/z01"
func main() {
alphabet := "abcdefghijklmnopqrstuvwxyz\n"
for _, letter := range alphabet {
z01.PrintRune(letter)
}
}

19
go/tests/rc/tests/testingRepetitions/main.go

@ -1,19 +0,0 @@
package main
import "github.com/01-edu/z01"
func main() {
z01.PrintRune('a')
z01.PrintRune('b')
z01.PrintRune('c')
z01.PrintRune('d')
z01.PrintRune('e')
z01.PrintRune('f')
z01.PrintRune('g')
z01.PrintRune('h')
z01.PrintRune('i')
z01.PrintRune('j')
z01.PrintRune('k')
z01.PrintRune('l')
z01.PrintRune('m')
}

11
go/tests/rc/tests/testingSimpleFunc.go

@ -1,11 +0,0 @@
package solutions
import (
"regexp"
)
func SimpleFunc(str string) int {
re := regexp.MustCompile(`[a-zA-Z]`)
found := re.FindAll([]byte(str), -1)
return len(found)
}

12
go/tests/rc/tests/testingWrapping.go

@ -1,12 +0,0 @@
package solutions
import (
util "./util"
)
func Length(ss []string) int {
return util.LenWrapperU(ss)
}
// func NotUsed() {
// }

23
go/tests/rc/tests/util/util.go

@ -1,23 +0,0 @@
package util
import (
"fmt"
util2 "../utilDepth2"
)
func LenWrapperU(ss []string) int {
return util2.LenWrapper(ss)
}
func NotUsed() {
b := []string{"just", "something"}
a := len(b)
for i, v := range b {
if i == a-1 {
fmt.Println("Last element", v)
continue
}
fmt.Println("Element", v)
}
}

5
go/tests/rc/tests/utilDepth2/wrapper.go

@ -1,5 +0,0 @@
package util
func LenWrapper(ss []string) int {
return len(ss)
}
Loading…
Cancel
Save