CodeSOD: Terned Around About Nullables
John H works with some industrial devices. After a recent upgrade at the falicity, the new control software just felt like it was packed with WTFs. Fortunately, John was able to get at the C# source code for these devices, which lets us see some of the logic used...
public bool SetCrossConveyorDoor(CrossConveyorDoorInfo ccdi, bool setOpen){ if (!ccdi.PowerBoxId.HasValue)return false; ulong? powerBoxId = ccdi.PowerBoxId; ulong pbid; ulong ccId; ulong rowId; ulong targetIdx; PBCrossConveyorConfiguration.ExtractIdsFromPowerboxId(powerBoxId.Value, out pbid, out ccId, out rowId, out targetIdx); TextWriter textWriter = Console.Out; object[] objArray1 = new object[8]; objArray1[0] = (object) pbid; objArray1[1] = (object) ccId; objArray1[2] = (object) setOpen; object[] objArray2 = objArray1; powerBoxId = ccdi.PowerBoxId; ulong local = powerBoxId.Value; objArray2[3] = (object) local; objArray1[4] = (object) pbid; objArray1[5] = (object) ccId; objArray1[6] = (object) rowId; objArray1[7] = (object) targetIdx; object[] objArray3 = objArray1; textWriter.WriteLine( "Sending CCD command to pbid = {0}, ccdId = {1}, Open={2}, orig PowerBoxId: {3} - divided:{4}/{5}/{6}/{7}", objArray3); bool? nullable1 = this.CopyDeviceToRegisters((int) (ushort) ccId); if ((!nullable1.GetValueOrDefault() ? 1 : (!nullable1.HasValue ? 1 : 0)) != 0)return false; byte? nullable2 = this.ReadDeviceRegister(19, "CrossConvDoor"); byte num = nullable2.HasValue ? nullable2.GetValueOrDefault() : (byte) 0; byte registerValue = setOpen ? (byte) ((int) num & -225 | 1 << (int) targetIdx) : (byte) ((int) num & -225 | 16); Console.Out.WriteLine("ccdid = {0} targetIdx = {1}, b={2:X2}", (object) ccId, (object) targetIdx, (object) registerValue); this.WriteDeviceRegister(19, registerValue, "CrossConvDoor"); nullable1 = this.CopyRegistersToDevice(); return nullable1.GetValueOrDefault() && nullable1.HasValue;}
There's a bunch in here, but I'm going to start at the very bottom:
return nullable1.GetValueOrDefault() && nullable1.HasValue
GetValueOrDefault, as the name implies, returns the value of the object, or if that object is null, it returns a suitable default value. Now, for any referenece type, that can still be null. But nullable1 is a boolean (defaults to false), and nullable2 is a byte (defaults to zero).
This line alone makes one suspect that the developer doesn't really understand how nullables work. And, as we read up the code, we see more evidence of this:
byte num = nullable2.HasValue ? nullable2.GetValueOrDefault() : (byte) 0;
Again, if nullable2 has a value, GetValueOrDefault will return that value, if it doesn't, it returns zero. So we've just taken a simple thing and made it less readable by surrounding it with a bunch of noise which doesn't change its behavior.
But, continuing reading backwards:
if ((!nullable1.GetValueOrDefault() ? 1 : (!nullable1.HasValue ? 1 : 0)) != 0)return false;
We've moved into nested ternaries inside an if. Which, if we try and parse through this one: if the nullable's value is false, 1 != 0, so we return false. If, on the other hand, the nullable's value is true, we check to see if it doesn't have a value, in which case we compare 1 != 0 and return false. Except the only way nullable1 could ever be true is if it has a value, so that means if nullable1 is true, we don't return false.
In other words, this is a really complicated way of saying:
if (!nullable1.GetValueOrDefault()) return false;
With all that out of the way, it brings us to the block of objArrays. The core purpose of this block is to populate what appears to be logging output. Now, the WriteLine method does take an object[] parameter to drive that formatting... but it's a param-array, which means you could invoke it as: Console.Out.WriteLine("...", pbid, ccId, setOpen...). I'm not 100% certain when params appeared in C#, and a cursory searching implies that it's always been a language feature. Still, I'll give the developer responsible the benefit of the doubt on just using the object[], because of how they used it.
They start with objArray1, and populate three fields. Then they create objArray2 which is just a reference to objArray1. They populate the fourth field through objArray2, then go back to using objArray1. Then they create objArray3 which is also just referencing objArray1, and send that to WriteLine.
Maybe the goal was some form of intentional obfuscation? Were they just... confused? It's impossible to guess.
So instead of guessing, I'll just share another snippet of code from the same program, which I think sums up my feelings:
private static void GenPwd(string[] args){ if (args[1].Contains("!"))Console.Out.WriteLine("Use password without tilde (~) please."); ...}[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!