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.

En Java y C ++, una estructura de datos stack se utiliza para almacenar información sobre qué función o método se está ejecutando actualmente.

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.

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» (IE, 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:

19  PWMSparkMax armMotorCtrl;
20
21  @Override
22  public void robotInit() {
23      armMotorCtrl.setInverted(true);
24  }

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.

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:

19  PWMSparkMax armMotorCtrl;
20
21  @Override
22  public void robotInit() {
23      armMotorCtrl = new PWMSparkMax(0);
24      armMotorCtrl.setInverted(true);
25  }

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:

18  int armLengthRatio;
19  int elbowToWrist_in = 39;
20  int shoulderToElbow_in = 0; //TODO
21
22  @Override
23  public void robotInit() {
24     armLengthRatio = elbowToWrist_in / shoulderToElbow_in;
25  }

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.

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:

18  int armLengthRatio;
19  int elbowToWrist_in = 39;
20  int shoulderToElbow_in = 3;
21
22  @Override
23  public void robotInit() {
24
25     armLengthRatio = elbowToWrist_in / shoulderToElbow_in;
26
27  }

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 on the same HAL resource (usually, roboRIO IO pin.)

For example, consider the following code:

19  PWMSparkMax leftFrontMotor;
20  PWMSparkMax leftRearMotor;
21
22  @Override
23  public void robotInit() {
24     leftFrontMotor = new PWMSparkMax(0);
25     leftRearMotor = new PWMSparkMax(0);
26  }

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 purposefully generates a custom error message and exception to alert the software developers of a non-achievable hardware configuration.

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:

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

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.