Static Value-Flow Analysis
Loading...
Searching...
No Matches
SVFIR.cpp
Go to the documentation of this file.
1//===- SVFIR.cpp -- IR of SVF ---------------------------------------------//
2//
3// SVF: Static Value-Flow Analysis
4//
5// Copyright (C) <2013-> <Yulei Sui>
6//
7
8// This program is free software: you can redistribute it and/or modify
9// it under the terms of the GNU Affero General Public License as published by
10// the Free Software Foundation, either version 3 of the License, or
11// (at your option) any later version.
12
13// This program is distributed in the hope that it will be useful,
14// but WITHOUT ANY WARRANTY; without even the implied warranty of
15// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16// GNU Affero General Public License for more details.
17
18// You should have received a copy of the GNU Affero General Public License
19// along with this program. If not, see <http://www.gnu.org/licenses/>.
20//
21//===----------------------------------------------------------------------===//
22
23/*
24 * SVFIR.cpp
25 *
26 * Created on: 31, 12, 2021
27 * Author: Yulei Sui
28 */
29
30#include "Util/Options.h"
31#include "SVFIR/SVFIR.h"
32#include "Graphs/CallGraph.h"
33
34using namespace SVF;
35using namespace SVFUtil;
36
37
38std::unique_ptr<SVFIR> SVFIR::pag;
39
41{
42}
43
48{
49 SVFVar* srcNode = getGNode(src);
50 SVFVar* dstNode = getGNode(dst);
52 return nullptr;
53 else
54 {
58 return addrPE;
59 }
60}
61
79
84{
86 SVFVar* resNode = getGNode(res);
87 PHINodeMap::iterator it = phiNodeMap.find(resNode);
88 if(it == phiNodeMap.end())
89 {
90 PhiStmt* phi = new PhiStmt(resNode, {opNode}, {pred});
94 return phi;
95 }
96 else
97 {
98 it->second->addOpVar(opNode,pred);
100 return nullptr;
101 }
102}
103
108{
111 SVFVar* dstNode = getGNode(res);
112 SVFVar* condNode = getGNode(cond);
114 return nullptr;
115 else
116 {
117 std::vector<SVFVar*> opnds = {op1Node, op2Node};
121 return select;
122 }
123}
124
129{
132 SVFVar* dstNode = getGNode(dst);
134 return nullptr;
135 else
136 {
137 std::vector<SVFVar*> opnds = {op1Node, op2Node};
138 CmpStmt* cmp = new CmpStmt(dstNode, opnds, predicate);
141 return cmp;
142 }
143}
144
145
150{
153 SVFVar* dstNode = getGNode(dst);
155 return nullptr;
156 else
157 {
158 std::vector<SVFVar*> opnds = {op1Node, op2Node};
162 return binaryOP;
163 }
164}
165
170{
171 SVFVar* srcNode = getGNode(src);
172 SVFVar* dstNode = getGNode(dst);
174 return nullptr;
175 else
176 {
180 return unaryOP;
181 }
182}
183
184/*
185* Add BranchStmt
186*/
201
206{
207 SVFVar* srcNode = getGNode(src);
208 SVFVar* dstNode = getGNode(dst);
210 return nullptr;
211 else
212 {
216 return loadPE;
217 }
218}
219
225{
226 SVFVar* srcNode = getGNode(src);
227 SVFVar* dstNode = getGNode(dst);
229 return nullptr;
230 else
231 {
232 StoreStmt* storePE = new StoreStmt(srcNode, dstNode, curVal);
235 return storePE;
236 }
237}
238
243{
244 SVFVar* srcNode = getGNode(src);
245 SVFVar* dstNode = getGNode(dst);
247 return nullptr;
248 else
249 {
250 CallPE* callPE = new CallPE(srcNode, dstNode, cs,entry);
253 return callPE;
254 }
255}
256
261{
262 SVFVar* srcNode = getGNode(src);
263 SVFVar* dstNode = getGNode(dst);
265 return nullptr;
266 else
267 {
268 RetPE* retPE = new RetPE(srcNode, dstNode, cs, exit);
269 addToStmt2TypeMap(retPE);
270 addEdge(srcNode,dstNode, retPE);
271 return retPE;
272 }
273}
274
279{
281 return pag->addAddrStmt(pag->getBlackHoleNode(), node);
282 else
283 return pag->addCopyStmt(pag->getNullPtr(), node, CopyStmt::COPYVAL);
284}
285
290{
291 SVFVar* srcNode = getGNode(src);
292 SVFVar* dstNode = getGNode(dst);
294 return nullptr;
295 else
296 {
297 TDForkPE* forkPE = new TDForkPE(srcNode, dstNode, cs, entry);
300 return forkPE;
301 }
302}
303
308{
309 SVFVar* srcNode = getGNode(src);
310 SVFVar* dstNode = getGNode(dst);
312 return nullptr;
313 else
314 {
315 TDJoinPE* joinPE = new TDJoinPE(srcNode, dstNode, cs, exit);
318 return joinPE;
319 }
320}
321
322
329{
330
331 SVFVar* node = getGNode(src);
332 if (!constGep || node->hasIncomingVariantGepEdge())
333 {
336 return addVariantGepStmt(src, dst, ap);
337 }
338 else
339 {
340 return addNormalGepStmt(src, dst, ap);
341 }
342}
343
348{
349 SVFVar* baseNode = getGNode(src);
350 SVFVar* dstNode = getGNode(dst);
352 return nullptr;
353 else
354 {
355 GepStmt* gepPE = new GepStmt(baseNode, dstNode, ap);
358 return gepPE;
359 }
360}
361
367{
368 SVFVar* baseNode = getGNode(src);
369 SVFVar* dstNode = getGNode(dst);
371 return nullptr;
372 else
373 {
374 GepStmt* gepPE = new GepStmt(baseNode, dstNode, ap, true);
377 return gepPE;
378 }
379}
380
381
382
388{
389 NodeID base = getValueNode(gepVal);
390 //assert(findPAGNode(i) == false && "this node should not be created before");
391 assert(0==GepValObjMap[curInst].count(std::make_pair(base, ap))
392 && "this node should not be created before");
393 GepValObjMap[curInst][std::make_pair(base, ap)] = i;
394 GepValVar *node = new GepValVar(base, gepVal, i, ap, type);
395 return addValNode(gepVal, node, i);
396}
397
402{
403 SVFVar* node = pag->getGNode(id);
404 if (GepObjVar* gepNode = SVFUtil::dyn_cast<GepObjVar>(node))
405 return getGepObjVar(gepNode->getMemObj(), gepNode->getConstantFieldIdx() + apOffset);
406 else if (BaseObjVar* baseNode = SVFUtil::dyn_cast<BaseObjVar>(node))
407 return getGepObjVar(baseNode->getMemObj(), apOffset);
408 else if (DummyObjVar* baseNode = SVFUtil::dyn_cast<DummyObjVar>(node))
409 return getGepObjVar(baseNode->getMemObj(), apOffset);
410 else
411 {
412 assert(false && "new gep obj node kind?");
413 return id;
414 }
415}
416
424{
425 NodeID base = obj->getId();
426
428 if (obj->isFieldInsensitive())
429 return getFIObjVar(obj);
430
431 APOffset newLS = pag->getSymbolInfo()->getModulusOffset(obj, apOffset);
432
433 // Base and first field are the same memory location.
434 if (Options::FirstFieldEqBase() && newLS == 0) return base;
435
436 NodeOffsetMap::iterator iter = GepObjVarMap.find(std::make_pair(base, newLS));
437 if (iter == GepObjVarMap.end())
438 {
440 return addGepObjNode(obj, newLS,gepId);
441 }
442 else
443 return iter->second;
444
445}
446
451{
452 //assert(findPAGNode(i) == false && "this node should not be created before");
453 NodeID base = obj->getId();
454 assert(0==GepObjVarMap.count(std::make_pair(base, apOffset))
455 && "this node should not be created before");
456
457 GepObjVarMap[std::make_pair(base, apOffset)] = gepId;
458 GepObjVar *node = new GepObjVar(obj, gepId, apOffset);
459 memToFieldsMap[base].set(gepId);
460 return addObjNode(obj->getValue(), node, gepId);
461}
462
467{
468 //assert(findPAGNode(i) == false && "this node should not be created before");
469 NodeID base = obj->getId();
470 memToFieldsMap[base].set(obj->getId());
471 BaseObjVar*node = new BaseObjVar(obj->getValue(), obj->getId(), obj);
472 return addObjNode(obj->getValue(), node, obj->getId());
473}
474
476{
477 const MemObj* mem = getMemObj(callGraphNode->getFunction());
478 assert(mem->getId() == id && "not same object id?");
479 //assert(findPAGNode(i) == false && "this node should not be created before");
480 NodeID base = mem->getId();
481 memToFieldsMap[base].set(mem->getId());
482 FunObjVar*node = new FunObjVar(callGraphNode, mem->getId(), mem);
483 return addObjNode(mem->getValue(), node, mem->getId());
484}
485
490{
491 NodeID base = obj->getId();
492 return memToFieldsMap[base];
493}
494
499{
500 const SVFVar* node = pag->getGNode(id);
501 assert(SVFUtil::isa<ObjVar>(node) && "need an object node");
502 const ObjVar* obj = SVFUtil::cast<ObjVar>(node);
503 return getAllFieldsObjVars(obj->getMemObj());
504}
505
512{
513 const SVFVar* node = pag->getGNode(id);
514 assert(SVFUtil::isa<ObjVar>(node) && "need an object node");
515 const MemObj* mem = SVFUtil::cast<ObjVar>(node)->getMemObj();
516 if(mem->isFieldInsensitive())
517 {
518 NodeBS bs;
519 bs.set(getFIObjVar(mem));
520 return bs;
521 }
522 else
523 return getAllFieldsObjVars(mem);
524}
525
530{
531 GepValueVarMap::const_iterator iter = GepValObjMap.find(curInst);
532 if(iter==GepValObjMap.end())
533 {
534 return UINT_MAX;
535 }
536 else
537 {
538 NodeAccessPathMap::const_iterator lit =
539 iter->second.find(std::make_pair(base, ap));
540 if (lit == iter->second.end())
541 return UINT_MAX;
542 else
543 return lit->second;
544 }
545}
546
547
552{
553 delete icfg;
554 icfg = nullptr;
555 delete chgraph;
556 chgraph = nullptr;
558 svfModule = nullptr;
559 delete callGraph;
560 callGraph = nullptr;
561}
562
567{
568
569 outs() << "-------------------SVFIR------------------------------------\n";
570 SVFStmt::SVFStmtSetTy& addrs = pag->getSVFStmtSet(SVFStmt::Addr);
571 for (SVFStmt::SVFStmtSetTy::iterator iter = addrs.begin(), eiter =
572 addrs.end(); iter != eiter; ++iter)
573 {
574 outs() << (*iter)->getSrcID() << " -- Addr --> " << (*iter)->getDstID()
575 << "\n";
576 }
577
578 SVFStmt::SVFStmtSetTy& copys = pag->getSVFStmtSet(SVFStmt::Copy);
579 for (SVFStmt::SVFStmtSetTy::iterator iter = copys.begin(), eiter =
580 copys.end(); iter != eiter; ++iter)
581 {
582 outs() << (*iter)->getSrcID() << " -- Copy --> " << (*iter)->getDstID()
583 << "\n";
584 }
585
586 SVFStmt::SVFStmtSetTy& calls = pag->getSVFStmtSet(SVFStmt::Call);
587 for (SVFStmt::SVFStmtSetTy::iterator iter = calls.begin(), eiter =
588 calls.end(); iter != eiter; ++iter)
589 {
590 outs() << (*iter)->getSrcID() << " -- Call --> " << (*iter)->getDstID()
591 << "\n";
592 }
593
594 SVFStmt::SVFStmtSetTy& rets = pag->getSVFStmtSet(SVFStmt::Ret);
595 for (SVFStmt::SVFStmtSetTy::iterator iter = rets.begin(), eiter =
596 rets.end(); iter != eiter; ++iter)
597 {
598 outs() << (*iter)->getSrcID() << " -- Ret --> " << (*iter)->getDstID()
599 << "\n";
600 }
601
603 for (SVFStmt::SVFStmtSetTy::iterator iter = tdfks.begin(), eiter =
604 tdfks.end(); iter != eiter; ++iter)
605 {
606 outs() << (*iter)->getSrcID() << " -- ThreadFork --> "
607 << (*iter)->getDstID() << "\n";
608 }
609
611 for (SVFStmt::SVFStmtSetTy::iterator iter = tdjns.begin(), eiter =
612 tdjns.end(); iter != eiter; ++iter)
613 {
614 outs() << (*iter)->getSrcID() << " -- ThreadJoin --> "
615 << (*iter)->getDstID() << "\n";
616 }
617
618 SVFStmt::SVFStmtSetTy& ngeps = pag->getSVFStmtSet(SVFStmt::Gep);
619 for (SVFStmt::SVFStmtSetTy::iterator iter = ngeps.begin(), eiter =
620 ngeps.end(); iter != eiter; ++iter)
621 {
622 GepStmt* gep = SVFUtil::cast<GepStmt>(*iter);
623 if(gep->isVariantFieldGep())
624 outs() << (*iter)->getSrcID() << " -- VariantGep --> "
625 << (*iter)->getDstID() << "\n";
626 else
627 outs() << gep->getRHSVarID() << " -- Gep (" << gep->getConstantStructFldIdx()
628 << ") --> " << gep->getLHSVarID() << "\n";
629 }
630
631 SVFStmt::SVFStmtSetTy& loads = pag->getSVFStmtSet(SVFStmt::Load);
632 for (SVFStmt::SVFStmtSetTy::iterator iter = loads.begin(), eiter =
633 loads.end(); iter != eiter; ++iter)
634 {
635 outs() << (*iter)->getSrcID() << " -- Load --> " << (*iter)->getDstID()
636 << "\n";
637 }
638
640 for (SVFStmt::SVFStmtSetTy::iterator iter = stores.begin(), eiter =
641 stores.end(); iter != eiter; ++iter)
642 {
643 outs() << (*iter)->getSrcID() << " -- Store --> " << (*iter)->getDstID()
644 << "\n";
645 }
646 outs() << "----------------------------------------------------------\n";
647
648}
649
652{
653 // collect candidate pointers for demand-driven analysis
654 for (iterator nIter = begin(); nIter != end(); ++nIter)
655 {
656 NodeID nodeId = nIter->first;
657 // do not compute points-to for isolated node
658 if (isValidPointer(nodeId) == false)
659 continue;
661 }
662}
663/*
664 * If this is a dummy node or node does not have incoming edges and outgoing edges we assume it is not a pointer here.
665 * However, if it is a pointer and it is an argument of a function definition, we assume it is a pointer here.
666 */
668{
669 SVFVar* node = pag->getGNode(nodeId);
670
671 if (node->hasValue() && node->isPointer())
672 {
673 if(const SVFArgument* arg = SVFUtil::dyn_cast<SVFArgument>(node->getValue()))
674 {
675 if (!(arg->getParent()->isDeclaration()))
676 return true;
677 }
678 }
679
680 if ((node->getInEdges().empty() && node->getOutEdges().empty()))
681 return false;
682 return node->isPointer();
683}
684
686{
687 if (SVFUtil::isa<ValVar>(node))
688 {
689 if (isValidPointer(node->getId()))
690 {
691 // TODO: after svf value is removed, we use type to determine top level ptr
692 if (SVFUtil::isa<RetPN, VarArgPN, FunValVar, HeapObjVar, StackObjVar>(node))
693 {
694 return true;
695 }
696 else if(node->hasValue())
698 }
699 }
700 return false;
701}
702
710
711
712
newitem type
Definition cJSON.cpp:2739
const cJSON *const b
Definition cJSON.h:255
int count
Definition cJSON.h:216
void setValue(T v)
std::vector< std::pair< const ICFGNode *, s32_t > > SuccAndCondPairVec
const SVFFunction * getFunction() const
Get function of this call node.
Definition CallGraph.h:131
IDToNodeMapTy::iterator iterator
Node Iterators.
NodeType * getGNode(NodeID id) const
Get a node.
const GEdgeSetTy & getOutEdges() const
const GEdgeSetTy & getInEdges() const
SVFStmt * hasLabeledEdge(SVFVar *src, SVFVar *dst, SVFStmt::PEDGEK kind, const ICFGNode *cs)
Definition IRGraph.cpp:88
NodeID getValueNode(const SVFValue *V)
Definition IRGraph.h:137
bool addEdge(SVFVar *src, SVFVar *dst, SVFStmt *edge)
Add an edge into the graph.
Definition IRGraph.cpp:45
SVFStmt * hasNonlabeledEdge(SVFVar *src, SVFVar *dst, SVFStmt::PEDGEK kind)
Definition IRGraph.cpp:60
const MemObj * getMemObj(const SVFValue *val) const
get MemObj according to LLVM value
Definition IRGraph.h:98
SymID getId() const
Get the memory object id.
bool isFieldInsensitive() const
Return true if its field limit is 0.
const SVFValue * getValue() const
Get the reference value to this object.
static NodeIDAllocator * get(void)
Return (singleton) allocator.
NodeID allocateGepObjectId(NodeID base, u32_t offset, u32_t maxFieldLimit)
static const Option< bool > FirstFieldEqBase
Definition Options.h:103
static Option< bool > HandBlackHole
Definition Options.h:102
static const Option< u32_t > MaxFieldLimit
Maximum number of field derivations for an object.
Definition Options.h:38
NodeID getId() const
Get ID.
NodeID addFIObjNode(const MemObj *obj)
Add a field-insensitive node, this method can only invoked by getFIGepObjNode.
Definition SVFIR.cpp:466
GepStmt * addVariantGepStmt(NodeID src, NodeID dst, const AccessPath &ap)
Add Variant(Gep) edge.
Definition SVFIR.cpp:366
NodeOffsetMap GepObjVarMap
Map a pair<base,off> to a gep obj node id.
Definition SVFIR.h:85
CopyStmt * addCopyStmt(NodeID src, NodeID dst, CopyStmt::CopyKind type)
Add Copy edge.
Definition SVFIR.cpp:65
RetPE * addRetPE(NodeID src, NodeID dst, const CallICFGNode *cs, const FunExitICFGNode *exit)
Add Return edge.
Definition SVFIR.cpp:260
GepStmt * addGepStmt(NodeID src, NodeID dst, const AccessPath &ap, bool constGep)
Add Gep edge.
Definition SVFIR.cpp:328
NodeID addObjNode(const SVFValue *val, NodeID i)
Add a memory obj node.
Definition SVFIR.h:615
void print()
Print SVFIR.
Definition SVFIR.cpp:566
static void handleBlackHole(bool b)
SVFIR build configurations.
Definition SVFIR.cpp:706
NodeID getFIObjVar(const MemObj *obj) const
Get a field-insensitive obj SVFIR node according to a mem obj.
Definition SVFIR.h:437
void addToStmt2TypeMap(SVFStmt *edge)
Map a SVFStatement type to a set of corresponding SVF statements.
Definition SVFIR.h:517
CommonCHGraph * chgraph
Definition SVFIR.h:100
SVFStmt * addBlackHoleAddrStmt(NodeID node)
Set a pointer points-to black hole (e.g. int2ptr)
Definition SVFIR.cpp:278
LoadStmt * addLoadStmt(NodeID src, NodeID dst)
Add Load edge.
Definition SVFIR.cpp:205
GepStmt * addNormalGepStmt(NodeID src, NodeID dst, const AccessPath &ap)
Add Offset(Gep) edge.
Definition SVFIR.cpp:347
MemObjToFieldsMap memToFieldsMap
Map a mem object id to all its fields.
Definition SVFIR.h:86
PHINodeMap phiNodeMap
A set of phi copy edges.
Definition SVFIR.h:88
NodeBS getFieldsAfterCollapse(NodeID id)
Definition SVFIR.cpp:511
NodeID addGepValNode(const SVFValue *curInst, const SVFValue *val, const AccessPath &ap, NodeID i, const SVFType *type)
Add a temp field value node, this method can only invoked by getGepValVar.
Definition SVFIR.cpp:387
CallPE * addCallPE(NodeID src, NodeID dst, const CallICFGNode *cs, const FunEntryICFGNode *entry)
Add Call edge.
Definition SVFIR.cpp:242
bool isValidTopLevelPtr(const SVFVar *node)
Definition SVFIR.cpp:685
NodeID addValNode(const SVFValue *val, NodeID i, const ICFGNode *icfgNode)
add node into SVFIR
Definition SVFIR.h:569
TDJoinPE * addThreadJoinPE(NodeID src, NodeID dst, const CallICFGNode *cs, const FunExitICFGNode *exit)
Add Thread join edge for parameter passing.
Definition SVFIR.cpp:307
CallGraph * callGraph
all the callsites of a program
Definition SVFIR.h:102
void destroy()
Clean up memory.
Definition SVFIR.cpp:551
NodeID addGepObjNode(const MemObj *obj, const APOffset &apOffset, const NodeID gepId)
Add a field obj node, this method can only invoked by getGepObjVar.
Definition SVFIR.cpp:450
NodeBS & getAllFieldsObjVars(const MemObj *obj)
Get all fields of an object.
Definition SVFIR.cpp:489
CmpStmt * addCmpStmt(NodeID op1, NodeID op2, NodeID dst, u32_t predict)
Add Copy edge.
Definition SVFIR.cpp:128
AddrStmt * addAddrStmt(NodeID src, NodeID dst)
Add an edge into SVFIR.
Definition SVFIR.cpp:47
UnaryOPStmt * addUnaryOPStmt(NodeID src, NodeID dst, u32_t opcode)
Add Unary edge.
Definition SVFIR.cpp:169
SVFModule * svfModule
Definition SVFIR.h:98
TDForkPE * addThreadForkPE(NodeID src, NodeID dst, const CallICFGNode *cs, const FunEntryICFGNode *entry)
Add Thread fork edge for parameter passing.
Definition SVFIR.cpp:289
BinaryOPStmt * addBinaryOPStmt(NodeID op1, NodeID op2, NodeID dst, u32_t opcode)
Add Copy edge.
Definition SVFIR.cpp:149
static std::unique_ptr< SVFIR > pag
call graph
Definition SVFIR.h:104
StoreStmt * addStoreStmt(NodeID src, NodeID dst, const ICFGNode *val)
Add Store edge.
Definition SVFIR.cpp:224
PhiStmt * addPhiStmt(NodeID res, NodeID opnd, const ICFGNode *pred)
Add phi node information.
Definition SVFIR.cpp:83
OrderedNodeSet candidatePointers
Definition SVFIR.h:97
ICFG * icfg
SVF Module.
Definition SVFIR.h:99
SVFIR(bool buildFromFile)
Constructor.
Definition SVFIR.cpp:40
SelectStmt * addSelectStmt(NodeID res, NodeID op1, NodeID op2, NodeID cond)
Add SelectStmt.
Definition SVFIR.cpp:107
NodeID getGepValVar(const SVFValue *curInst, NodeID base, const AccessPath &ap) const
Due to constraint expression, curInst is used to distinguish different instructions (e....
Definition SVFIR.cpp:529
GepValueVarMap GepValObjMap
Map a pair<base,off> to a gep value node id.
Definition SVFIR.h:83
NodeID addFunObjNode(const CallGraphNode *callGraphNode, NodeID id)
Definition SVFIR.cpp:475
bool isValidPointer(NodeID nodeId) const
Whether a node is a valid pointer.
Definition SVFIR.cpp:667
NodeID getGepObjVar(const MemObj *obj, const APOffset &ap)
Get a field SVFIR Object node according to base mem obj and offset.
Definition SVFIR.cpp:423
BranchStmt * addBranchStmt(NodeID br, NodeID cond, const BranchStmt::SuccAndCondPairVec &succs)
Add BranchStmt.
Definition SVFIR.cpp:187
void initialiseCandidatePointers()
Initialize candidate pointers.
Definition SVFIR.cpp:651
static void releaseSVFModule()
Definition SVFModule.cpp:69
GenericNode< SVFVar, SVFStmt >::GEdgeSetTy SVFStmtSetTy
const SVFValue * getValue() const
Get/has methods of the components.
bool hasValue() const
virtual bool isPointer() const
Whether it is a pointer.
bool hasIncomingVariantGepEdge() const
Has incoming VariantGepEdges.
void set(unsigned Idx)
bool isArgOfUncalledFunction(const SVFValue *svfval)
Return true if this argument belongs to an uncalled function.
Definition SVFUtil.h:346
std::ostream & outs()
Overwrite llvm::outs()
Definition SVFUtil.h:50
for isBitcode
Definition BasicTypes.h:68
u32_t NodeID
Definition GeneralType.h:55
s64_t APOffset
Definition GeneralType.h:60
llvm::IRBuilder IRBuilder
Definition BasicTypes.h:74
unsigned u32_t
Definition GeneralType.h:46