Closed CortneyOfstad closed 1 month ago
Current assignee @CortneyOfstad is eligible for the Bug assigner, not assigning anyone new.
@CortneyOfstad Huh... This is 4 days overdue. Who can take care of this?
@TMisiukiewicz it was indicated on one of the other Crashlytic GHs that the logs are truncated. Any way you could pull up the full logs? Thanks!
@CortneyOfstad Eep! 4 days overdue now. Issues have feelings too...
can we change it to weekly?
@CortneyOfstad this issue was created 2 weeks ago. Are we close to a solution? Let's make sure we're treating this as a top priority. Don't hesitate to create a thread in #expensify-open-source to align faster in real time. Thanks!
@CortneyOfstad Still overdue 6 days?! Let's take care of this!
@CortneyOfstad 8 days overdue is a lot. Should this be a Weekly issue? If so, feel free to change it!
Sorry, was OoO, so adjusting the frequency now!
Hey @CortneyOfstad π
To reproduce this I went through the Logs and Breadcrumbs section here in firebase crashlytics but I couldn't reproduce it. Also, for another similar event the logs and breadcrumbs were different, so I am not sure whether following are a correct reproduction steps but I am writing these from the link above:
After the last step, we get an error in firebase crashlytics. I tried it but I couldn't reproduce it.
Now, let's try to find the root cause in the code using the help from the stack trace. The function which is causing this error is getExtension
which is a function in Str.ts
in expensify-common
. I opted to test this by isolating this function and below are the results:
We get an error url.split
is not a function, which is similar to the error we get in crashlytics. This error only happens if the passed url
is of another type than string
, precisely it's of type number
or array
. On any other type like null
or undefined
, we get a different error.
To fix this, we can add a typeof
operator in getExtension
function, which will early return if url
is not of string
type. Below is the diff of suggested changes:
diff --git a/lib/str.ts b/lib/str.ts
index e1dd37c..432f8ed 100644
--- a/lib/str.ts
+++ b/lib/str.ts
@@ -992,6 +992,7 @@ const Str = {
* without query parameters
*/
getExtension(url: string): string | undefined {
+ if (typeof url !== 'string') return undefined;
return url.split('.').pop()?.split('?')[0]?.toLowerCase();
},
Thanks for the detailed explanation @hurali97!
@TMisiukiewicz reviewing the comment above, there may potentially be an issue in the breadcrumb flow from the original issue that this was created from. Do you have any insight into what could potentially be missing or needs to be adjusted in order to recreate?
@CortneyOfstad currently breadcrumbs are tied only to navigation events only, and this, unfortunately, it gaves us very limited knowledge about users actions. When I have a bit more capacity, I am planning to review if there is a space for improvements in this area.
Also, the proposal from @hurali97 looks good to me π
@CortneyOfstad do you think we can move forward with creating a PR to fix this?
Sorry for the delay here β PR looks great! Thank you!
Job added to Upwork: https://www.upwork.com/jobs/~0147e5efa631c195d0
Triggered auto assignment to Contributor-plus team member for initial proposal review - @paultsimura (External
)
@paultsimura proposed solution is here
The explanation and the solution in the proposal look good to me.
πππ C+ reviewed
I'm a little uncertain about the testing method for this issue though βΒ do we just create a PR inside expensify-common
and add unit tests to cover the failure scenarios? Or are there specific steps to reproduce the original error?
Triggered auto assignment to @francoisl, see https://stackoverflow.com/c/expensify/questions/7972 for more details.
The explanation makes sense, but since the url
param is already defined as a string in the TS function signature getExtension(url: string)
, wouldn't adding a check on typeof url
be redundant?
The explanation makes sense, but since the
url
param is already defined as a string in the TS function signaturegetExtension(url: string)
, wouldn't adding a check ontypeof url
be redundant?
@francoisl That's true but it will only take effect in debug builds. On release builds, TS is stripped and we only have plain JS, so there could be anything passed to getExtension
. Since we are not aware of the reproduction steps, we can't test this locally and see what value is being passed to it and why.
Since typeof
is a JS operator it will be present in the final JS build and guarantees that the no type other than string
should fall through.
The explanation and the solution in the proposal look good to me.
πππ C+ reviewed
I'm a little uncertain about the testing method for this issue though βΒ do we just create a PR inside
expensify-common
and add unit tests to cover the failure scenarios? Or are there specific steps to reproduce the original error?
@paultsimura unfortunately, we don't have any reproduction steps. I agree that to give us more confidence we can add unit tests.
I will create a PR shortly and add unit tests for the implementation.
I got the PR to review and have the same concern as @francoisl. This should not be needed and should be caught by TS.
I see you say we don't have reproduction steps, but we do have some trace, can't we use that to figure out where this is coming from and why it is not being caught by TS?
@francoisl, @CortneyOfstad, @paultsimura, @hurali97 Uh oh! This issue is overdue by 2 days. Don't forget to update your issues!
Doesn't look overdue to me π
I got the PR to review and have the same concern as @francoisl. This should not be needed and should be caught by TS.
I see you say we don't have reproduction steps, but we do have some trace, can't we use that to figure out where this is coming from and why it is not being caught by TS?
@iwiznia I agree and I provided an explanation here. TS will only help us in debug environments, so it won't catch anything on release.
As for the trace, they don't appear to be useful in order to further narrow down this issue. Only thing I was able to deduce was that we have calls to isImage
and isPDF
, which internally calls getExtension
. The former functions are invoked in getThumbnailAndImageURIs
and MoneyRequestConfirmationListFooter
respectively and they deduce the fileName from transaction
object.
I expect transaction
object to come from Onyx and it could be a reason of stale onyx data or some other thing. I can not dive further on how it is possible to receive anything other than the string or undefined here, because of lack of reproduction data.
We can argue that we can add additional checks in these two places instead of adding one in getExtension
but it won't scale. If in future, we have crashes in other places we will need to add checks in those places as well.
TS will only help us in debug environments, so it won't catch anything on release.
The whole idea of TS is that it provides "compile" time checks. Why is it not catching this bad call then?
I'd be much more on board if we:
@iwiznia Since you're already saying that "TS provides compile time checks", so it won't help us in runtime which means you can pass any data to it.
Why is it not catching this bad call then?
Because in compile time, we are not invoking getExtension
with bad data.
If you want, we can add a log of what data are we passing to getExtension
, only in the case if it's type is not string | undefined
. Also, if we go down this path, I think it won't be okay to add a firebase bread crumb from this function, so as to keep it decoupled. So this would mean to add a log wherever, getExtension
is called from isPDF
and isImage
, that's a total of 19 places in the App codebase, so a lot of redundancy.
Anyways, Let me know what you think of the above.
From my experience, most of the open-source libraries use additional runtime checks in order to ensure safety, where there's a chance of type ambiguity or a union type. Also, I think the cost of doing above is more than adding a runtime check.
Because in compile time, we are not invoking getExtension with bad data.
I am confused, nothing is really invoked at compile time.
@francoisl curious for your thoughts about the PR...
I am confused, nothing is really invoked at compile time.
@iwiznia TS is aimed to handle the following cases:
function doSmth(param: string) { ... };
const num = 123;
doSmth(num);
TS will show you the following error both in IDE and during compilation, because it knows the type of num
, and that it isn't what doSmth
expects:
Argument of type
number
is not assignable to parameter of typestring
However, there are cases when the passed parameter's type is not clearly known (e.g. it comes from some file parsing library). We assume it's a string, and we pass it as such into the function, and the TS compiler doesn't complain. But in reality, it may not be a string, so the explicit type check is useful here.
However, there are cases when the passed parameter's type is not clearly known (e.g. it comes from some file parsing library). We assume it's a string, and we pass it as such into the function, and the TS compiler doesn't complain.
But even in this case, wherever that parsing call is done the variables should be typed and thus should still get the error, no? If everything is ts and has proper types, the bug should be caught by ts somewhere.
In a perfect world - yes. But IIRC, there was a bug sometime ago where we'd expect the image URL to always be a string (which is natural), but iOS Native used numeric 1
for representing the URL of a locally stored image blob. This was quite unexpected and caused by a platform specificity. Maybe we're facing something similar hereπ€
Left a comment in the PR about the logs, but for now I'm not sure I understand the part about the firebase breadcrumb in this comment:
If you want, we can add a log of what data are we passing to getExtension, only in the case if it's type is not string | undefined. Also, if we go down this path, I think it won't be okay to add a firebase bread crumb from this function, so as to keep it decoupled. So this would mean to add a log wherever, getExtension is called from isPDF and isImage, that's a total of 19 places in the App codebase, so a lot of redundancy.
Would it be fine if we use Log.alert()
with includeStackTrace: true
directly in str.js? That way it's just one place to update, and we'd still get a stacktrace to get context about where the issue was from?
@francoisl, @CortneyOfstad, @paultsimura, @hurali97 Huh... This is 4 days overdue. Who can take care of this?
There is an ongoing discussion in the PR
@francoisl, @CortneyOfstad, @paultsimura, @hurali97 Whoops! This issue is 2 days overdue. Let's get this updated quick!
Oh, we still need a PR to bump the expensify-common version in package.json
, @hurali97 do you mind sending one please?
We are about to bump to 2.0.88
in this PR. Do you want us to wait, or just pull in your changes with ours?
@francoisl, @CortneyOfstad, @paultsimura, @hurali97 Still overdue 6 days?! Let's take care of this!
@deetergp I think you can pull them in. There is nothing breaking here β just some extra precautions & logging.
@francoisl, @CortneyOfstad, @paultsimura, @hurali97 Eep! 4 days overdue now. Issues have feelings too...
Still waiting on https://github.com/Expensify/App/pull/49038 to be merged
Not overdue.
@francoisl, @CortneyOfstad, @paultsimura, @hurali97 Eep! 4 days overdue now. Issues have feelings too...
@francoisl https://github.com/Expensify/App/pull/49038 was merged 4 days ago. What's the plan here now?
Ah nice, and it was just deployed to production too.
I think we can issue payments and close this. With the new Log.warn
, if the issue shows up again, we'll see it in our logs and an internal engineer will be assigned to investigate.
The corresponding version bump was deployed to production on Oct 1. Payment is due on 2024-10-08.
I don't think this GH is suitable for a BZ checklist: it is more like a preventive change with extra logging to detect and avoid previously uncaught crashes.
@paultsimura β i sent you an offer in Upwork here. Please let me know once you accept, and I'll get that paid ASAP. Thanks!
Accepted, thanks @CortneyOfstad
Coming from this GH β https://github.com/Expensify/App/issues/45054#issuecomment-2238548276
Reported by @TMisiukiewicz
Upwork Automation - Do Not Edit
Issue Owner
Current Issue Owner: @paultsimura