Leyendo Stacktraces

Ha ocurrido un error inesperado.

Cuando el código de su robot encuentra un error inesperado, verá este mensaje aparecer en alguna salida de la consola (Driver Station o RioLog). Probablemente también notará que su robot se detiene abruptamente, o posiblemente nunca se mueva. Estos errores inesperados se denominan unhandled exceptions.

Cuando se produce una excepción no controlada, significa que su código tiene uno o más errores que deben corregirse.

Este artículo explorará algunas de las herramientas y técnicas involucradas en encontrar y corregir esos errores.

¿Qué es un «Stack Trace»?

El mensaje unexpected error has occurred es una señal de que se ha impreso un stack trace.

In Java and C++, the call stack data structure is used to store information about which function or method is currently being executed.

Un stack trace imprime información acerca de lo que estaba sucediendo en el programa cuando la excepción sin manejar ocurrió. Esto apunto a las lineas de código que estaban ejecutándose justo antes de que el problema pasara. Mientras que no siempre apunta a los puntos exactos que son la causa principal de su problema, normalmente es el mejor lugar para empezar a buscar.

¿Qué es una «Unhandled Exception»?

Un error irrecuperable es cualquier condición que se manifieste donde el procesador no puede continuar ejecutando código. Casi siempre implica que, a pesar de que el código haya sido compilado y se está ejecutando, no hace sentido que la ejecución continúe.

En casi todos los casos, la raíz de la excepción no manejada es que el código no está correctamente implementado. Casi nunca implica que cualquier parte del hardware no esté funcionando.

¿Cómo resuelvo mi problema?

Leyendo el Stack Trace

Para empezar, busque arriba de unexpected error has occurred para el stack trace.

En Java, debería verse como algo así:

Error at frc.robot.Robot.robotInit(Robot.java:24): Unhandled exception: java.lang.NullPointerException
         at frc.robot.Robot.robotInit(Robot.java:24)
         at edu.wpi.first.wpilibj.TimedRobot.startCompetition(TimedRobot.java:94)
         at edu.wpi.first.wpilibj.RobotBase.runRobot(RobotBase.java:335)
         at edu.wpi.first.wpilibj.RobotBase.lambda$startRobot$0(RobotBase.java:387)
         at java.base/java.lang.Thread.run(Thread.java:834)

Hay algunas cosas importantes que entender de aquí:

  • Hubo un Error

  • El error se provocó por una Excepción no manejada

  • La excepción fue un java.lang.NullPointerException

  • El error ocurrió mientras se ejecutaba la línea 24 adentro de Robot.java

    • robotInit era el nombre de el método que se estaba ejecutando cuando el error sucedió.

  • robotInit es una función en el paquete de frc.robot.Robot (AKA, el código de su equipo)

  • robotInit was called from a number of functions from the edu.wpi.first.wpilibj package (AKA, the WPILib libraries)

The list of indented lines starting with the word at represent the state of the stack at the time the error happened. Each line represents one method, which was called by the method right below it.

For example, If the error happened deep inside your codebase, you might see more entries on the stack:

Error at frc.robot.Robot.buggyMethod(TooManyBugs.java:1138): Unhandled exception: java.lang.NullPointerException
         at frc.robot.Robot.buggyMethod(TooManyBugs.java:1138)
         at frc.robot.Robot.barInit(Bar.java:21)
         at frc.robot.Robot.fooInit(Foo.java:34)
         at frc.robot.Robot.robotInit(Robot.java:24)
         at edu.wpi.first.wpilibj.TimedRobot.startCompetition(TimedRobot.java:94)
         at edu.wpi.first.wpilibj.RobotBase.runRobot(RobotBase.java:335)
         at edu.wpi.first.wpilibj.RobotBase.lambda$startRobot$0(RobotBase.java:387)
         at java.base/java.lang.Thread.run(Thread.java:834)

In this case: robotInit called fooInit, which in turn called barInit, which in turn called buggyMethod. Then, during the execution of buggyMethod, the NullPointerException occurred.

Java will usually produce stack traces automatically when programs run into issues. C++ will require more digging to extract the same info. Usually, a single-step debugger will need to be hooked up to the executing robot program.

Stack traces can be found in the debugger tab of VS Code:

VS Code Stack Trace location

Stack traces in C++ will generally look similar to this:

Stack Trace associated with a null-related error

Hay algunas cosas importantes que entender de aquí:

  • The code execution is currently paused.

  • The reason it paused was one thread having an exception

  • The error happened while running line 20 inside of Robot.cpp

    • RobotInit was the name of the method executing when the error happened.

  • RobotInit is a function in the Robot:: namespace (AKA, your team’s code)

  • RobotInit was called from a number of functions from the frc:: namespace (AKA, the WPILib libraries)

This «call stack» window represents the state of the stack at the time the error happened. Each line represents one method, which was called by the method right below it.

The examples in this page assume you are running code examples in simulation, with the debugger connected and watching for unexpected errors. Similar techniques should apply while running on a real robot.

Perform Code Analysis

Once you’ve found the stack trace, and found the lines of code which are triggering the unhandled exception, you can start the process of determining root cause.

Often, just looking in (or near) the problematic location in code will be fruitful. You may notice things you forgot, or lines which don’t match an example you’re referencing.

Nota

Developers who have lots of experience working with code will often have more luck looking at code than newer folks. That’s ok, don’t be discouraged! The experience will come with time.

A key strategy for analyzing code is to ask the following questions:

  • When was the last time the code «worked» (I.e., didn’t have this particular error)?

  • What has changed in the code between the last working version, and now?

Frequent testing and careful code changes help make this particular strategy more effective.

Run the Single Step Debugger

Sometimes, just looking at code isn’t enough to spot the issue. The single-step debugger is a great option in this case - it allows you to inspect the series of events leading up to the unhandled exception.

Search for More Information

Google is a phenomenal resource for understanding the root cause of errors. Searches involving the programming language and the name of the exception will often yield good results on more explanations for what the error means, how it comes about, and potential fixes.

Seeking Outside Help

If all else fails, you can seek out advice and help from others (both in-person and online). When working with folks who aren’t familiar with your codebase, it’s very important to provide the following information:

  • Access to your source code, (EX: on github.com)

  • The full text of the error, including the full stack trace.

Common Examples & Patterns

There are a number of common issues which result in runtime exceptions.

Null Pointers and References

Both C++ and Java have the concept of «null» - they use it to indicate something which has not yet been initialized, and does not refer to anything meaningful.

Manipulating a «null» reference will produce a runtime error.

For example, consider the following code:

19PWMSparkMax armMotorCtrl;
20
21@Override
22public void robotInit() {
23      armMotorCtrl.setInverted(true);
24}
17class Robot : public frc::TimedRobot {
18   public:
19      void RobotInit() override {
20         motorRef->SetInverted(false);
21      }
22
23   private:
24      frc::PWMVictorSPX m_armMotor{0};
25      frc::PWMVictorSPX* motorRef;
26};

When run, you’ll see output that looks like this:

********** Robot program starting **********
Error at frc.robot.Robot.robotInit(Robot.java:23): Unhandled exception: java.lang.NullPointerException
        at frc.robot.Robot.robotInit(Robot.java:23)
        at edu.wpi.first.wpilibj.TimedRobot.startCompetition(TimedRobot.java:107)
        at edu.wpi.first.wpilibj.RobotBase.runRobot(RobotBase.java:373)
        at edu.wpi.first.wpilibj.RobotBase.startRobot(RobotBase.java:463)
        at frc.robot.Main.main(Main.java:23)

Warning at edu.wpi.first.wpilibj.RobotBase.runRobot(RobotBase.java:388): The robot program quit unexpectedly. This is usually due to a code error.
  The above stacktrace can help determine where the error occurred.
  See https://wpilib.org/stacktrace for more information.
Error at edu.wpi.first.wpilibj.RobotBase.runRobot(RobotBase.java:395): The startCompetition() method (or methods called by it) should have handled the exception above.

Reading the stack trace, you can see that the issue happened inside of the robotInit() function, on line 23, and the exception involved «Null Pointer».

By going to line 23, you can see there is only one thing which could be null - armMotorCtrl. Looking further up, you can see that the armMotorCtrl object is declared, but never instantiated.

Alternatively, you can step through lines of code with the single step debugger, and stop when you hit line 23. Inspecting the armMotorCtrl object at that point would show that it is null.

Exception has occurred: W32/0xc0000005
Unhandled exception thrown: read access violation.
this->motorRef was nullptr.

In Simulation, this will show up in a debugger window that points to line 20 in the above buggy code.

You can view the full stack trace by clicking the debugger tab in VS Code:

Stack Trace associated with a null-related error

The error is specific - our member variable motorRef was declared, but never assigned a value. Therefore, when we attempt to use it to call a method using the -> operator, the exception occurs.

The exception states its type was nullptr.

Fixing Null Object Issues

Generally, you will want to ensure each reference has been initialized before using it. In this case, there is a missing line of code to instantiate the armMotorCtrl before calling the setInverted() method.

A functional implementation could look like this:

19PWMSparkMax armMotorCtrl;
20
21@Override
22public void robotInit() {
23      armMotorCtrl = new PWMSparkMax(0);
24      armMotorCtrl.setInverted(true);
25}
17class Robot : public frc::TimedRobot {
18   public:
19      void RobotInit() override {
20         motorRef = &m_armMotor;
21         motorRef->SetInverted(false);
22      }
23
24   private:
25      frc::PWMVictorSPX m_armMotor{0};
26      frc::PWMVictorSPX* motorRef;
27};

Divide by Zero

It is not generally possible to divide an integer by zero, and expect reasonable results. Most processors (including the roboRIO) will raise an Unhandled Exception.

For example, consider the following code:

18int armLengthRatio;
19int elbowToWrist_in = 39;
20int shoulderToElbow_in = 0; //TODO
21
22@Override
23public void robotInit() {
24   armLengthRatio = elbowToWrist_in / shoulderToElbow_in;
25}
17class Robot : public frc::TimedRobot {
18   public:
19   void RobotInit() override {
20      armLengthRatio = elbowToWrist_in / shoulderToElbow_in;
21   }
22
23   private:
24      int armLengthRatio;
25      int elbowToWrist_in = 39;
26      int shoulderToElbow_in = 0; //TODO
27
28};

When run, you’ll see output that looks like this:

********** Robot program starting **********
Error at frc.robot.Robot.robotInit(Robot.java:24): Unhandled exception: java.lang.ArithmeticException: / by zero
        at frc.robot.Robot.robotInit(Robot.java:24)
        at edu.wpi.first.wpilibj.TimedRobot.startCompetition(TimedRobot.java:107)
        at edu.wpi.first.wpilibj.RobotBase.runRobot(RobotBase.java:373)
        at edu.wpi.first.wpilibj.RobotBase.startRobot(RobotBase.java:463)
        at frc.robot.Main.main(Main.java:23)

Warning at edu.wpi.first.wpilibj.RobotBase.runRobot(RobotBase.java:388): The robot program quit unexpectedly. This is usually due to a code error.
  The above stacktrace can help determine where the error occurred.
  See https://wpilib.org/stacktrace for more information.
Error at edu.wpi.first.wpilibj.RobotBase.runRobot(RobotBase.java:395): The startCompetition() method (or methods called by it) should have handled the exception above.

Looking at the stack trace, we can see a java.lang.ArithmeticException: / by zero exception has occurred on line 24. If you look at the two variables which are used on the right-hand side of the = operator, you might notice one of them has been initialized to zero. Looks like someone forgot to update it! Furthermore, the zero-value variable is used in the denominator of a division operation. Hence, the divide by zero error happens.

Alternatively, by running the single-step debugger and stopping on line 24, you could inspect the value of all variables to discover shoulderToElbow_in has a value of 0.

Exception has occurred: W32/0xc0000094
Unhandled exception at 0x00007FF71B223CD6 in frcUserProgram.exe: 0xC0000094: Integer division by zero.

In Simulation, this will show up in a debugger window that points to line 20 in the above buggy code.

You can view the full stack trace by clicking the debugger tab in VS Code:

Stack Trace associated with a divide by zero error

Looking at the message, we see the error is described as Integer division by zero. If you look at the two variables which are used on the right-hand side of the = operator on line 20, you might notice one of them has been initialized to zero. Looks like someone forgot to update it! Furthermore, the zero-value variable is used in the denominator of a division operation. Hence, the divide by zero error happens.

Note that the error messages might look slightly different on the roboRIO, or on an operating system other than windows.

Fixing Divide By Zero Issues

Divide By Zero issues can be fixed in a number of ways. It’s important to start by thinking about what a zero in the denominator of your calculation means. Is it plausible? Why did it happen in the particular case you saw?

Sometimes, you just need to use a different number other than 0.

A functional implementation could look like this:

18int armLengthRatio;
19int elbowToWrist_in = 39;
20int shoulderToElbow_in = 3;
21
22@Override
23public void robotInit() {
24
25   armLengthRatio = elbowToWrist_in / shoulderToElbow_in;
26
27}
17class Robot : public frc::TimedRobot {
18   public:
19   void RobotInit() override {
20      armLengthRatio = elbowToWrist_in / shoulderToElbow_in;
21   }
22
23   private:
24      int armLengthRatio;
25      int elbowToWrist_in = 39;
26      int shoulderToElbow_in = 3
27
28};

Alternatively, if zero is a valid value, adding if/else statements around the calculation can help you define alternate behavior to avoid making the processor perform a division by zero.

Finally, changing variable types to be float or double can help you get around the issue - floating-point numbers have special values like NaN to represent the results of a divide-by-zero operation. However, you may still have to handle this in code which consumes that calculation’s value.

HAL Resource Already Allocated

A very common FRC-specific error occurs when the code attempts to put two hardware-related entities on the same HAL resource (usually, roboRIO IO pin).

For example, consider the following code:

19PWMSparkMax leftFrontMotor;
20PWMSparkMax leftRearMotor;
21
22@Override
23public void robotInit() {
24   leftFrontMotor = new PWMSparkMax(0);
25   leftRearMotor = new PWMSparkMax(0);
26}
17class Robot : public frc::TimedRobot {
18   public:
19      void RobotInit() override {
20         m_frontLeftMotor.Set(0.5);
21         m_rearLeftMotor.Set(0.25);
22      }
23
24   private:
25      frc::PWMVictorSPX m_frontLeftMotor{0};
26      frc::PWMVictorSPX m_rearLeftMotor{0};
27
28   };

When run, you’ll see output that looks like this:

********** Robot program starting **********
Error at frc.robot.Robot.robotInit(Robot.java:25): Unhandled exception: edu.wpi.first.hal.util.AllocationException: Code: -1029
PWM or DIO 0 previously allocated.
Location of the previous allocation:
        at frc.robot.Robot.robotInit(Robot.java:24)
        at edu.wpi.first.wpilibj.TimedRobot.startCompetition(TimedRobot.java:107)
        at edu.wpi.first.wpilibj.RobotBase.runRobot(RobotBase.java:373)
        at edu.wpi.first.wpilibj.RobotBase.startRobot(RobotBase.java:463)
        at frc.robot.Main.main(Main.java:23)

Location of the current allocation:
        at edu.wpi.first.hal.PWMJNI.initializePWMPort(Native Method)
        at edu.wpi.first.wpilibj.PWM.<init>(PWM.java:66)
        at edu.wpi.first.wpilibj.motorcontrol.PWMMotorController.<init>(PWMMotorController.java:27)
        at edu.wpi.first.wpilibj.motorcontrol.PWMSparkMax.<init>(PWMSparkMax.java:35)
        at frc.robot.Robot.robotInit(Robot.java:25)
        at edu.wpi.first.wpilibj.TimedRobot.startCompetition(TimedRobot.java:107)
        at edu.wpi.first.wpilibj.RobotBase.runRobot(RobotBase.java:373)
        at edu.wpi.first.wpilibj.RobotBase.startRobot(RobotBase.java:463)
        at frc.robot.Main.main(Main.java:23)

Warning at edu.wpi.first.wpilibj.RobotBase.runRobot(RobotBase.java:388): The robot program quit unexpectedly. This is usually due to a code error.
  The above stacktrace can help determine where the error occurred.
  See https://wpilib.org/stacktrace for more information.
Error at edu.wpi.first.wpilibj.RobotBase.runRobot(RobotBase.java:395): The startCompetition() method (or methods called by it) should have handled the exception above.

This stack trace shows that a edu.wpi.first.hal.util.AllocationException has occurred. It also gives the helpful message: PWM or DIO 0 previously allocated..

Looking at our stack trace, we see two stack traces. The first stack trace shows that the first allocation occurred in Robot.java:25. The second stack trace shows that the error actually happened deep within WPILib. However, we should start by looking in our own code. Halfway through the stack trace, you can find a reference to the last line of the team’s robot code that called into WPILib: Robot.java:25.

Taking a peek at the code, we see line 24 is where the first motor controller is declared and line 25 is where the second motor controller is declared. We can also note that both motor controllers are assigned to PWM output 0. This doesn’t make logical sense, and isn’t physically possible. Therefore, WPILib purposely generates a custom error message and exception to alert the software developers of a non-achievable hardware configuration.

In C++, you won’t specifically see a stacktrace from this issue. Instead, you’ll get messages which look like the following:

Error at PWM [C::31]: PWM or DIO 0 previously allocated.
Location of the previous allocation:
        at frc::PWM::PWM(int, bool) + 0x50 [0xb6f01b68]
        at frc::PWMMotorController::PWMMotorController(std::basic_string_view<char, std::char_traits<char> >, int) + 0x70 [0xb6ef7d50]
        at frc::PWMVictorSPX::PWMVictorSPX(int) + 0x3c [0xb6e9af1c]
        at void frc::impl::RunRobot<Robot>(wpi::priority_mutex&, Robot**) + 0xa8 [0x13718]
        at int frc::StartRobot<Robot>() + 0x3d4 [0x13c9c]
        at __libc_start_main + 0x114 [0xb57ec580]

Location of the current allocation:: Channel 0
        at  + 0x5fb5c [0xb6e81b5c]
        at frc::PWM::PWM(int, bool) + 0x334 [0xb6f01e4c]
        at frc::PWMMotorController::PWMMotorController(std::basic_string_view<char, std::char_traits<char> >, int) + 0x70 [0xb6ef7d50]
        at frc::PWMVictorSPX::PWMVictorSPX(int) + 0x3c [0xb6e9af1c]
        at void frc::impl::RunRobot<Robot>(wpi::priority_mutex&, Robot**) + 0xb4 [0x13724]
        at int frc::StartRobot<Robot>() + 0x3d4 [0x13c9c]
        at __libc_start_main + 0x114 [0xb57ec580]

Error at RunRobot: Error: The robot program quit unexpectedly. This is usually due to a code error.
  The above stacktrace can help determine where the error occurred.
  See https://wpilib.org/stacktrace for more information.

        at void frc::impl::RunRobot<Robot>(wpi::priority_mutex&, Robot**) + 0x1c8 [0x13838]
        at int frc::StartRobot<Robot>() + 0x3d4 [0x13c9c]
        at __libc_start_main + 0x114 [0xb57ec580]

terminate called after throwing an instance of 'frc::RuntimeError'
  what():  PWM or DIO 0 previously allocated.
Location of the previous allocation:
        at frc::PWM::PWM(int, bool) + 0x50 [0xb6f01b68]
        at frc::PWMMotorController::PWMMotorController(std::basic_string_view<char, std::char_traits<char> >, int) + 0x70 [0xb6ef7d50]
        at frc::PWMVictorSPX::PWMVictorSPX(int) + 0x3c [0xb6e9af1c]
        at void frc::impl::RunRobot<Robot>(wpi::priority_mutex&, Robot**) + 0xa8 [0x13718]
        at int frc::StartRobot<Robot>() + 0x3d4 [0x13c9c]
        at __libc_start_main + 0x114 [0xb57ec580]

Location of the current allocation:: Channel 0

The key thing to notice here is the string, PWM or DIO 0 previously allocated.. That string is your primary clue that something in code has incorrectly «doubled up» on pin 0 usage.

The message example above was generated on a roboRIO. If you are running in simulation, it might look different.

Fixing HAL Resource Already Allocated Issues

HAL: Resource already allocated are some of the most straightforward errors to fix. Just spend a bit of time looking at the electrical wiring on the robot, and compare that to what’s in code.

In the example, the left motor controllers are plugged into PWM ports 0 and 1. Therefore, corrected code would look like this:

19PWMSparkMax leftFrontMotor;
20PWMSparkMax leftRearMotor;
21
22@Override
23public void robotInit() {
24
25   leftFrontMotor = new PWMSparkMax(0);
26   leftRearMotor = new PWMSparkMax(1);
27
28}
:lineno-start: 17

class Robot : public frc::TimedRobot {
   public:
      void RobotInit() override {
         m_frontLeftMotor.Set(0.5);
         m_rearLeftMotor.Set(0.25);
      }

   private:
      frc::PWMVictorSPX m_frontLeftMotor{0};
      frc::PWMVictorSPX m_rearLeftMotor{1};

   };

gradlew is not recognized…

gradlew is not recognized as an internal or external command is a common error that can occur when the project or directory that you are currently in does not contain a gradlew file. This usually occurs when you open the wrong directory.

Image containing that the left-hand VS Code sidebar does not contain gradlew

In the above screenshot, you can see that the left-hand sidebar does not contain many files. At a minimum, VS Code needs a couple of files to properly build and deploy your project.

  • gradlew

  • build.gradle

  • gradlew.bat

If you do not see any one of the above files in your project directory, then you have two possible causes.

  • A corrupt or bad project.

  • You are in the wrong directory.

Fixing gradlew is not recognized…

gradlew is not recognized... is a fairly easy problem to fix. First identify the problem source:

Are you in the wrong directory? - Verify that the project directory is the correct directory and open this.

Is your project missing essential files? - This issue is more complex to solve. The recommended solution is to recreate your project and manually copy necessary code in.