Dilbert





The Daily WTF

Error'd: Encoded for YOUR Benefit;

"Oracle makes it easy! Just dereference this pointer to view your failed invoice," wrote Jeremy W.

 

"Oh no! It's too late for Computing. They weren't able to fend off evolving threats in time!" writes David B.

 

Jean R. wrote, "Well, thank you Google for this handy tip! I'm sure it is helpful for any cyborg out there that might want to do some mental arithmetic."

 

"While this Microsoft mouse is light on CPU and storage, it makes up for it by being so small and lightweight!" Dave L. writes.

 

Andrzej wrote, "Ah yes, undefined, undefined, and undefined...truly some of my favorite artists. WAAAAY better than null."

 

Bob E. writes, "Keep this up and it'll be free Ham and Cheese Benedicts for everyone!"

 

[Advertisement] Forget logs. Next time you're struggling to replicate error, crash and performance issues in your apps - Think Raygun! Installs in minutes. Learn more.


CodeSOD: A Context for Logging;

When logging in Java, especially frameworks like Spring, making sure the logging statement has access to the full context of the operation in flight is important. Instead of spamming piles of logging statements in your business logic, you can use a “mapped diagnostic context” to cache useful bits of information during an operation, such that any logging statement can access it.

One of the tools for this is the “Mapped Data Context”, MDC. Essentially, it’s very much like a great big hash map that happens to be thread-local and is meant to be used by the logging framework. It’s a global-ish variable, but without the worst side effects of being global.

And you know people just love to use global variables.

Lothar was trying to figure out some weird requests coming out of an API, and needed to know where certain session ID values were coming from. There are a lot of “correct” ways to store session information in your Java Spring applications, and he assumed that was how they were storing those things. Lothar was wrong.

He provided this anonymized/generalized example of how pretty much every one of their REST request methods looked:

 @Override
   public Wtf getWtf(String wtfId) {

    Map<String, Object> params = new HashMap<>();
    params.put("wtfId", wtfId);
    params.put("sessId", MDC.get(MDC_LABEL_SESSION_ID));
    params.put(MDC_LABEL_SESSION_ID, MDC.get(MDC_LABEL_SESSION_ID));

    UriComponents uriComponents = UriComponentsBuilder
            .fromUriString("https://thedailywtf.com")
            .buildAndExpand(params);
    String urlString = uriComponents.toUriString();
        ResponseEntity<byte[]> responseEntity = restTemplate.getForEntity(urlString, byte[].class);
  }

Throughout their application, they (ab)used their logging framework as a thread-local storage system for passing user session data around.

Sure, the code was stupid, but the worst part about this code was that it worked. It did everything it needed to do, and it also meant that all of their log messages had rich context which made it easier to diagnose issues.

If it’s stupid and it works, that means you ship it.

[Advertisement] Otter - Provision your servers automatically without ever needing to log-in to a command prompt. Get started today!


CodeSOD: The Replacements;

Nobody wants to have a Bobby Tables moment in their database. So we need to to sanitize our inputs. Ted C noticed a bunch of stored procedures which contained lines like this:

  @scrubbed = fn_ScrubInput(fn_ScrubInput(@input))

Obviously, they wanted to be super careful, and make sure their inputs were clean. But it got Ted curious, so he checked out how the function was implemented. The function body had one line, the RETURN line, which looked like this:

  RETURN REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(@input, '"', '"'), 
'*', '\*'),'~', '\~'), '@', '\@'), '#', 
'\#'), '$','\$'),'%','\%'),'^','\^'),
'&','\&'),'(','\('),')','\)'),
'_','\_'),'+','\+'),'=','\='),'>',
'\>'),'<','\<'),'?','\?'),'/',
'\/')

Whitespace added.

Ted REPLACE REPLACE REPLACEd this with a call to the built-in STRING_ESCAPE function, which handled the escaping they needed.

[Advertisement] Forget logs. Next time you're struggling to replicate error, crash and performance issues in your apps - Think Raygun! Installs in minutes. Learn more.


CodeSOD: Cast Away;

The accountants at Gary's company had a problem: sometimes, when they wanted to check the price to ship a carton of product, that price was zero. No one had, as of yet, actually shipped product for free, but they needed to understand why certain cartons were showing up as having zero cost.

The table which tracks this, CartonFee, has three fields: ID, Carton, and Cost. Carton names are unique, and things like 12x3x6, or Box1, or even Large box. So, given a carton name, it should be pretty easy to update the cost, yes? The stored procedure which does this, spQuickBooks_UpdateCartonCost should be pretty simple.

ALTER PROCEDURE [dbo].[spQuickBooks_UpdateCartonCost] @Carton varchar(100), @Fee decimal(6,2) AS BEGIN DECLARE @Cost decimal(8,3) = LEFT(CAST(CAST((CAST(@Fee AS NUMERIC(36,3))/140) * 100 AS NUMERIC(36,3)) AS VARCHAR), LEN(CAST(CAST((CAST(@Fee AS NUMERIC(36,3))/140) * 100 AS NUMERIC(36,3)) AS VARCHAR)) - 1) + CASE WHEN RIGHT(LEFT(CAST(CAST((CAST(@Fee AS NUMERIC(36,3))/140) * 100 AS NUMERIC(36,4)) AS VARCHAR), LEN(CAST(CAST((CAST(@Fee AS NUMERIC(36,3))/140) * 100 AS NUMERIC(36,4)) AS VARCHAR)) - 1), 1) > 5 THEN '5' ELSE '0' END IF NOT EXISTS (SELECT 1 FROM CartonFee WHERE Carton = @Carton) BEGIN INSERT INTO CartonFee VALUES (@Carton, @Cost) END ELSE BEGIN UPDATE CartonFee SET Cost = @Cost WHERE Carton = @Carton END END

Just stare at that chain of casts for a moment. It teeters on the verge of making sense, calls to LEFT and RIGHT and multiplying by 100- we're just doing string munging to round off, that must be what's going on. If I count the parentheses, and really sit down and sketch this out, I can figure out what's going on, it must make sense, right?

And then you spot the /140. Divide by 140. Why? Why that very specific number? Is it a secret code? Is it a signal to the Illuminated Seers of Bavaria such that they know the stars are right and they may leave Aghartha to sit upon the Throne of the World? After all, 1 + 4 + 0 is five, and as we know, the law of fives is never wrong.

As it turns out, this stored procedure wasn't the problem. While it looks like it's responsible for updating the cost field, it's never actually called anywhere. It was, at one point, but it caused so much confusion that the users just started updating the table by hand. Somebody thought they'd get clever and use an UPDATE statement and messed up.

[Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today!


CodeSOD: I See What Happened;

Graham picked up a ticket regarding their password system. It seemed that several users had tried to put in a perfectly valid password, according to the rules, but it was rejected.

Graham's first step was to attempt to replicate on his own, but couldn't do it. So he followed up with one of the end users, and got them to reveal the password they had tried to use. That allowed him to trigger the bug, so he dug into the debugger to find the root cause.

private static final String UPPERCASE_LETTERS = "ABDEFGHIJKLMNOPQRSTUVWXYZ"; private int countMatches(String string, String charList) { int count = 0; for (char c : charList.toCharArray()) { count += StringUtils.countMatches(string, String.valueOf(c)); } return count; }

This isn't a great solution, but it at least works. Well, it "works" if you are able to remember how to recite the alphabet. If you look closely, you can tell that there are no pirate on their development team, because while pirates are fond of the letter "R", their first love will always be the "C".

[Advertisement] Forget logs. Next time you're struggling to replicate error, crash and performance issues in your apps - Think Raygun! Installs in minutes. Learn more.


Error'd: The WTF Experience;

"As it turns out, they've actually been singing Purple Haze before the start of all of those sportsball games," Adam writes.

 

Andrew C. writes, "When you buy from 'Best Pool Supplies', make no mistake...you're going to pay for that level of quality."

 

Jared wrote, "Pulling invalid data is forgiveable, but using a loop is not."

 

"VMware ESXi seems a little confused about how power state transitions work," writes Paul N.

 

"At first I was annoyed I didn't get the job, but now I really want to go in for free and fix their systems for them!" Mark wrote.

 

Peter M. writes, "Oh yes, Verizon! I am very excited! ...I'm just having a difficult time defining why."

 

[Advertisement] ProGet supports your applications, Docker containers, and third-party packages, allowing you to enforce quality standards across all components. Download and see how!


CodeSOD: Parse, Parse Again;

Sometimes, a block of terrible code exists for a good reason. Usually, it exists because someone was lazy or incompetent, which while not a good reason, at least makes sense. Sometimes, it exists for a stupid reason.

Janet’s company recently bought another company, and now the new company had to be integrated into their IT operations. One of the little, tiny, minuscule known-issues in the new company’s system was that their logging was mis-configured. Instead of putting a new-line after each logging message, it put only a single space.

That tiny problem was a little bit larger, as each log message was a JSON object. The whole point of logging out a single JSON document per line was that it would be easy to parse/understand the log messages, but since they were all on a single line, it was impossible to just do that.

The developers at the acquired company were left with a choice: they could fix the glitch in the logging system so that it output a newline after each message, or they could just live with this. For some reason, they decided to live with it, and they came up with this solution for parsing the log files:

def parse(string):
  obs = []
  j = ""
  for c in string.split():
    j += c
    try:
      obs.append(json.loads(j))
      j = ""
    except ValueError:
      pass
 
  return obs

This splits the string on spaces. Then, for each substring, it tries to parse it as a JSON object. If it succeeds, great. If it throws an exception, append the next substring to this one, and then try parsing again. Repeat until we’ve built a valid JSON document, than clear out the accumulator and repeat the process for all the rest of the messages. Eventually, return all the log messages parsed as JSON.

As a fun side effect, .split is going to throw the spaces away, so when they j += c, if your log message looked like:

{"type": "Error", "message": "Unable to parse JSON document"}

After parsing that into JSON, the message becomes UnabletoparseJSONdocument.

But at least they didn’t have to solve than newline bug.

[Advertisement] Forget logs. Next time you're struggling to replicate error, crash and performance issues in your apps - Think Raygun! Installs in minutes. Learn more.