Repository: fastify/fastify (JavaScript)

Issue:
Calling reply.callNotFound inside frameworkErrors can result in an uncaught exception
### Prerequisites

- [X] I have written a descriptive issue title
- [X] I have searched existing issues to ensure the bug has not already been reported


### Fastify version

3.27.0

### Plugin version

4.5.0

### Node.js version

17.4.0

### Operating system

Linux

### Operating system version (i.e. 20.04, 11.3, 10)

20.04

### Description

Following the example below it'll instantly log an error:
```
TypeError: Cannot read properties of undefined (reading 'preHandler')
    at notFound (node_modules/fastify/lib/reply.js:707:21)
    at Reply.callNotFound (node_modules/fastify/lib/reply.js:309:3)
    at frameworkErrors (test.js:14:17)
    at onBadUrl (node_modules/fastify/fastify.js:613:14)
    at Object.handler (node_modules/find-my-way/index.js:621:33)
    at Router.lookup (node_modules/find-my-way/index.js:378:14)
    at node_modules/light-my-request/index.js:87:41
    at Request.prepare (node_modules/light-my-request/lib/request.js:132:12)
    at makeRequest (node_modules/light-my-request/index.js:87:14)
    at node_modules/light-my-request/index.js:113:7
```

If an url with an invalid uri is opened in the browser then an uncaught exception will happen.

### Steps to Reproduce

Run this example:

```
const fastifyStatic = require('fastify-static');

(async () => {
	process.on(
		'uncaughtException',
		(err) => {
			console.log('uncaughtException', err);
		}
	);
	
	const fastify = require('fastify')({
		logger: true,
		frameworkErrors: (error, req, reply) => {
			return reply.callNotFound();
		}
	});
	
	fastify.register(
		fastifyStatic,
		{
			root: '/www' // doesn't need to exit
		}
	);

	await fastify.listen(8080);
	
	await fastify.inject({
		method: 'GET',
		url: '/%'
	});
})()
	.catch((err) => {
		console.error(err);
	})
```

### Expected Behavior

Calling reply.callNotFound should work in this case.


Discussion:
I transferred this to Fastify proper, it's a bug there.

Would you like to send a Pull Request to address this issue? Remember to add unit tests.

For some reason there is no `context` defined in:
https://github.com/fastify/fastify/blob/25147feda161e0f51e5b32e88bec87793188a8d4/lib/reply.js#L707

Likely we are not instatiating something correctly in https://github.com/fastify/fastify/blob/main/fastify.js#L611-L612
@mcollina 

Problems lies in https://github.com/fastify/fastify/blob/25147feda161e0f51e5b32e88bec87793188a8d4/lib/reply.js#L698

We are using string null checker here which is causing https://github.com/fastify/fastify/blob/25147feda161e0f51e5b32e88bec87793188a8d4/lib/reply.js#L707 to have null context because in https://github.com/fastify/fastify/blob/25147feda161e0f51e5b32e88bec87793188a8d4/lib/reply.js#L704 we trying to assign a undefined context. 

We should be checking `(typeof reply.context[kFourOhFourContext] === 'undefined' )` instead so it'll handle both undefined and null is what I feel. Thoughts ?

I am raising the PR for the same
Where is that assigned to undefined? I think we should not be in that position.
`reply.context[kFourOhFourContext] ` is coming in as undefined

![image](https://user-images.githubusercontent.com/9670465/151168204-b84f9cb8-a548-4bfc-b695-f947ee3745c5.png)

![image](https://user-images.githubusercontent.com/9670465/151168230-ba08ea9f-7076-45d7-b9f0-c00546ec3ccf.png)
> I transferred this to Fastify proper, it's a bug there.
> 
> 
> 
> Would you like to send a Pull Request to address this issue? Remember to add unit tests.
> 
> 
> 
> For some reason there is no `context` defined in:
> 
> https://github.com/fastify/fastify/blob/25147feda161e0f51e5b32e88bec87793188a8d4/lib/reply.js#L707
> 
> 
> 
> Likely we are not instatiating something correctly in https://github.com/fastify/fastify/blob/main/fastify.js#L611-L612

As I said in the text above, We are not initializing Reply and the context correctly in 

https://github.com/fastify/fastify/blob/main/fastify.js#L611-L612
Sorry I think I got confused here. This is what I analysed. Kindly correct me and point me to the place where I am going wrong.

In line number https://github.com/fastify/fastify/blob/20f934307c29ab97988222a165c84f16b7146ce6/fastify.js#L611 we are initialising the onBadUrl Context with https://github.com/fastify/fastify/blob/20f934307c29ab97988222a165c84f16b7146ce6/fastify.js#L57-L62 where we don't have the `kfourOhFourContext`

So https://github.com/fastify/fastify/blob/25147feda161e0f51e5b32e88bec87793188a8d4/lib/reply.js#L698 will fail as it isn't null but undefined.

So this line  https://github.com/fastify/fastify/blob/25147feda161e0f51e5b32e88bec87793188a8d4/lib/reply.js#L704 would assign undefined context.

So as per your last comment I should be modyfying this context object https://github.com/fastify/fastify/blob/20f934307c29ab97988222a165c84f16b7146ce6/fastify.js#L57 to accommodate fourOhFourContext. Is that what you are suggesting ?

 



https://github.com/fastify/fastify/pull/3661



Patch:
diff --git a/fastify.js b/fastify.js
index 5a638c37cc8..4a53945ba18 100644
--- a/fastify.js
+++ b/fastify.js
@@ -27,7 +27,8 @@ const {
   kPluginNameChain,
   kSchemaErrorFormatter,
   kErrorHandler,
-  kKeepAliveConnections
+  kKeepAliveConnections,
+  kFourOhFourContext
 } = require('./lib/symbols.js')
 
 const { createServer } = require('./lib/server')
@@ -58,7 +59,8 @@ const onBadUrlContext = {
   config: {
   },
   onSend: [],
-  onError: []
+  onError: [],
+  [kFourOhFourContext]: null
 }
 
 function defaultBuildPrettyMeta (route) {


Test patch:
diff --git a/test/404s.test.js b/test/404s.test.js
index 60b510f35f9..3bbe69f7345 100644
--- a/test/404s.test.js
+++ b/test/404s.test.js
@@ -1772,3 +1772,27 @@ test('setNotFoundHandler should be chaining fastify instance', t => {
 
   t.end()
 })
+
+test('Send 404 when frameworkError calls reply.callNotFound', t => {
+  t.test('Dynamic route', t => {
+    t.plan(4)
+    const fastify = Fastify({
+      logger: true,
+      frameworkErrors: (error, req, reply) => {
+        t.equal(error.message, "'%world' is not a valid url component")
+        return reply.callNotFound()
+      }
+    })
+    fastify.get('/hello/:id', () => t.fail('we should not be here'))
+    fastify.inject({
+      url: '/hello/%world',
+      method: 'GET'
+    }, (err, response) => {
+      t.error(err)
+      t.equal(response.statusCode, 404)
+      t.equal(response.payload, '404 Not Found')
+    })
+  })
+
+  t.end()
+})