mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-03-27 14:53:03 -04:00
One of the security patches released 2026-03-09 [fixed a vulnerability](d1c7b04d09) caused by a misapplication of Go `case` statements, where the implementation would have been correct if Go `case` statements automatically fall through to the next case block, but they do not. This PR adds a semgrep rule which detects any empty `case` statement and raises an error, in order to prevent this coding mistake in the future.
For example, code like this will now trigger a build error:
```go
switch setting.Protocol {
case setting.HTTPUnix:
case setting.FCGI:
case setting.FCGIUnix:
default:
defaultLocalURL := string(setting.Protocol) + "://"
}
```
Example error:
```
cmd/web.go
❯❯❱ semgrep.config.forgejo-switch-empty-case
switch has a case block with no content. This is treated as "break" by Go, but developers may
confuse it for "fallthrough". To fix this error, disambiguate by using "break" or
"fallthrough".
279┆ switch setting.Protocol {
280┆ case setting.HTTPUnix:
281┆ case setting.FCGI:
282┆ case setting.FCGIUnix:
283┆ default:
284┆ defaultLocalURL := string(setting.Protocol) + "://"
285┆ if setting.HTTPAddr == "0.0.0.0" {
286┆ defaultLocalURL += "localhost"
287┆ } else {
288┆ defaultLocalURL += setting.HTTPAddr
```
As described in the error output, this error can be fixed by explicitly listing `break` (the real Go behaviour, to do nothing in the block), or by listing `fallthrough` (if the intent was to fall through).
All existing code triggering this detection has been changed to `break` (or, rarely, irrelevant cases have been removed), which should maintain the same code functionality. While performing this fixup, a light analysis was performed on each case and they *appeared* correct, but with ~65 cases I haven't gone into extreme depth.
Tests are present for the semgrep rule in `.semgrep/tests/go.go`.
## Checklist
The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).
### Documentation
- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.
### Release notes
- [ ] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change.
- [x] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change.
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11593
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Co-authored-by: Mathieu Fenniak <mathieu@fenniak.net>
Co-committed-by: Mathieu Fenniak <mathieu@fenniak.net>
339 lines
9.5 KiB
Go
339 lines
9.5 KiB
Go
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package gitgraph
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
)
|
|
|
|
// Parser represents a git graph parser. It is stateful containing the previous
|
|
// glyphs, detected flows and color assignments.
|
|
type Parser struct {
|
|
glyphs []byte
|
|
oldGlyphs []byte
|
|
flows []int64
|
|
oldFlows []int64
|
|
maxFlow int64
|
|
colors []int
|
|
oldColors []int
|
|
availableColors []int
|
|
nextAvailable int
|
|
firstInUse int
|
|
firstAvailable int
|
|
maxAllowedColors int
|
|
}
|
|
|
|
// Reset resets the internal parser state.
|
|
func (parser *Parser) Reset() {
|
|
parser.glyphs = parser.glyphs[0:0]
|
|
parser.oldGlyphs = parser.oldGlyphs[0:0]
|
|
parser.flows = parser.flows[0:0]
|
|
parser.oldFlows = parser.oldFlows[0:0]
|
|
parser.maxFlow = 0
|
|
parser.colors = parser.colors[0:0]
|
|
parser.oldColors = parser.oldColors[0:0]
|
|
parser.availableColors = parser.availableColors[0:0]
|
|
parser.availableColors = append(parser.availableColors, 1, 2)
|
|
parser.nextAvailable = 0
|
|
parser.firstInUse = -1
|
|
parser.firstAvailable = 0
|
|
parser.maxAllowedColors = 0
|
|
}
|
|
|
|
// AddLineToGraph adds the line as a row to the graph
|
|
func (parser *Parser) AddLineToGraph(graph *Graph, row int, line []byte) error {
|
|
idx := bytes.Index(line, []byte("DATA:"))
|
|
if idx < 0 {
|
|
parser.ParseGlyphs(line)
|
|
} else {
|
|
parser.ParseGlyphs(line[:idx])
|
|
}
|
|
|
|
var err error
|
|
commitDone := false
|
|
|
|
for column, glyph := range parser.glyphs {
|
|
if glyph == ' ' {
|
|
continue
|
|
}
|
|
|
|
flowID := parser.flows[column]
|
|
|
|
graph.AddGlyph(row, column, flowID, parser.colors[column], glyph)
|
|
|
|
if glyph == '*' {
|
|
if commitDone {
|
|
if err != nil {
|
|
err = fmt.Errorf("double commit on line %d: %s. %w", row, string(line), err)
|
|
} else {
|
|
err = fmt.Errorf("double commit on line %d: %s", row, string(line))
|
|
}
|
|
}
|
|
commitDone = true
|
|
if idx < 0 {
|
|
if err != nil {
|
|
err = fmt.Errorf("missing data section on line %d with commit: %s. %w", row, string(line), err)
|
|
} else {
|
|
err = fmt.Errorf("missing data section on line %d with commit: %s", row, string(line))
|
|
}
|
|
continue
|
|
}
|
|
if column < len(parser.oldGlyphs) && parser.oldGlyphs[column] == '|' {
|
|
graph.continuationAbove[[2]int{row, column}] = true
|
|
}
|
|
err2 := graph.AddCommit(row, column, flowID, line[idx+5:])
|
|
if err != nil && err2 != nil {
|
|
err = fmt.Errorf("%v %w", err2, err)
|
|
continue
|
|
} else if err2 != nil {
|
|
err = err2
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
if !commitDone {
|
|
graph.Commits = append(graph.Commits, RelationCommit)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (parser *Parser) releaseUnusedColors() {
|
|
if parser.firstInUse > -1 {
|
|
// Here we step through the old colors, searching for them in the
|
|
// "in-use" section of availableColors (that is, the colors between
|
|
// firstInUse and firstAvailable)
|
|
// Ensure that the benchmarks are not worsened with proposed changes
|
|
stepstaken := 0
|
|
position := parser.firstInUse
|
|
for _, color := range parser.oldColors {
|
|
if color == 0 {
|
|
continue
|
|
}
|
|
found := false
|
|
i := position
|
|
for j := stepstaken; i != parser.firstAvailable && j < len(parser.availableColors); j++ {
|
|
colorToCheck := parser.availableColors[i]
|
|
if colorToCheck == color {
|
|
found = true
|
|
break
|
|
}
|
|
i = (i + 1) % len(parser.availableColors)
|
|
}
|
|
if !found {
|
|
// Duplicate color
|
|
continue
|
|
}
|
|
// Swap them around
|
|
parser.availableColors[position], parser.availableColors[i] = parser.availableColors[i], parser.availableColors[position]
|
|
stepstaken++
|
|
position = (parser.firstInUse + stepstaken) % len(parser.availableColors)
|
|
if position == parser.firstAvailable || stepstaken == len(parser.availableColors) {
|
|
break
|
|
}
|
|
}
|
|
if stepstaken == len(parser.availableColors) {
|
|
parser.firstAvailable = -1
|
|
} else {
|
|
parser.firstAvailable = position
|
|
if parser.nextAvailable == -1 {
|
|
parser.nextAvailable = parser.firstAvailable
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ParseGlyphs parses the provided glyphs and sets the internal state
|
|
func (parser *Parser) ParseGlyphs(glyphs []byte) {
|
|
// Clean state for parsing this row
|
|
parser.glyphs, parser.oldGlyphs = parser.oldGlyphs, parser.glyphs
|
|
parser.glyphs = parser.glyphs[0:0]
|
|
parser.flows, parser.oldFlows = parser.oldFlows, parser.flows
|
|
parser.flows = parser.flows[0:0]
|
|
parser.colors, parser.oldColors = parser.oldColors, parser.colors
|
|
|
|
// Ensure we have enough flows and colors
|
|
parser.colors = parser.colors[0:0]
|
|
for range glyphs {
|
|
parser.flows = append(parser.flows, 0)
|
|
parser.colors = append(parser.colors, 0)
|
|
}
|
|
|
|
// Copy the provided glyphs in to state.glyphs for safekeeping
|
|
parser.glyphs = append(parser.glyphs, glyphs...)
|
|
|
|
// release unused colors
|
|
parser.releaseUnusedColors()
|
|
|
|
for i := len(glyphs) - 1; i >= 0; i-- {
|
|
glyph := glyphs[i]
|
|
switch glyph {
|
|
case '|':
|
|
fallthrough
|
|
case '*':
|
|
parser.setUpFlow(i)
|
|
case '/':
|
|
parser.setOutFlow(i)
|
|
case '\\':
|
|
parser.setInFlow(i)
|
|
case '_':
|
|
parser.setRightFlow(i)
|
|
case '.':
|
|
fallthrough
|
|
case '-':
|
|
parser.setLeftFlow(i)
|
|
case ' ':
|
|
break
|
|
default:
|
|
parser.newFlow(i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (parser *Parser) takePreviousFlow(i, j int) {
|
|
if j < len(parser.oldFlows) && parser.oldFlows[j] > 0 {
|
|
parser.flows[i] = parser.oldFlows[j]
|
|
parser.oldFlows[j] = 0
|
|
parser.colors[i] = parser.oldColors[j]
|
|
parser.oldColors[j] = 0
|
|
} else {
|
|
parser.newFlow(i)
|
|
}
|
|
}
|
|
|
|
func (parser *Parser) takeCurrentFlow(i, j int) {
|
|
if j < len(parser.flows) && parser.flows[j] > 0 {
|
|
parser.flows[i] = parser.flows[j]
|
|
parser.colors[i] = parser.colors[j]
|
|
} else {
|
|
parser.newFlow(i)
|
|
}
|
|
}
|
|
|
|
func (parser *Parser) newFlow(i int) {
|
|
parser.maxFlow++
|
|
parser.flows[i] = parser.maxFlow
|
|
|
|
// Now give this flow a color
|
|
if parser.nextAvailable == -1 {
|
|
next := len(parser.availableColors)
|
|
if parser.maxAllowedColors < 1 || next < parser.maxAllowedColors {
|
|
parser.nextAvailable = next
|
|
parser.firstAvailable = next
|
|
parser.availableColors = append(parser.availableColors, next+1)
|
|
}
|
|
}
|
|
parser.colors[i] = parser.availableColors[parser.nextAvailable]
|
|
if parser.firstInUse == -1 {
|
|
parser.firstInUse = parser.nextAvailable
|
|
}
|
|
parser.availableColors[parser.firstAvailable], parser.availableColors[parser.nextAvailable] = parser.availableColors[parser.nextAvailable], parser.availableColors[parser.firstAvailable]
|
|
|
|
parser.nextAvailable = (parser.nextAvailable + 1) % len(parser.availableColors)
|
|
parser.firstAvailable = (parser.firstAvailable + 1) % len(parser.availableColors)
|
|
|
|
if parser.nextAvailable == parser.firstInUse {
|
|
parser.nextAvailable = parser.firstAvailable
|
|
}
|
|
if parser.nextAvailable == parser.firstInUse {
|
|
parser.nextAvailable = -1
|
|
parser.firstAvailable = -1
|
|
}
|
|
}
|
|
|
|
// setUpFlow handles '|' or '*'
|
|
func (parser *Parser) setUpFlow(i int) {
|
|
// In preference order:
|
|
//
|
|
// Previous Row: '\? ' ' |' ' /'
|
|
// Current Row: ' | ' ' |' ' | '
|
|
if i > 0 && i-1 < len(parser.oldGlyphs) && parser.oldGlyphs[i-1] == '\\' {
|
|
parser.takePreviousFlow(i, i-1)
|
|
} else if i < len(parser.oldGlyphs) && (parser.oldGlyphs[i] == '|' || parser.oldGlyphs[i] == '*') {
|
|
parser.takePreviousFlow(i, i)
|
|
} else if i+1 < len(parser.oldGlyphs) && parser.oldGlyphs[i+1] == '/' {
|
|
parser.takePreviousFlow(i, i+1)
|
|
} else {
|
|
parser.newFlow(i)
|
|
}
|
|
}
|
|
|
|
// setOutFlow handles '/'
|
|
func (parser *Parser) setOutFlow(i int) {
|
|
// In preference order:
|
|
//
|
|
// Previous Row: ' |/' ' |_' ' |' ' /' ' _' '\'
|
|
// Current Row: '/| ' '/| ' '/ ' '/ ' '/ ' '/'
|
|
if i+2 < len(parser.oldGlyphs) &&
|
|
(parser.oldGlyphs[i+1] == '|' || parser.oldGlyphs[i+1] == '*') &&
|
|
(parser.oldGlyphs[i+2] == '/' || parser.oldGlyphs[i+2] == '_') &&
|
|
i+1 < len(parser.glyphs) &&
|
|
(parser.glyphs[i+1] == '|' || parser.glyphs[i+1] == '*') {
|
|
parser.takePreviousFlow(i, i+2)
|
|
} else if i+1 < len(parser.oldGlyphs) &&
|
|
(parser.oldGlyphs[i+1] == '|' || parser.oldGlyphs[i+1] == '*' ||
|
|
parser.oldGlyphs[i+1] == '/' || parser.oldGlyphs[i+1] == '_') {
|
|
parser.takePreviousFlow(i, i+1)
|
|
if parser.oldGlyphs[i+1] == '/' {
|
|
parser.glyphs[i] = '|'
|
|
}
|
|
} else if i < len(parser.oldGlyphs) && parser.oldGlyphs[i] == '\\' {
|
|
parser.takePreviousFlow(i, i)
|
|
} else {
|
|
parser.newFlow(i)
|
|
}
|
|
}
|
|
|
|
// setInFlow handles '\'
|
|
func (parser *Parser) setInFlow(i int) {
|
|
// In preference order:
|
|
//
|
|
// Previous Row: '| ' '-. ' '| ' '\ ' '/' '---'
|
|
// Current Row: '|\' ' \' ' \' ' \' '\' ' \ '
|
|
if i > 0 && i-1 < len(parser.oldGlyphs) &&
|
|
(parser.oldGlyphs[i-1] == '|' || parser.oldGlyphs[i-1] == '*') &&
|
|
(parser.glyphs[i-1] == '|' || parser.glyphs[i-1] == '*') {
|
|
parser.newFlow(i)
|
|
} else if i > 0 && i-1 < len(parser.oldGlyphs) &&
|
|
(parser.oldGlyphs[i-1] == '|' || parser.oldGlyphs[i-1] == '*' ||
|
|
parser.oldGlyphs[i-1] == '.' || parser.oldGlyphs[i-1] == '\\') {
|
|
parser.takePreviousFlow(i, i-1)
|
|
if parser.oldGlyphs[i-1] == '\\' {
|
|
parser.glyphs[i] = '|'
|
|
}
|
|
} else if i < len(parser.oldGlyphs) && parser.oldGlyphs[i] == '/' {
|
|
parser.takePreviousFlow(i, i)
|
|
} else {
|
|
parser.newFlow(i)
|
|
}
|
|
}
|
|
|
|
// setRightFlow handles '_'
|
|
func (parser *Parser) setRightFlow(i int) {
|
|
// In preference order:
|
|
//
|
|
// Current Row: '__' '_/' '_|_' '_|/'
|
|
if i+1 < len(parser.glyphs) &&
|
|
(parser.glyphs[i+1] == '_' || parser.glyphs[i+1] == '/') {
|
|
parser.takeCurrentFlow(i, i+1)
|
|
} else if i+2 < len(parser.glyphs) &&
|
|
(parser.glyphs[i+1] == '|' || parser.glyphs[i+1] == '*') &&
|
|
(parser.glyphs[i+2] == '_' || parser.glyphs[i+2] == '/') {
|
|
parser.takeCurrentFlow(i, i+2)
|
|
} else {
|
|
parser.newFlow(i)
|
|
}
|
|
}
|
|
|
|
// setLeftFlow handles '----.'
|
|
func (parser *Parser) setLeftFlow(i int) {
|
|
if parser.glyphs[i] == '.' {
|
|
parser.newFlow(i)
|
|
} else if i+1 < len(parser.glyphs) &&
|
|
(parser.glyphs[i+1] == '-' || parser.glyphs[i+1] == '.') {
|
|
parser.takeCurrentFlow(i, i+1)
|
|
} else {
|
|
parser.newFlow(i)
|
|
}
|
|
}
|