diff --git a/internal/arduino/builder/internal/preprocessor/gcc.go b/internal/arduino/builder/internal/preprocessor/gcc.go index cbf156dfae6..f8d6100d10d 100644 --- a/internal/arduino/builder/internal/preprocessor/gcc.go +++ b/internal/arduino/builder/internal/preprocessor/gcc.go @@ -16,6 +16,7 @@ package preprocessor import ( + "bytes" "context" "errors" "fmt" @@ -77,10 +78,42 @@ func GCC( if err != nil { return Result{}, err } - stdout, stderr, err := proc.RunAndCaptureOutput(ctx) - // Append gcc arguments to stdout - stdout = append([]byte(fmt.Sprintln(strings.Join(args, " "))), stdout...) + stdout := bytes.NewBuffer(nil) + stderr := bytes.NewBuffer(nil) - return Result{args: proc.GetArgs(), stdout: stdout, stderr: stderr}, err + ctx, cancel := context.WithCancel(ctx) + defer cancel() + count := 0 + stderrLimited := writerFunc(func(p []byte) (int, error) { + // Limit the size of the stderr buffer to 100KB + n, err := stderr.Write(p) + count += n + if count > 100*1024 { + fmt.Fprintln(stderr, i18n.Tr("Compiler error output has been truncated.")) + cancel() + } + return n, err + }) + + proc.RedirectStdoutTo(stdout) + proc.RedirectStderrTo(stderrLimited) + + // Append gcc arguments to stdout before running the command + fmt.Fprintln(stdout, strings.Join(args, " ")) + + if err := proc.Start(); err != nil { + return Result{}, err + } + + // Wait for the process to finish + err = proc.WaitWithinContext(ctx) + + return Result{args: proc.GetArgs(), stdout: stdout.Bytes(), stderr: stderr.Bytes()}, err +} + +type writerFunc func(p []byte) (n int, err error) + +func (f writerFunc) Write(p []byte) (n int, err error) { + return f(p) } diff --git a/internal/integrationtest/arduino-cli.go b/internal/integrationtest/arduino-cli.go index f035e792f55..4b2b4850450 100644 --- a/internal/integrationtest/arduino-cli.go +++ b/internal/integrationtest/arduino-cli.go @@ -22,6 +22,7 @@ import ( "errors" "fmt" "io" + "maps" "os" "runtime" "strings" @@ -190,12 +191,16 @@ func (cli *ArduinoCLI) Run(args ...string) ([]byte, []byte, error) { return cli.RunWithCustomEnv(cli.cliEnvVars, args...) } +// RunWithContext executes the given arduino-cli command with the given context and returns the output. +// If the context is canceled, the command is killed. +func (cli *ArduinoCLI) RunWithContext(ctx context.Context, args ...string) ([]byte, []byte, error) { + return cli.RunWithCustomEnvContext(ctx, cli.cliEnvVars, args...) +} + // GetDefaultEnv returns a copy of the default execution env used with the Run method. func (cli *ArduinoCLI) GetDefaultEnv() map[string]string { res := map[string]string{} - for k, v := range cli.cliEnvVars { - res[k] = v - } + maps.Copy(res, cli.cliEnvVars) return res } @@ -324,8 +329,13 @@ func (cli *ArduinoCLI) InstallMockedAvrdude(t *testing.T) { // RunWithCustomEnv executes the given arduino-cli command with the given custom env and returns the output. func (cli *ArduinoCLI) RunWithCustomEnv(env map[string]string, args ...string) ([]byte, []byte, error) { + return cli.RunWithCustomEnvContext(context.Background(), env, args...) +} + +// RunWithCustomEnv executes the given arduino-cli command with the given custom env and returns the output. +func (cli *ArduinoCLI) RunWithCustomEnvContext(ctx context.Context, env map[string]string, args ...string) ([]byte, []byte, error) { var stdoutBuf, stderrBuf bytes.Buffer - err := cli.run(&stdoutBuf, &stderrBuf, nil, env, args...) + err := cli.run(ctx, &stdoutBuf, &stderrBuf, nil, env, args...) errBuf := stderrBuf.Bytes() cli.t.NotContains(string(errBuf), "panic: runtime error:", "arduino-cli panicked") @@ -336,7 +346,7 @@ func (cli *ArduinoCLI) RunWithCustomEnv(env map[string]string, args ...string) ( // RunWithCustomInput executes the given arduino-cli command pushing the given input stream and returns the output. func (cli *ArduinoCLI) RunWithCustomInput(in io.Reader, args ...string) ([]byte, []byte, error) { var stdoutBuf, stderrBuf bytes.Buffer - err := cli.run(&stdoutBuf, &stderrBuf, in, cli.cliEnvVars, args...) + err := cli.run(context.Background(), &stdoutBuf, &stderrBuf, in, cli.cliEnvVars, args...) errBuf := stderrBuf.Bytes() cli.t.NotContains(string(errBuf), "panic: runtime error:", "arduino-cli panicked") @@ -344,7 +354,7 @@ func (cli *ArduinoCLI) RunWithCustomInput(in io.Reader, args ...string) ([]byte, return stdoutBuf.Bytes(), errBuf, err } -func (cli *ArduinoCLI) run(stdoutBuff, stderrBuff io.Writer, stdinBuff io.Reader, env map[string]string, args ...string) error { +func (cli *ArduinoCLI) run(ctx context.Context, stdoutBuff, stderrBuff io.Writer, stdinBuff io.Reader, env map[string]string, args ...string) error { if cli.cliConfigPath != nil { args = append([]string{"--config-file", cli.cliConfigPath.String()}, args...) } @@ -402,8 +412,8 @@ func (cli *ArduinoCLI) run(stdoutBuff, stderrBuff io.Writer, stdinBuff io.Reader } }() } + cliErr := cliProc.WaitWithinContext(ctx) wg.Wait() - cliErr := cliProc.Wait() fmt.Fprintln(terminalOut, color.HiBlackString("<<< Run completed (err = %v)", cliErr)) return cliErr diff --git a/internal/integrationtest/compile_5/recusive_include_test.go b/internal/integrationtest/compile_5/recusive_include_test.go new file mode 100644 index 00000000000..59a3c421c7f --- /dev/null +++ b/internal/integrationtest/compile_5/recusive_include_test.go @@ -0,0 +1,44 @@ +// This file is part of arduino-cli. +// +// Copyright 2025 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of arduino-cli. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to +// modify or otherwise use the software for commercial activities involving the +// Arduino software without disclosing the source code of your own applications. +// To purchase a commercial license, send an email to license@arduino.cc. + +package compile_test + +import ( + "context" + "testing" + "time" + + "github.com/arduino/arduino-cli/internal/integrationtest" + "github.com/arduino/go-paths-helper" + "github.com/stretchr/testify/require" +) + +func TestCompileWithInfiniteMultipleIncludeRecursion(t *testing.T) { + env, cli := integrationtest.CreateArduinoCLIWithEnvironment(t) + t.Cleanup(env.CleanUp) + + // Install Arduino AVR Boards + _, _, err := cli.Run("core", "install", "arduino:avr@1.8.6") + require.NoError(t, err) + + sketch, err := paths.New("testdata", "SketchWithRecursiveIncludes").Abs() + require.NoError(t, err) + + // Time-limited test to prevent OOM + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) + t.Cleanup(cancel) + _, _, _ = cli.RunWithContext(ctx, "compile", "-b", "arduino:avr:uno", sketch.String()) + require.NotErrorIs(t, ctx.Err(), context.DeadlineExceeded, "compilation should not hang") +} diff --git a/internal/integrationtest/compile_5/testdata/SketchWithRecursiveIncludes/SketchWithRecursiveIncludes.ino b/internal/integrationtest/compile_5/testdata/SketchWithRecursiveIncludes/SketchWithRecursiveIncludes.ino new file mode 100644 index 00000000000..2243de1baf9 --- /dev/null +++ b/internal/integrationtest/compile_5/testdata/SketchWithRecursiveIncludes/SketchWithRecursiveIncludes.ino @@ -0,0 +1 @@ +#include "a.h" diff --git a/internal/integrationtest/compile_5/testdata/SketchWithRecursiveIncludes/a.h b/internal/integrationtest/compile_5/testdata/SketchWithRecursiveIncludes/a.h new file mode 100644 index 00000000000..7c626e5beb0 --- /dev/null +++ b/internal/integrationtest/compile_5/testdata/SketchWithRecursiveIncludes/a.h @@ -0,0 +1,2 @@ +#include "a.h" +#include "a.h" pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy