Some time ago, my partner in the group asked me to help troubleshoot an online problem. I thought the troubleshooting process was interesting, and I wanted to record it and see if it could help other students, so I wrote this article.

TypeError: C.fein is not a function TypeError: c.fein is not a function The relevant students tried to check but failed, and then rolled back the recent online changes and failed to check the problem. Although the reappearance path was finally confirmed, it could not be reappeared locally.

🔍 Preliminary investigation

After replaying the error online, click the error stack file jump to quickly locate the code that failed online. Since the line is full of compressed code, here we can click {} in the lower left corner to beautify the code.

After beautification, we can see that it should be line 189624. We’ll just try to break on this line, and then we’ll see the code spinning around like crazy. That’s because it’s in a for loop. If you look closely, you can see that the code is actually a recursive execution of the this.head chain. After each execution, the current C is assigned to the next value of the chain and the corresponding fn() method is executed. The problem is that some value on the chain has no fn() method, which ultimately causes this error.

And maybe once we’ve confirmed the problem, we need to see what the final value of this C is. Being in a loop, clicking the next step over and over again is a hassle. Since we have a clear goal, we can try to add conditional breakpoints so that only breakpoints that meet our criteria stop, otherwise we ignore normal execution.

Right click on line 189624 and Add Conditional Breakpoint… Option, and type typeof C.fin! == ‘function’ as a conditional expression. This allows us to implement a conditional breakpoint that only fires when c. fin is not a method.

Once a conditional breakpoint is triggered, we can debug from the console based on the context output variable at the time of the breakpoint. As can be clearly seen from the lower left figure, C. fein does not exist at this time.

Since we already know that this.head should be a chain, execute the chain methods in turn. So theoretically every member of the chain is the same. So I tried to output all the elements in the this.head chain to see what the chain would look like. I also tried to write the loop in the simulation code on the console and found that the output is shown below on the left. The last element in the chain is the element we have a problem with.

We already know that we can’t reproduce this problem in a local development environment, so I’m going to print the this.head chain in the same location, as shown above. And it turns out that the output is basically the same as the output on the line, except for the last problematic element.

It seems that the problem is caused by the online execution of the code adding such a thing to the chain, while the local execution does not trigger the problem because it does not have this extra element.

🐞 Confirm the problem

Once I found out why, I thought I’d try to figure out at the code level where this was added. Since the word i.prototype.finish was clearly visible in the previous code, I guess this is a class definition. So I want to see where this class is instantiated.

From the compressed code that just reported the error, we can see that the module that reported the error is the “protobuf.js” module. So I looked in the project and dependencies to see which module depended on it, and finally found that it was an IM message module that we used internally.

After searching for the words.finish() in the specific dependency module, the final call was found in the following place. The serialize() method calls request.encode (), which returns an instance of the $Writer base class, which is the Writer base class in the protobuf.js module. After the request.encode () method instantiates the Writer base class, it executes a series of member functions. After the execution, it returns the Writer instance and calls its Finish () method.

Once I understood the process, I went along with itRequest.encode(req).finish()The sentence begins to go upRequest.encode()Method to perform breakpoints (lower left). Try output at the end of the breakpointo.head(oIt’s a compressed pointWriterInstance variable), and find that the exception chain element already exists (lower right).

The code in the middle is broken a little bit and it’s still there. Finally, I found a clue at the breakpoint in the header. After attempting to add a power outage at the beginning, it was found that abnormal data already existed on the O.read chain after line 120274 was executed.

Let’s take a look at the code and see what the O.Crew () method does. Writer.create() is essentially an instance-chemical method of Writer’s base class. As you can see in the following figure, Writer’s constructor assigns initial values to some member attributes. The key initial value of this.head is an instance of the Op base class. On the right you can see that the Op base class constructor also assigns some initial values. We can also see that function noop() {} is actually an empty method. This. Head, by default, points to an Op object instantiated by an empty method.

At first glance, the whole process looks very simple. In essence, there are simple assignments inside the constructor, and there are no problems. As a result, the fault is rectified by link. We need to do a breakpoint check on the Writer constructor because last week we had a problem after executing the writer.create () factory method.

After constructing a breakpoint at the end of the method, print the this.head chain. And this is just doing the initial value of the operation, how can this be a problem? Since I can debug in the current context with breakpoints, I try to instantiate the Op base class myself at this point (see figure below). At this point, we found that it is indeed the wrong next attribute, is the problem element we are looking for!

At this point, I feel like we’re getting closer to the truth!

If we hover on the f variable for a while, there will be a link to its definition. After clicking it, it will directly jump to the right of its definition (in fact, it is not too far away).

You may have noticed that in the code we just looked at, this.next is defined as undefined. This g matches line 189456 with g = s.base64, which is why we see this. Head. Next. We try to look at the referenced protobuf.js code and see that this.next is equal to g but it is not associated with u.base64.

Since I’ve dealt with a few cases where the code is compressed and then compressed, I can basically conclude at this point that protobuf.js is the compressed code introduced in our dependencies, and the compressed code is compressed again causing the variable pointing problem. This also confirms the reason why only online can be reproduced locally. Because there is no compression locally.

🛠 How to solve the problem

There are two ways to solve a problem. One is to find the cause of the compression tool to cause this problem; The other is to avoid this problem in the reverse direction. We do not introduce the compressed code, but normally introduce the uncompressed code, and finally compress it by the project.

Both approaches solve the problem. The first method will take a long time, so we first adopted the second method to solve temporarily. Since the dependency package is not maintained by us, we can only use patch-package to patch the module to repair it. Its function is to modify dependencies based on our diff files after they are installed.

Here we have a simple change, find the place where our dependent module introduces protobuf.min.js and change it to protobuf.js.

🗒 afterword.

Undefined becomes g when compressed. The initial guess is that we want to define an undefined variable, so undefined is undefined. I tried to clone the Protobuf.js repository and found that marguel.eval was configured in UglifyJS to cause this feature.

The above is the complete investigation of the blood case caused by compression. The whole process can be summarized with the following experiences for your reference:

  1. In addition to single-step breakpoints, we also have conditional breakpoints, log breakpoints and other breakpoints to help us troubleshoot problems. Proper use of breakpoints will accelerate our troubleshooting speed.
  2. After the breakpoint, the current JS environment will remain in the current context, and we can execute on the console, output data we want to the current environment to help troubleshoot.
  3. Console we can also hover view the definition of the location, a quick jump between definitions.
  4. After the compression of the code is not terrible, we can compare the source code, unable to compress the keyword for positioning search.
  5. As long as it’s reproducible, it’s not a problem!

Finally, I wish everyone a happy start and no bugs in the New Year!